Files
codexbar-mobile/components/ServiceStatusCard.tsx
T
space d5b0f9c833
CodexBar Mobile Build / build-android (push) Successful in 31m31s
CodexBar Mobile Build / release (push) Failing after 23s
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>
2026-06-24 17:52:06 +02:00

125 lines
3.9 KiB
TypeScript

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>
);
}