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 { 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?.plan_type && (
{codex.usage.plan_type}
)}
{codex.auth && codex.status === "error" && (
)}
{codex.status === "success" && !codex.usage?.plan_type && (
)}
{!codex.auth && (
)}
{codex.auth && codex.status === "loading" && !codex.usage && (
)}
{codex.auth && codex.status === "error" && (
Failed to fetch usage
)}
{codex.usage && (
{!codex.usage.credits.unlimited &&
codex.usage.credits.has_credits && (
${Number(codex.usage.credits.balance).toFixed(2)}{" "}
credits remaining
)}
{codex.usage.credits.unlimited && (
Unlimited credits
)}
)}
{/* 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" && (
Failed to fetch usage
)}
{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 (
);
}