refactor: update styling and improve code readability across multiple components
This commit is contained in:
+139
-84
@@ -1,3 +1,12 @@
|
||||
import {
|
||||
Copy,
|
||||
Check,
|
||||
RefreshCw,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
RotateCcw,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react'
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
@@ -17,20 +26,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { type ReactNode, useCallback, useMemo, useState } from 'react'
|
||||
import {
|
||||
Copy,
|
||||
Check,
|
||||
RefreshCw,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
RotateCcw,
|
||||
AlertTriangle,
|
||||
} from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import { formatDateTimeStr, formatTimestampToDate } from '@/lib/format'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -55,9 +55,11 @@ import {
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import { formatDateTimeStr, formatTimestampToDate } from '@/lib/format'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import {
|
||||
getCodexResetCredits,
|
||||
resetCodexUsage,
|
||||
@@ -153,9 +155,13 @@ function formatUnixSeconds(unixSeconds: unknown): string {
|
||||
}
|
||||
|
||||
function formatIsoTimestamp(value: unknown): string {
|
||||
if (typeof value !== 'string' || value.trim() === '') return '-'
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
return '-'
|
||||
}
|
||||
const d = dayjs(value)
|
||||
if (!d.isValid()) return value
|
||||
if (!d.isValid()) {
|
||||
return value
|
||||
}
|
||||
return formatDateTimeStr(d.toDate())
|
||||
}
|
||||
|
||||
@@ -164,15 +170,21 @@ function formatDurationSeconds(
|
||||
t: (key: string) => string
|
||||
): string {
|
||||
const s = Number(seconds)
|
||||
if (!Number.isFinite(s) || s <= 0) return '-'
|
||||
if (!Number.isFinite(s) || s <= 0) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const total = Math.floor(s)
|
||||
const hours = Math.floor(total / 3600)
|
||||
const minutes = Math.floor((total % 3600) / 60)
|
||||
const secs = total % 60
|
||||
|
||||
if (hours > 0) return `${hours}${t('h')} ${minutes}${t('m')}`
|
||||
if (minutes > 0) return `${minutes}${t('m')} ${secs}${t('s')}`
|
||||
if (hours > 0) {
|
||||
return `${hours}${t('h')} ${minutes}${t('m')}`
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}${t('m')} ${secs}${t('s')}`
|
||||
}
|
||||
return `${secs}${t('s')}`
|
||||
}
|
||||
|
||||
@@ -180,12 +192,18 @@ function formatTimeLeftUntil(
|
||||
value: unknown,
|
||||
t: (key: string) => string
|
||||
): string {
|
||||
if (typeof value !== 'string' || value.trim() === '') return '-'
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
return '-'
|
||||
}
|
||||
const expiresAt = dayjs(value)
|
||||
if (!expiresAt.isValid()) return '-'
|
||||
if (!expiresAt.isValid()) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const secondsLeft = expiresAt.diff(dayjs(), 'second')
|
||||
if (secondsLeft <= 0) return t('Expired')
|
||||
if (secondsLeft <= 0) {
|
||||
return t('Expired')
|
||||
}
|
||||
|
||||
const days = Math.floor(secondsLeft / (24 * 60 * 60))
|
||||
const remainingSeconds = secondsLeft % (24 * 60 * 60)
|
||||
@@ -198,7 +216,9 @@ function formatTimeLeftUntil(
|
||||
}
|
||||
|
||||
function normalizePlanType(value: unknown): string {
|
||||
if (value == null) return ''
|
||||
if (value == null) {
|
||||
return ''
|
||||
}
|
||||
return String(value).trim().toLowerCase()
|
||||
}
|
||||
|
||||
@@ -220,15 +240,21 @@ function sortResetCredits(credits: CodexResetCredit[]): CodexResetCredit[] {
|
||||
return [...credits].sort((a, b) => {
|
||||
const aAvailable = normalizeResetCreditStatus(a.status) === 'available'
|
||||
const bAvailable = normalizeResetCreditStatus(b.status) === 'available'
|
||||
if (aAvailable !== bAvailable) return aAvailable ? -1 : 1
|
||||
if (aAvailable !== bAvailable) {
|
||||
return aAvailable ? -1 : 1
|
||||
}
|
||||
|
||||
const expiresDiff =
|
||||
parseTimeValue(a.expires_at) - parseTimeValue(b.expires_at)
|
||||
if (expiresDiff !== 0) return expiresDiff
|
||||
if (expiresDiff !== 0) {
|
||||
return expiresDiff
|
||||
}
|
||||
|
||||
const grantedDiff =
|
||||
parseTimeValue(a.granted_at) - parseTimeValue(b.granted_at)
|
||||
if (grantedDiff !== 0) return grantedDiff
|
||||
if (grantedDiff !== 0) {
|
||||
return grantedDiff
|
||||
}
|
||||
|
||||
return String(a.id || '').localeCompare(String(b.id || ''))
|
||||
})
|
||||
@@ -238,7 +264,9 @@ function classifyWindowByDuration(
|
||||
windowData?: CodexRateLimitWindow | null
|
||||
): 'weekly' | 'fiveHour' | null {
|
||||
const seconds = Number(windowData?.limit_window_seconds)
|
||||
if (!Number.isFinite(seconds) || seconds <= 0) return null
|
||||
if (!Number.isFinite(seconds) || seconds <= 0) {
|
||||
return null
|
||||
}
|
||||
return seconds >= 24 * 60 * 60 ? 'weekly' : 'fiveHour'
|
||||
}
|
||||
|
||||
@@ -272,7 +300,9 @@ function resolveRateLimitWindows(data: RateLimitSource | null): {
|
||||
}
|
||||
|
||||
if (planType === 'free') {
|
||||
if (!weeklyWindow) weeklyWindow = primary ?? secondary ?? null
|
||||
if (!weeklyWindow) {
|
||||
weeklyWindow = primary ?? secondary ?? null
|
||||
}
|
||||
return { fiveHourWindow: null, weeklyWindow }
|
||||
}
|
||||
|
||||
@@ -338,8 +368,12 @@ function getResetCreditStatusBadge(
|
||||
|
||||
function windowLabel(windowData?: CodexRateLimitWindow | null) {
|
||||
const percent = clampPercent(windowData?.used_percent)
|
||||
const variant: StatusBadgeProps['variant'] =
|
||||
percent >= 95 ? 'danger' : percent >= 80 ? 'warning' : 'info'
|
||||
let variant: StatusBadgeProps['variant'] = 'info'
|
||||
if (percent >= 95) {
|
||||
variant = 'danger'
|
||||
} else if (percent >= 80) {
|
||||
variant = 'warning'
|
||||
}
|
||||
return { percent, variant }
|
||||
}
|
||||
|
||||
@@ -381,7 +415,7 @@ const percentTextClassName: Record<
|
||||
grey: 'text-muted-foreground',
|
||||
indigo: 'text-chart-1',
|
||||
'light-blue': 'text-info',
|
||||
'light-green': 'text-success',
|
||||
'light-green': 'text-emerald-500 dark:text-emerald-300',
|
||||
lime: 'text-chart-3',
|
||||
orange: 'text-warning',
|
||||
pink: 'text-chart-5',
|
||||
@@ -708,6 +742,51 @@ function ResetCreditsPanel(props: {
|
||||
? String(props.payload?.total_earned_count)
|
||||
: '-'
|
||||
const canReset = Number(availableCount) > 0
|
||||
let creditsContent: ReactNode
|
||||
if (props.errorMessage) {
|
||||
creditsContent = (
|
||||
<div className='border-destructive/40 bg-destructive/10 text-destructive rounded-lg border px-3 py-2 text-sm'>
|
||||
{props.errorMessage}
|
||||
</div>
|
||||
)
|
||||
} else if (props.isLoading) {
|
||||
creditsContent = (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Skeleton className='h-24 w-full' />
|
||||
<Skeleton className='h-24 w-full' />
|
||||
</div>
|
||||
)
|
||||
} else if (credits.length > 0) {
|
||||
creditsContent = (
|
||||
<div className='flex flex-col gap-2'>
|
||||
{credits.map((credit, index) => (
|
||||
<ResetCreditItem
|
||||
key={
|
||||
credit.id ??
|
||||
credit.expires_at ??
|
||||
credit.granted_at ??
|
||||
credit.title ??
|
||||
credit.reset_type ??
|
||||
''
|
||||
}
|
||||
credit={credit}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
creditsContent = (
|
||||
<Empty className='min-h-32 border'>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{t('No reset credits')}</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
{t('Upstream did not return reset credit details.')}
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-3 p-3'>
|
||||
@@ -796,35 +875,7 @@ function ResetCreditsPanel(props: {
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{props.errorMessage ? (
|
||||
<div className='border-destructive/40 bg-destructive/10 text-destructive rounded-lg border px-3 py-2 text-sm'>
|
||||
{props.errorMessage}
|
||||
</div>
|
||||
) : props.isLoading ? (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Skeleton className='h-24 w-full' />
|
||||
<Skeleton className='h-24 w-full' />
|
||||
</div>
|
||||
) : credits.length > 0 ? (
|
||||
<div className='flex flex-col gap-2'>
|
||||
{credits.map((credit, index) => (
|
||||
<ResetCreditItem
|
||||
key={`${credit.id ?? credit.expires_at ?? 'credit'}-${index}`}
|
||||
credit={credit}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Empty className='min-h-32 border'>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{t('No reset credits')}</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
{t('Upstream did not return reset credit details.')}
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
)}
|
||||
{creditsContent}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -853,13 +904,17 @@ export function CodexUsageDialog({
|
||||
|
||||
const payload: CodexUsagePayload | null = useMemo(() => {
|
||||
const raw = response?.data
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
if (!raw || typeof raw !== 'object') {
|
||||
return null
|
||||
}
|
||||
return raw as CodexUsagePayload
|
||||
}, [response?.data])
|
||||
|
||||
const resetCreditsPayload: CodexResetCreditsPayload | null = useMemo(() => {
|
||||
const raw = resetCreditsResponse?.data
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
if (!raw || typeof raw !== 'object') {
|
||||
return null
|
||||
}
|
||||
return raw as CodexResetCreditsPayload
|
||||
}, [resetCreditsResponse?.data])
|
||||
|
||||
@@ -892,7 +947,9 @@ export function CodexUsageDialog({
|
||||
setResetCreditsError(t('Channel ID is required'))
|
||||
return
|
||||
}
|
||||
if (isLoadingResetCredits || (!force && resetCreditsResponse)) return
|
||||
if (isLoadingResetCredits || (!force && resetCreditsResponse)) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoadingResetCredits(true)
|
||||
setResetCreditsError('')
|
||||
@@ -940,7 +997,9 @@ export function CodexUsageDialog({
|
||||
}
|
||||
|
||||
const handleConfirmReset = async () => {
|
||||
if (!channelId || isResetting || !canResetCodexUsage) return
|
||||
if (!channelId || isResetting || !canResetCodexUsage) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsResetting(true)
|
||||
setResetActionError('')
|
||||
@@ -975,7 +1034,9 @@ export function CodexUsageDialog({
|
||||
}
|
||||
|
||||
const rawJsonText = useMemo(() => {
|
||||
if (!response) return ''
|
||||
if (!response) {
|
||||
return ''
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(
|
||||
{
|
||||
@@ -1002,15 +1063,13 @@ export function CodexUsageDialog({
|
||||
contentHeight='auto'
|
||||
bodyClassName='flex flex-col gap-4'
|
||||
footer={
|
||||
<>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={() => handleDialogOpenChange(false)}
|
||||
>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={() => handleDialogOpenChange(false)}
|
||||
>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-col gap-4'>
|
||||
@@ -1074,11 +1133,7 @@ export function CodexUsageDialog({
|
||||
) : null}
|
||||
</div>
|
||||
<div className='mt-4 grid grid-cols-1 gap-3 md:grid-cols-2'>
|
||||
<InfoField
|
||||
label={t('Email')}
|
||||
value={payload?.email}
|
||||
copyable={true}
|
||||
/>
|
||||
<InfoField label={t('Email')} value={payload?.email} copyable />
|
||||
<InfoField
|
||||
label={t('Channel')}
|
||||
value={channelLabel}
|
||||
@@ -1171,14 +1226,14 @@ export function CodexUsageDialog({
|
||||
)}
|
||||
/>
|
||||
<div className='flex flex-col gap-3'>
|
||||
{additionalRateLimits.map((item, index) => {
|
||||
{additionalRateLimits.map((item) => {
|
||||
const limitName =
|
||||
item.limit_name ||
|
||||
item.metered_feature ||
|
||||
`${t('Additional Limit')} ${index + 1}`
|
||||
t('Additional Limit')
|
||||
return (
|
||||
<RateLimitGroupSection
|
||||
key={`${limitName}-${item.metered_feature ?? ''}-${index}`}
|
||||
key={`${limitName}-${item.metered_feature ?? ''}-${item.plan_type ?? ''}`}
|
||||
title={limitName}
|
||||
description={t('Additional metered capability')}
|
||||
source={item}
|
||||
|
||||
Reference in New Issue
Block a user