Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,13 @@ export function useRedemptionsColumns(): ColumnDef<Redemption>[] {
</Tooltip>
)
},
size: 140,
size: 150,
},
{
id: 'actions',
header: () => t('Actions'),
cell: ({ row }) => <DataTableRowActions row={row} />,
meta: { pinned: 'right' as const },
size: 72,
},
]
}
24 changes: 24 additions & 0 deletions web/default/src/features/usage-logs/data/schema.ts
Original file line number Diff line number Diff line change
@@ -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
}
95 changes: 56 additions & 39 deletions web/default/src/features/wallet/hooks/use-redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

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'

Expand All @@ -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<boolean> => {
if (!code || code.trim() === '') {
toast.error(i18next.t('Please enter a redemption code'))
return false
}
const redeemCode = useCallback(
async (code: string): Promise<boolean> => {
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,
Expand Down