()
| 16 | export const areCreditsRestored = () => creditsRestoredGlobal |
| 17 | |
| 18 | export const OutOfCreditsBanner = () => { |
| 19 | if (IS_FREEBUFF) return null |
| 20 | |
| 21 | const sessionCreditsUsed = useChatStore((state) => state.sessionCreditsUsed) |
| 22 | const [creditsRestored, setCreditsRestored] = useState(false) |
| 23 | |
| 24 | const { data: apiData } = useUsageQuery({ |
| 25 | enabled: true, |
| 26 | refetchInterval: CREDIT_POLL_INTERVAL, |
| 27 | }) |
| 28 | |
| 29 | // Get cached data for immediate display |
| 30 | const cachedUsageData = getActivityQueryData<{ |
| 31 | type: 'usage-response' |
| 32 | usage: number |
| 33 | remainingBalance: number | null |
| 34 | balanceBreakdown?: { free: number; paid: number; ad?: number } |
| 35 | next_quota_reset: string | null |
| 36 | }>(usageQueryKeys.current()) |
| 37 | |
| 38 | const theme = useTheme() |
| 39 | const activeData = apiData || cachedUsageData |
| 40 | const remainingBalance = activeData?.remainingBalance ?? 0 |
| 41 | |
| 42 | // Track if we've confirmed the zero-balance state to avoid false positives from stale cache |
| 43 | const [confirmedZeroBalance, setConfirmedZeroBalance] = useState(false) |
| 44 | |
| 45 | // Reset global flag when component mounts (handles re-entry to out-of-credits mode) |
| 46 | useEffect(() => { |
| 47 | creditsRestoredGlobal = false |
| 48 | }, []) |
| 49 | |
| 50 | // Confirm zero balance on first fetch to avoid race condition with cached data |
| 51 | useEffect(() => { |
| 52 | if (apiData && !confirmedZeroBalance) { |
| 53 | if ((apiData.remainingBalance ?? 0) <= 0) { |
| 54 | setConfirmedZeroBalance(true) |
| 55 | } |
| 56 | } |
| 57 | }, [apiData, confirmedZeroBalance]) |
| 58 | |
| 59 | // Check if credits have been restored - show celebratory message |
| 60 | useEffect(() => { |
| 61 | // Only check for restoration after we've confirmed zero balance |
| 62 | if (!confirmedZeroBalance || remainingBalance <= 0 || creditsRestored) { |
| 63 | return |
| 64 | } |
| 65 | |
| 66 | // Credits restored! Show the success state |
| 67 | setCreditsRestored(true) |
| 68 | creditsRestoredGlobal = true |
| 69 | }, [remainingBalance, creditsRestored, confirmedZeroBalance]) |
| 70 | |
| 71 | // Build stats text |
| 72 | const statsText = activeData |
| 73 | ? `Session: ${sessionCreditsUsed.toLocaleString()} credits used · Balance: ${remainingBalance.toLocaleString()} credits` |
| 74 | : `Session: ${sessionCreditsUsed.toLocaleString()} credits used` |
| 75 |
nothing calls this directly
no test coverage detected