import { ScrollView, View, Text, TouchableOpacity, RefreshControl, ActivityIndicator, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { router, useFocusEffect } from "expo-router"; import { useCallback, useEffect } from "react"; import { MaterialIcons } from "@expo/vector-icons"; import { useCodexUsage } from "@/hooks/useCodexUsage"; import { useClaudeUsage } from "@/hooks/useClaudeUsage"; import { onUsageDataLoaded } from "@/lib/notifications"; import { ProgressBar } from "@/components/ProgressBar"; import { ResetCountdown } from "@/components/ResetCountdown"; import { LastUpdated } from "@/components/LastUpdated"; import { ErrorMessage } from "@/components/ErrorMessage"; import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary"; import { COLORS, getUsageColor } from "@/lib/constants"; import { windowLabel } from "@/lib/timeUtils"; function UsageRow({ label, percent, resetAtSeconds, resetAtISO, }: { label: string; percent: number; resetAtSeconds?: number; resetAtISO?: string; }) { const color = getUsageColor(percent); return ( {label} {Math.round(percent)}% used ); } function SetupPrompt({ label }: { label: string }) { return ( {label} router.navigate("/(tabs)/settings")} className="flex-row items-center self-start gap-x-1.5 px-3 py-2 rounded-xl border border-neutral-200 dark:border-neutral-700" > Set up in Settings ); } function DashboardTabContent() { const codex = useCodexUsage(); const claude = useClaudeUsage(); // Fire daily digest reschedule + threshold check whenever fresh data arrives useEffect(() => { if (codex.usage || claude.usage) { void onUsageDataLoaded( claude.usage, codex.usage ); } }, [codex.lastFetchedAt, claude.lastFetchedAt]); useFocusEffect( useCallback(() => { void codex.reloadCredentials(); void claude.reloadCredentials(); }, [codex.reloadCredentials, claude.reloadCredentials]) ); const isRefreshing = codex.status === "loading" || claude.status === "loading"; const handleRefresh = () => { void codex.refresh(); void claude.refresh(); }; const connectedCount = [codex.auth, claude.auth].filter(Boolean).length; return ( } > {/* Header */} Codexbar {connectedCount === 0 ? "No services connected" : connectedCount === 2 ? "2 services connected" : "1 of 2 services connected"} {/* Codex card */} Codex CLI {codex.usage?.planType && ( {codex.usage.planType} )} {codex.auth && codex.status === "error" && ( )} {codex.status === "success" && !codex.usage?.planType && ( )} {!codex.auth && ( )} {codex.auth && codex.status === "loading" && !codex.usage && ( )} {codex.auth && codex.status === "error" && ( )} {codex.usage && ( {codex.usage.primary && ( )} {codex.usage.secondary && ( )} {!codex.usage.credits.unlimited && codex.usage.credits.hasCredits && ( ${Number(codex.usage.credits.balance).toFixed(2)}{" "} credits remaining )} {codex.usage.credits.unlimited && ( Unlimited credits )} {!!codex.usage.resetCoupons && ( {codex.usage.resetCoupons.availableCount ?? 0} reset credits available {!!codex.usage.resetCoupons.nextExpiringCredit?.timeUntilExpiry && ( Next expires in {codex.usage.resetCoupons.nextExpiringCredit.timeUntilExpiry} )} )} )} {/* Claude card */} Claude.ai {claude.auth && claude.status === "error" && ( )} {claude.status === "success" && ( )} {!claude.auth && ( )} {claude.auth && claude.status === "loading" && !claude.usage && ( )} {claude.auth && claude.status === "error" && ( )} {claude.usage && ( {(claude.usage.seven_day_sonnet || claude.usage.seven_day_opus) && ( By Model (7-day) {claude.usage.seven_day_sonnet && ( )} {claude.usage.seven_day_opus && ( )} )} )} ); } export default function DashboardTab() { return ( ); }