d5b0f9c833
- 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>
149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
import {
|
|
ScrollView,
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
} from "react-native";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import { router, useFocusEffect } from "expo-router";
|
|
import { useCallback } from "react";
|
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
import { useCodexUsage } from "@/hooks/useCodexUsage";
|
|
import { useClaudeUsage } from "@/hooks/useClaudeUsage";
|
|
import { ServiceStatusCard } from "@/components/ServiceStatusCard";
|
|
import { windowLabel } from "@/lib/timeUtils";
|
|
|
|
export default function DashboardTab() {
|
|
const codex = useCodexUsage();
|
|
const claude = useClaudeUsage();
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
codex.reload();
|
|
claude.reload();
|
|
}, [])
|
|
);
|
|
|
|
const isRefreshing =
|
|
codex.status === "loading" || claude.status === "loading";
|
|
|
|
const handleRefresh = () => {
|
|
codex.refresh();
|
|
claude.refresh();
|
|
};
|
|
|
|
const codexRows =
|
|
codex.usage
|
|
? [
|
|
{
|
|
label: `Primary (${windowLabel(codex.usage.rate_limit.primary_window.limit_window_seconds)})`,
|
|
percent: codex.usage.rate_limit.primary_window.used_percent,
|
|
resetAtSeconds: codex.usage.rate_limit.primary_window.reset_at,
|
|
},
|
|
{
|
|
label: `Secondary (${windowLabel(codex.usage.rate_limit.secondary_window.limit_window_seconds)})`,
|
|
percent: codex.usage.rate_limit.secondary_window.used_percent,
|
|
resetAtSeconds: codex.usage.rate_limit.secondary_window.reset_at,
|
|
},
|
|
]
|
|
: undefined;
|
|
|
|
const claudeRows =
|
|
claude.usage
|
|
? [
|
|
{
|
|
label: "5-hour window",
|
|
percent: claude.usage.five_hour.utilization,
|
|
resetAtISO: claude.usage.five_hour.resets_at,
|
|
},
|
|
{
|
|
label: "7-day window",
|
|
percent: claude.usage.seven_day.utilization,
|
|
resetAtISO: claude.usage.seven_day.resets_at,
|
|
},
|
|
]
|
|
: undefined;
|
|
|
|
return (
|
|
<SafeAreaView
|
|
className="flex-1 bg-neutral-50 dark:bg-neutral-950"
|
|
edges={["top", "bottom"]}
|
|
>
|
|
<ScrollView
|
|
className="flex-1"
|
|
contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 32 }}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={isRefreshing}
|
|
onRefresh={handleRefresh}
|
|
tintColor="#10a37f"
|
|
/>
|
|
}
|
|
>
|
|
{/* Header */}
|
|
<View className="flex-row items-center justify-between py-5">
|
|
<View>
|
|
<Text className="text-2xl font-bold text-neutral-900 dark:text-white tracking-tight">
|
|
Codexbar
|
|
</Text>
|
|
<Text className="text-xs text-neutral-400 mt-0.5">
|
|
AI usage monitor
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={handleRefresh}
|
|
className="w-9 h-9 rounded-xl bg-neutral-100 dark:bg-neutral-800 items-center justify-center"
|
|
>
|
|
<MaterialIcons name="refresh" size={18} color="#10a37f" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Codex card */}
|
|
<ServiceStatusCard
|
|
title="Codex CLI"
|
|
icon="auto-awesome"
|
|
accentColor="#10a37f"
|
|
status={!codex.auth ? "idle" : codex.status}
|
|
badge={codex.usage?.plan_type}
|
|
rows={codexRows}
|
|
unconfiguredLabel="Import auth.json to see your Codex rate limits."
|
|
onConfigurePress={() => router.navigate("/(tabs)/codex")}
|
|
footer={
|
|
codex.usage && !codex.usage.credits.unlimited && codex.usage.credits.has_credits
|
|
? (
|
|
<View className="flex-row items-center gap-x-2">
|
|
<MaterialIcons name="toll" size={14} color="#a3a3a3" />
|
|
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
|
|
${Number(codex.usage.credits.balance).toFixed(2)} credits remaining
|
|
</Text>
|
|
</View>
|
|
)
|
|
: codex.usage?.credits.unlimited
|
|
? (
|
|
<View className="flex-row items-center gap-x-2">
|
|
<MaterialIcons name="all-inclusive" size={14} color="#10a37f" />
|
|
<Text className="text-xs text-green-600 dark:text-green-400">
|
|
Unlimited credits
|
|
</Text>
|
|
</View>
|
|
)
|
|
: null
|
|
}
|
|
/>
|
|
|
|
{/* Claude card */}
|
|
<ServiceStatusCard
|
|
title="Claude.ai"
|
|
icon="psychology"
|
|
accentColor="#d97706"
|
|
status={!claude.sessionKey ? "idle" : claude.status}
|
|
rows={claudeRows}
|
|
unconfiguredLabel="Add your session key to see Claude usage windows."
|
|
onConfigurePress={() => router.navigate("/(tabs)/claude")}
|
|
/>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|