Maximus upgradus
CodexBar Mobile Build / build-android (push) Failing after 1m38s
CodexBar Mobile Build / release (push) Has been skipped

This commit is contained in:
2026-06-24 20:16:34 +02:00
parent d5b0f9c833
commit ba7d957155
20 changed files with 1192 additions and 642 deletions
+249 -82
View File
@@ -4,20 +4,81 @@ import {
Text,
TouchableOpacity,
RefreshControl,
ActivityIndicator,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { router, useFocusEffect } from "expo-router";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import { MaterialIcons } from "@expo/vector-icons";
import { useCodexUsage } from "@/hooks/useCodexUsage";
import { useClaudeUsage } from "@/hooks/useClaudeUsage";
import { ServiceStatusCard } from "@/components/ServiceStatusCard";
import { onUsageDataLoaded } from "@/lib/notifications";
import { ProgressBar } from "@/components/ProgressBar";
import { ResetCountdown } from "@/components/ResetCountdown";
import { windowLabel } from "@/lib/timeUtils";
function UsageRow({
label,
percent,
resetAtSeconds,
resetAtISO,
}: {
label: string;
percent: number;
resetAtSeconds?: number;
resetAtISO?: string;
}) {
const color =
percent >= 90 ? "#ef4444" : percent >= 70 ? "#f59e0b" : "#10a37f";
return (
<View className="mb-4">
<View className="flex-row justify-between items-baseline mb-1.5">
<Text className="text-xs font-medium text-neutral-500 dark:text-neutral-400">
{label}
</Text>
<Text className="text-sm font-bold" style={{ color }}>
{Math.round(percent)}% used
</Text>
</View>
<ProgressBar percent={percent} />
<ResetCountdown resetAtSeconds={resetAtSeconds} resetAtISO={resetAtISO} />
</View>
);
}
function SetupPrompt({ label }: { label: string }) {
return (
<View>
<Text className="text-sm text-neutral-400 dark:text-neutral-500 mb-3">
{label}
</Text>
<TouchableOpacity
onPress={() => 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"
>
<Text className="text-xs font-semibold text-neutral-600 dark:text-neutral-300">
Set up in Settings
</Text>
<MaterialIcons name="arrow-forward" size={12} color="#737373" />
</TouchableOpacity>
</View>
);
}
export default function DashboardTab() {
const codex = useCodexUsage();
const claude = useClaudeUsage();
// Fire daily digest reschedule + threshold check whenever fresh data arrives
useEffect(() => {
if (codex.status === "success" || claude.status === "success") {
void onUsageDataLoaded(
claude.status === "success" ? claude.usage : null,
codex.status === "success" ? codex.usage : null
);
}
}, [codex.status, claude.status]);
useFocusEffect(
useCallback(() => {
codex.reload();
@@ -33,46 +94,16 @@ export default function DashboardTab() {
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;
const connectedCount = [codex.auth, claude.auth].filter(Boolean).length;
return (
<SafeAreaView
className="flex-1 bg-neutral-50 dark:bg-neutral-950"
edges={["top", "bottom"]}
edges={["top"]}
>
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 32 }}
contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 40 }}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
@@ -82,14 +113,23 @@ export default function DashboardTab() {
}
>
{/* 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 className="flex-row items-center justify-between pt-6 pb-5">
<View className="flex-row items-center gap-x-3">
<View className="w-10 h-10 rounded-2xl bg-emerald-500 items-center justify-center">
<MaterialIcons name="bar-chart" size={20} color="white" />
</View>
<View>
<Text className="text-xl font-bold text-neutral-900 dark:text-white tracking-tight">
Codexbar
</Text>
<Text className="text-xs text-neutral-400 mt-0.5">
{connectedCount === 0
? "No services connected"
: connectedCount === 2
? "2 services connected"
: "1 of 2 services connected"}
</Text>
</View>
</View>
<TouchableOpacity
onPress={handleRefresh}
@@ -100,48 +140,175 @@ export default function DashboardTab() {
</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 className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
<View style={{ height: 3, backgroundColor: "#10a37f" }} />
<View className="p-4">
<View className="flex-row items-center justify-between mb-4">
<View className="flex-row items-center gap-x-2.5">
<View
className="w-8 h-8 rounded-xl items-center justify-center"
style={{ backgroundColor: "#10a37f18" }}
>
<MaterialIcons name="auto-awesome" size={16} color="#10a37f" />
</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
}
/>
<Text className="text-sm font-semibold text-neutral-900 dark:text-white">
Codex CLI
</Text>
</View>
<View className="flex-row items-center gap-x-2">
{codex.usage?.plan_type && (
<View className="px-2.5 py-1 rounded-full bg-green-100 dark:bg-green-900/30">
<Text className="text-xs font-semibold text-green-700 dark:text-green-300 capitalize">
{codex.usage.plan_type}
</Text>
</View>
)}
{codex.auth && codex.status === "error" && (
<View className="w-2 h-2 rounded-full bg-red-500" />
)}
{codex.status === "success" && !codex.usage?.plan_type && (
<View className="w-2 h-2 rounded-full bg-green-500" />
)}
</View>
</View>
{!codex.auth && (
<SetupPrompt label="Import auth.json to see your Codex rate limits." />
)}
{codex.auth && codex.status === "loading" && (
<ActivityIndicator
color="#10a37f"
style={{ marginVertical: 16 }}
/>
)}
{codex.auth && codex.status === "error" && (
<View className="flex-row items-center gap-x-2 py-1">
<MaterialIcons name="error-outline" size={16} color="#ef4444" />
<Text className="text-sm text-red-500 dark:text-red-400">
Failed to fetch usage
</Text>
</View>
)}
{codex.status === "success" && codex.usage && (
<View>
<UsageRow
label={`${windowLabel(codex.usage.rate_limit.primary_window.limit_window_seconds)} window`}
percent={codex.usage.rate_limit.primary_window.used_percent}
resetAtSeconds={codex.usage.rate_limit.primary_window.reset_at}
/>
<UsageRow
label={`${windowLabel(codex.usage.rate_limit.secondary_window.limit_window_seconds)} window`}
percent={
codex.usage.rate_limit.secondary_window.used_percent
}
resetAtSeconds={
codex.usage.rate_limit.secondary_window.reset_at
}
/>
{!codex.usage.credits.unlimited &&
codex.usage.credits.has_credits && (
<View className="pt-3 border-t border-neutral-100 dark:border-neutral-800 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="pt-3 border-t border-neutral-100 dark:border-neutral-800 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>
)}
</View>
)}
</View>
</View>
{/* 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")}
/>
<View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
<View style={{ height: 3, backgroundColor: "#d97706" }} />
<View className="p-4">
<View className="flex-row items-center justify-between mb-4">
<View className="flex-row items-center gap-x-2.5">
<View
className="w-8 h-8 rounded-xl items-center justify-center"
style={{ backgroundColor: "#d9770618" }}
>
<MaterialIcons name="psychology" size={16} color="#d97706" />
</View>
<Text className="text-sm font-semibold text-neutral-900 dark:text-white">
Claude.ai
</Text>
</View>
{claude.auth && claude.status === "error" && (
<View className="w-2 h-2 rounded-full bg-red-500" />
)}
{claude.status === "success" && (
<View className="w-2 h-2 rounded-full bg-green-500" />
)}
</View>
{!claude.auth && (
<SetupPrompt label="Add your session key to see Claude usage windows." />
)}
{claude.auth && claude.status === "loading" && (
<ActivityIndicator
color="#d97706"
style={{ marginVertical: 16 }}
/>
)}
{claude.auth && claude.status === "error" && (
<View className="flex-row items-center gap-x-2 py-1">
<MaterialIcons name="error-outline" size={16} color="#ef4444" />
<Text className="text-sm text-red-500 dark:text-red-400">
Failed to fetch usage
</Text>
</View>
)}
{claude.status === "success" && claude.usage && (
<View>
<UsageRow
label="5-hour window"
percent={claude.usage.five_hour.utilization}
resetAtISO={claude.usage.five_hour.resets_at}
/>
<UsageRow
label="7-day window"
percent={claude.usage.seven_day.utilization}
resetAtISO={claude.usage.seven_day.resets_at}
/>
{(claude.usage.seven_day_sonnet ||
claude.usage.seven_day_opus) && (
<View className="pt-3 border-t border-neutral-100 dark:border-neutral-800">
<Text className="text-xs font-semibold uppercase tracking-widest text-neutral-400 mb-3">
By Model (7-day)
</Text>
{claude.usage.seven_day_sonnet && (
<UsageRow
label="Sonnet"
percent={claude.usage.seven_day_sonnet.utilization}
/>
)}
{claude.usage.seven_day_opus && (
<UsageRow
label="Opus"
percent={claude.usage.seven_day_opus.utilization}
/>
)}
</View>
)}
</View>
)}
</View>
</View>
</ScrollView>
</SafeAreaView>
);