From fc52c9890095afe8aff2106562ef508da640a887 Mon Sep 17 00:00:00 2001 From: Ooxygen7 <216126594+Ooxygen7@users.noreply.github.com> Date: Sun, 9 Aug 2026 03:02:53 +0800 Subject: [PATCH] Refresh entitlement state after redemption --- .../components/redemptions-columns.tsx | 4 +- .../src/features/usage-logs/data/schema.ts | 24 +++++ .../features/wallet/hooks/use-redemption.ts | 95 +++++++++++-------- 3 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 web/default/src/features/usage-logs/data/schema.ts diff --git a/web/default/src/features/redemption-codes/components/redemptions-columns.tsx b/web/default/src/features/redemption-codes/components/redemptions-columns.tsx index 4e1f7ae..f2811b5 100644 --- a/web/default/src/features/redemption-codes/components/redemptions-columns.tsx +++ b/web/default/src/features/redemption-codes/components/redemptions-columns.tsx @@ -261,13 +261,13 @@ export function useRedemptionsColumns(): ColumnDef[] { ) }, - size: 140, + size: 150, }, { id: 'actions', header: () => t('Actions'), cell: ({ row }) => , - meta: { pinned: 'right' as const }, + size: 72, }, ] } diff --git a/web/default/src/features/usage-logs/data/schema.ts b/web/default/src/features/usage-logs/data/schema.ts new file mode 100644 index 0000000..4fde90c --- /dev/null +++ b/web/default/src/features/usage-logs/data/schema.ts @@ -0,0 +1,24 @@ +/** API shape returned by the common usage-log endpoints. */ +export interface UsageLog { + id: number + user_id: number + created_at: number + type: number + content: string + username: string + token_name: string + model_name: string + quota: number + prompt_tokens: number + completion_tokens: number + use_time: number + is_stream: boolean + channel: number + channel_name: string + token_id: number + group: string + ip: string + request_id?: string + upstream_request_id?: string + other: string +} diff --git a/web/default/src/features/wallet/hooks/use-redemption.ts b/web/default/src/features/wallet/hooks/use-redemption.ts index c58fe2d..8810c3a 100644 --- a/web/default/src/features/wallet/hooks/use-redemption.ts +++ b/web/default/src/features/wallet/hooks/use-redemption.ts @@ -16,12 +16,14 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ +import { useQueryClient } from '@tanstack/react-query' import i18next from 'i18next' import { useState, useCallback } from 'react' import { toast } from 'sonner' import { getSelf } from '@/lib/api' import { formatQuota } from '@/lib/format' +import { useAuthStore, type AuthUser } from '@/stores/auth-store' import { redeemTopupCode } from '../api' @@ -30,51 +32,66 @@ import { redeemTopupCode } from '../api' // ============================================================================ export function useRedemption() { + const queryClient = useQueryClient() const [redeeming, setRedeeming] = useState(false) - const redeemCode = useCallback(async (code: string): Promise => { - if (!code || code.trim() === '') { - toast.error(i18next.t('Please enter a redemption code')) - return false - } + const redeemCode = useCallback( + async (code: string): Promise => { + if (!code || code.trim() === '') { + toast.error(i18next.t('Please enter a redemption code')) + return false + } - try { - setRedeeming(true) - const response = await redeemTopupCode({ key: code }) + try { + setRedeeming(true) + const response = await redeemTopupCode({ key: code }) - if (response.success && response.data !== undefined) { - if (typeof response.data === 'number') { - toast.success( - i18next.t('Redemption successful! Added: {{quota}}', { - quota: formatQuota(response.data), - }) - ) - } else if (response.data.type === 'group') { - toast.success( - i18next.t('Group entitlement activated: {{group}}', { - group: response.data.group_name, - }) - ) - } else { - toast.success( - i18next.t('Redemption successful! Added: {{quota}}', { - quota: formatQuota(response.data.quota), - }) - ) + if (response.success && response.data !== undefined) { + if (typeof response.data === 'number') { + toast.success( + i18next.t('Redemption successful! Added: {{quota}}', { + quota: formatQuota(response.data), + }) + ) + } else if (response.data.type === 'group') { + toast.success( + i18next.t('Group entitlement activated: {{group}}', { + group: response.data.group_name, + }) + ) + } else { + toast.success( + i18next.t('Redemption successful! Added: {{quota}}', { + quota: formatQuota(response.data.quota), + }) + ) + } + const selfResponse = await getSelf().catch(() => null) + if (selfResponse?.success && selfResponse.data) { + useAuthStore.getState().auth.setUser(selfResponse.data as AuthUser) + } + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['model-catalog'] }), + queryClient.invalidateQueries({ queryKey: ['user-models'] }), + queryClient.invalidateQueries({ + queryKey: ['user-models-ccswitch'], + }), + queryClient.invalidateQueries({ queryKey: ['user-groups'] }), + ]) + return true } - await getSelf() - return true - } - toast.error(response.message || i18next.t('Redemption failed')) - return false - } catch { - toast.error(i18next.t('Redemption failed')) - return false - } finally { - setRedeeming(false) - } - }, []) + toast.error(response.message || i18next.t('Redemption failed')) + return false + } catch { + toast.error(i18next.t('Redemption failed')) + return false + } finally { + setRedeeming(false) + } + }, + [queryClient] + ) return { redeeming,