import { useAtomValue } from "@effect/atom-react"; import { useNavigation, type StaticScreenProps } from "@react-navigation/native"; import { EnvironmentId } from "@t3tools/contracts"; import { collectLimitAccounts, collectLimitNotices, collectLimitPools, formatDuration, formatResetsIn, remainingPercent, type LimitAccount, type LimitPoolWindow, } from "@t3tools/shared/usageLimits"; import { useId, useState } from "react"; import { Platform, Pressable, ScrollView, View } from "react-native"; import { Defs, Path, Pattern, Rect, Svg } from "react-native-svg"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; import { SymbolView } from "../../components/AppSymbol"; import { AppText as Text } from "../../components/AppText"; import { ProviderIcon } from "../../components/ProviderIcon"; import { NativeStackScreenOptions } from "../../native/StackHeader"; import { environmentPresentations } from "../../state/presentation"; import { ResetCredits } from "./UsageLimitsSection"; import { useProviderColors } from "./usageProviders"; const DRIVER_LABEL: Partial> = { codex: "Codex", claudeAgent: "Claude" }; const PACE_LABEL = { ahead: "Ahead of pace", on: "On pace", under: "Under pace" } as const; function accountName(account: LimitAccount) { if (account.displayName) return account.displayName; if (!account.email) return DRIVER_LABEL[account.driver] ?? String(account.driver); const [local = "", domain = ""] = account.email.split("@"); return `${local[0] ?? ""}${domain[0] ?? ""}`.toUpperCase() || "Account"; } /** The spent share comes back at reset. SVG keeps the hatching static on both platforms. */ function AccountSegment({ remaining, color, pending, }: { readonly remaining: number; readonly color: string; readonly pending: boolean; }) { const patternId = useId().replace(/:/g, ""); return ( {pending ? ( ) : null} ); } function PoolWindowCard({ pool, color, now, environmentIds, }: { readonly pool: LimitPoolWindow; readonly color: string; readonly now: number; readonly environmentIds: readonly string[] | null; }) { const navigation = useNavigation(); const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); const openAccount = (account: LimitAccount) => navigation.navigate("SettingsSheet", { screen: "SettingsContent", params: { screen: "SettingsUsageAccount", params: { accountKey: account.key, windowId: pool.id, windowKind: pool.kind, environmentIds, now, }, }, }); return ( {pool.label} {pool.remainingPercent}% left {pool.pace ? ( {PACE_LABEL[pool.pace]} ) : null} {nextRefill ? ( ↻ +{nextRefill.restoresPercent}%{" "} {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`} ) : null} {pool.columns.map(({ account, window }, index) => { if (!window) return ; return ( openAccount(account)} className="h-7 min-w-0 flex-1 overflow-hidden rounded-md bg-subtle" > {index + 1} ); })} {pool.columns.map(({ account, window }, index) => { if (!window) return null; const credits = account.limits.resetCredits?.availableCount ?? 0; const resetsIn = formatResetsIn(window, now); return ( openAccount(account)} className="min-h-[44px] flex-row items-center gap-2 active:opacity-60" > {index + 1} {accountName(account)} {remainingPercent(window)}% {resetsIn ? ( {resetsIn.replace("resets in ", "↻ ")} ) : null} {credits ? ( <> {resetsIn ? · : null} {credits} ) : null} ); })} ); } export function UsageLimitsSection({ now, failedLabels, selectedEnvironmentIds, }: { readonly now: number; readonly failedLabels: readonly string[]; readonly selectedEnvironmentIds: ReadonlySet | null; }) { const presentations = useAtomValue(environmentPresentations.presentationsAtom); const selected = selectedEnvironmentIds === null ? presentations : new Map([...presentations].filter(([id]) => selectedEnvironmentIds.has(id))); const pools = collectLimitPools(collectLimitAccounts(selected), now); const notices = collectLimitNotices(selected); const colors = useProviderColors(); return ( {failedLabels.length ? ( {failedLabels.join(", ")} could not refresh limits. Showing the last known values. ) : null} {pools.length === 0 ? ( {selected.size === 0 ? "Select an environment to see limits." : "No provider on the selected environments reports subscription limits."} ) : null} {pools.map((pool) => ( {DRIVER_LABEL[pool.driver] ?? pool.driver} {pool.windows.map((window) => ( ))} ))} {notices.map((notice) => ( {notice} ))} ); } type AccountScreenProps = StaticScreenProps<{ accountKey: string; windowId: string; windowKind: LimitPoolWindow["kind"]; environmentIds: readonly string[] | null; now: number; }>; /** Resolve the account again so live quota and credit updates reach the open detail screen. */ export function UsageLimitAccountScreen({ route }: AccountScreenProps) { const navigation = useNavigation(); const insets = useSafeAreaInsets(); const presentations = useAtomValue(environmentPresentations.presentationsAtom); const { accountKey, windowId, windowKind, environmentIds, now } = route.params; const selectedIds = environmentIds === null ? null : new Set(environmentIds.map((id) => EnvironmentId.make(id))); const selected = selectedIds === null ? presentations : new Map([...presentations].filter(([id]) => selectedIds.has(id))); const accounts = collectLimitAccounts(selected); const account = accounts.find((candidate) => candidate.key === accountKey); const pool = collectLimitPools(accounts, now) .find((candidate) => candidate.driver === account?.driver) ?.windows.find((candidate) => candidate.id === windowId && candidate.kind === windowKind); const window = pool?.members.find((member) => member.account.key === accountKey)?.window; const reset = pool?.resets.find((candidate) => candidate.member.account.key === accountKey); const [revealed, setRevealed] = useState(false); return ( {Platform.OS === "android" ? ( <> navigation.goBack()} /> ) : null} {!account || !window ? ( This account is no longer reporting limits on the selected environments. ) : ( <> {account.displayName ?? DRIVER_LABEL[account.driver] ?? account.driver} {account.email ? ( setRevealed((value) => !value)} className="min-h-[44px] justify-center" > {revealed ? account.email : "••••••@••••••"} ) : null} {account.plan ? ( {account.plan} ) : null} {window.label} {remainingPercent(window)}% left {window.resetsAt ? ( Resets{" "} {new Date(window.resetsAt).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short", })} ) : null} {reset && reset.restoresPercent > 0 ? ( Restores {reset.restoresPercent}% of the pool ) : null} {account.environments.length ? "Signed in" : "Source"} {account.environments.length ? ( account.environments.map((environment) => ( {environment.label} )) ) : ( {account.sourceLabel} )} {account.redeem && account.limits.resetCredits ? ( Reset credits ) : null} )} ); }