/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useQueryClient } from '@tanstack/react-query' import { Loader2, RefreshCw, DollarSign } from 'lucide-react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Dialog } from '@/components/dialog' import { Button } from '@/components/ui/button' import { IconBadge } from '@/components/ui/icon-badge' import { formatCurrencyFromUSD } from '@/lib/currency' import { formatTimestampToDate } from '@/lib/format' import { getCodexUsage, updateChannelBalance } from '../../api' import { channelsQueryKeys } from '../../lib' import { useChannels } from '../channels-provider' import { CodexUsageDialog, type CodexUsageDialogData, } from './codex-usage-dialog' type BalanceQueryDialogProps = { open: boolean onOpenChange: (open: boolean) => void } export function BalanceQueryDialog({ open, onOpenChange, }: BalanceQueryDialogProps) { const { t } = useTranslation() const { currentRow, setCurrentRow } = useChannels() const queryClient = useQueryClient() const [isQuerying, setIsQuerying] = useState(false) const [balance, setBalance] = useState(null) const [balanceUpdatedTime, setBalanceUpdatedTime] = useState( null ) const [codexUsageResponse, setCodexUsageResponse] = useState(null) const isCodex = currentRow?.type === 57 const handleQueryCodexUsage = async () => { const row = currentRow if (!row) return setIsQuerying(true) try { const res = await getCodexUsage(row.id) if (!res.success) { throw new Error(res.message || t('Failed to fetch usage')) } setCodexUsageResponse(res) } catch (error: unknown) { toast.error( error instanceof Error ? error.message : t('Failed to fetch usage') ) } finally { setIsQuerying(false) } } useEffect(() => { if (!isCodex) return if (!open) return handleQueryCodexUsage() // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, isCodex]) if (!currentRow) return null const handleQueryBalance = async () => { setIsQuerying(true) try { const response = await updateChannelBalance(currentRow.id) if (response.success && response.balance !== undefined) { const newBalance = response.balance const now = Math.floor(Date.now() / 1000) setBalance(newBalance) setBalanceUpdatedTime(now) toast.success(t('Balance updated successfully')) // Update currentRow immediately with new balance and timestamp setCurrentRow({ ...currentRow, balance: newBalance, balance_updated_time: now, }) // Invalidate queries to refresh the table await queryClient.invalidateQueries({ queryKey: channelsQueryKeys.lists(), }) } else { toast.error(response.message || t('Failed to query balance')) } } catch (error: unknown) { toast.error( error instanceof Error ? error.message : t('Failed to query balance') ) } finally { setIsQuerying(false) } } const handleClose = () => { setBalance(null) setBalanceUpdatedTime(null) setCodexUsageResponse(null) onOpenChange(false) } const formatBalance = (bal: number) => formatCurrencyFromUSD(bal, { digitsLarge: 2, digitsSmall: 4, abbreviate: false, }) const formatDate = (timestamp: number) => { if (!timestamp) return 'Never' return formatTimestampToDate(timestamp) } if (isCodex) { return ( { if (!v) handleClose() }} channelName={currentRow.name} channelId={currentRow.id} response={codexUsageResponse} onRefresh={handleQueryCodexUsage} isRefreshing={isQuerying} /> ) } return ( {t('Update balance for:')} {currentRow.name} } contentHeight='auto' bodyClassName='space-y-4' footer={ } >
{/* Current Balance Display */}
{t('Current Balance')}
{balance !== null ? formatBalance(balance) : formatBalance(currentRow.balance)}
{t('Last updated:')}{' '} {formatDate(balanceUpdatedTime ?? currentRow.balance_updated_time)}
{/* Balance Update Button */}
) }