feat: production-ready UI with onboarding wizard and dashboard
- Onboarding flow (welcome -> Codex setup -> Claude setup -> done) with dark-themed screens and step indicators; completion stored in SecureStore - Smart root redirect checks onboarding state on launch - Dashboard tab shows both services at a glance via ServiceStatusCard; useFocusEffect + reload() keeps it fresh when credentials change in tabs - Settings tab manages credentials and exposes re-run setup wizard - Polished Codex and Claude detail screens with service headers and empty states - 4-tab layout (Home, Codex, Claude, Settings) with dark-mode tab bar - Both hooks gain reload() for cross-tab credential refresh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { View, Text, TouchableOpacity, ActivityIndicator } from "react-native";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { ProgressBar } from "./ProgressBar";
|
||||
import { ResetCountdown } from "./ResetCountdown";
|
||||
|
||||
interface UsageRow {
|
||||
label: string;
|
||||
percent: number;
|
||||
resetAtSeconds?: number;
|
||||
resetAtISO?: string;
|
||||
}
|
||||
|
||||
interface ServiceStatusCardProps {
|
||||
title: string;
|
||||
icon: React.ComponentProps<typeof MaterialIcons>["name"];
|
||||
accentColor: string;
|
||||
status: "idle" | "loading" | "success" | "error";
|
||||
badge?: string;
|
||||
rows?: UsageRow[];
|
||||
footer?: React.ReactNode;
|
||||
unconfiguredLabel?: string;
|
||||
onConfigurePress?: () => void;
|
||||
}
|
||||
|
||||
export function ServiceStatusCard({
|
||||
title,
|
||||
icon,
|
||||
accentColor,
|
||||
status,
|
||||
badge,
|
||||
rows,
|
||||
footer,
|
||||
unconfiguredLabel,
|
||||
onConfigurePress,
|
||||
}: ServiceStatusCardProps) {
|
||||
return (
|
||||
<View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 p-4 mb-4">
|
||||
{/* Header */}
|
||||
<View className="flex-row items-center justify-between mb-3">
|
||||
<View className="flex-row items-center gap-x-2.5">
|
||||
<View
|
||||
className="w-8 h-8 rounded-xl items-center justify-center"
|
||||
style={{ backgroundColor: `${accentColor}20` }}
|
||||
>
|
||||
<MaterialIcons name={icon} size={16} color={accentColor} />
|
||||
</View>
|
||||
<Text className="text-sm font-semibold text-neutral-800 dark:text-white">
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
{badge && (
|
||||
<View className="px-2.5 py-1 rounded-full bg-green-100 dark:bg-green-900/40">
|
||||
<Text className="text-xs font-semibold text-green-700 dark:text-green-300 capitalize">
|
||||
{badge}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{status === "success" && !badge && (
|
||||
<View className="w-2 h-2 rounded-full bg-green-500" />
|
||||
)}
|
||||
{status === "error" && (
|
||||
<View className="w-2 h-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Body */}
|
||||
{status === "loading" && (
|
||||
<ActivityIndicator color={accentColor} style={{ marginVertical: 12 }} />
|
||||
)}
|
||||
|
||||
{status === "idle" && (
|
||||
<View className="py-2 items-start">
|
||||
<Text className="text-sm text-neutral-400 dark:text-neutral-500 mb-3">
|
||||
{unconfiguredLabel ?? "Not configured"}
|
||||
</Text>
|
||||
{onConfigurePress && (
|
||||
<TouchableOpacity
|
||||
onPress={onConfigurePress}
|
||||
className="px-3 py-1.5 rounded-lg border border-neutral-200 dark:border-neutral-700"
|
||||
>
|
||||
<Text className="text-xs font-semibold text-neutral-600 dark:text-neutral-300">
|
||||
Configure →
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{status === "error" && (
|
||||
<Text className="text-sm text-red-500 dark:text-red-400 py-2">
|
||||
Failed to load usage data
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{status === "success" && rows && (
|
||||
<View>
|
||||
{rows.map((row, i) => (
|
||||
<View key={i} className={`gap-y-1 ${i < rows.length - 1 ? "mb-3" : ""}`}>
|
||||
<View className="flex-row justify-between items-center">
|
||||
<Text className="text-xs font-medium text-neutral-500 dark:text-neutral-400">
|
||||
{row.label}
|
||||
</Text>
|
||||
<Text className="text-xs font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
{Math.round(row.percent)}%
|
||||
</Text>
|
||||
</View>
|
||||
<ProgressBar percent={row.percent} />
|
||||
<ResetCountdown
|
||||
resetAtSeconds={row.resetAtSeconds}
|
||||
resetAtISO={row.resetAtISO}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{footer && (
|
||||
<View className="mt-3 pt-3 border-t border-neutral-100 dark:border-neutral-800">
|
||||
{footer}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user