feat: update theme colors
This commit is contained in:
@@ -33,6 +33,7 @@ import { Dialog } from '@/components/dialog'
|
||||
import { Turnstile } from '@/components/turnstile'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { IconBadge } from '@/components/ui/icon-badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -162,7 +163,7 @@ export function CheckinCalendarCard({
|
||||
}
|
||||
toast.error(res.message || t('Check-in failed'))
|
||||
}
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
toast.error(t('Check-in failed'))
|
||||
} finally {
|
||||
setCheckinLoading(false)
|
||||
@@ -241,6 +242,13 @@ export function CheckinCalendarCard({
|
||||
)
|
||||
}
|
||||
|
||||
let checkinButtonLabel = t('Check in now')
|
||||
if (checkinLoading) {
|
||||
checkinButtonLabel = t('Loading...')
|
||||
} else if (checkedToday) {
|
||||
checkinButtonLabel = t('Checked in')
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider delay={100}>
|
||||
<Dialog
|
||||
@@ -282,12 +290,12 @@ export function CheckinCalendarCard({
|
||||
className='flex min-w-0 flex-1 items-start gap-3 rounded-lg text-left whitespace-normal outline-none'
|
||||
onClick={() => setCollapsed((v) => !v)}
|
||||
>
|
||||
<div className='bg-primary/10 text-primary flex h-10 w-10 shrink-0 items-center justify-center rounded-xl sm:h-11 sm:w-11'>
|
||||
<IconBadge tone='neutral' size='lg' className='sm:size-11'>
|
||||
<CalendarDays
|
||||
className='h-4 w-4 sm:h-5 sm:w-5'
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
</IconBadge>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='flex flex-wrap items-center gap-1.5 sm:gap-2'>
|
||||
<h3 className='text-base font-semibold tracking-tight sm:text-lg'>
|
||||
@@ -320,11 +328,7 @@ export function CheckinCalendarCard({
|
||||
size='sm'
|
||||
className='w-full shrink-0 sm:w-auto'
|
||||
>
|
||||
{checkinLoading
|
||||
? t('Loading...')
|
||||
: checkedToday
|
||||
? t('Checked in')
|
||||
: t('Check in now')}
|
||||
{checkinButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -405,7 +409,7 @@ export function CheckinCalendarCard({
|
||||
))}
|
||||
|
||||
{/* Calendar days */}
|
||||
{calendarDays.map((dayObj, idx) => {
|
||||
{calendarDays.map((dayObj) => {
|
||||
const dateStr = `${dayObj.date.getFullYear()}-${String(
|
||||
dayObj.date.getMonth() + 1
|
||||
).padStart(2, '0')}-${String(
|
||||
@@ -418,7 +422,7 @@ export function CheckinCalendarCard({
|
||||
|
||||
const dayButton = (
|
||||
<Button
|
||||
key={idx}
|
||||
key={dateStr}
|
||||
variant={isToday ? 'default' : 'ghost'}
|
||||
disabled={!dayObj.isCurrentMonth}
|
||||
className={cn(
|
||||
@@ -430,15 +434,15 @@ export function CheckinCalendarCard({
|
||||
>
|
||||
<span className='tabular-nums'>{dayNum}</span>
|
||||
{isCheckedIn && !isToday && (
|
||||
<span className='absolute bottom-0.5 h-1 w-1 rounded-full bg-emerald-500 sm:bottom-1' />
|
||||
<span className='bg-success absolute bottom-0.5 size-1 rounded-full sm:bottom-1' />
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
|
||||
if (isCheckedIn && dayObj.isCurrentMonth) {
|
||||
return (
|
||||
<Tooltip key={idx}>
|
||||
<TooltipTrigger render={dayButton}></TooltipTrigger>
|
||||
<Tooltip key={dateStr}>
|
||||
<TooltipTrigger render={dayButton} />
|
||||
<TooltipContent>
|
||||
<div className='text-xs'>
|
||||
<div className='font-medium'>
|
||||
|
||||
@@ -93,7 +93,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
|
||||
|
||||
props.onProfileUpdate()
|
||||
toast.success(t('Language preference saved'))
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
setCurrentLanguage(previousLanguage)
|
||||
await i18n.changeLanguage(previousLanguage)
|
||||
toast.error(t('Failed to update settings'))
|
||||
@@ -107,6 +107,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
|
||||
title={t('Language Preferences')}
|
||||
description={t('Set the language used across the interface')}
|
||||
icon={<Languages className='h-4 w-4' />}
|
||||
iconTone='chart-4'
|
||||
disableHoverEffect
|
||||
>
|
||||
<div className='flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4'>
|
||||
@@ -120,12 +121,10 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
|
||||
</div>
|
||||
<div className='flex items-center gap-2 sm:min-w-48'>
|
||||
<Select
|
||||
items={[
|
||||
...INTERFACE_LANGUAGE_OPTIONS.map((language) => ({
|
||||
value: language.code,
|
||||
label: language.label,
|
||||
})),
|
||||
]}
|
||||
items={INTERFACE_LANGUAGE_OPTIONS.map((language) => ({
|
||||
value: language.code,
|
||||
label: language.label,
|
||||
}))}
|
||||
value={currentLanguage}
|
||||
onValueChange={handleLanguageChange}
|
||||
disabled={saving}
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { IconBadge } from '@/components/ui/icon-badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { usePasskeyManagement } from '@/features/auth/passkey'
|
||||
import {
|
||||
@@ -242,9 +243,9 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
|
||||
<div className='space-y-6'>
|
||||
<div className='flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between xl:flex-col 2xl:flex-row'>
|
||||
<div className='flex items-start gap-4'>
|
||||
<div className='bg-muted rounded-md p-2'>
|
||||
<KeyRound className='h-5 w-5' />
|
||||
</div>
|
||||
<IconBadge tone='info' size='sm'>
|
||||
<KeyRound />
|
||||
</IconBadge>
|
||||
<div className='space-y-1'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
<p className='font-medium'>{t('Passkey Authentication')}</p>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { IconBadge, type IconBadgeTone } from '@/components/ui/icon-badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { getUserAvatarFallback, getUserAvatarStyle } from '@/lib/avatar'
|
||||
import { formatCompactNumber, formatQuota } from '@/lib/format'
|
||||
@@ -63,8 +64,8 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
|
||||
</CardContent>
|
||||
<div className='border-t'>
|
||||
<div className='divide-border/60 grid grid-cols-1 divide-y sm:grid-cols-3 sm:divide-x sm:divide-y-0'>
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className='px-4 py-3.5 sm:px-5 sm:py-4'>
|
||||
{['balance', 'usage', 'requests'].map((key) => (
|
||||
<div key={key} className='px-4 py-3.5 sm:px-5 sm:py-4'>
|
||||
<Skeleton className='h-3.5 w-20' />
|
||||
<Skeleton className='mt-2 h-7 w-28' />
|
||||
<Skeleton className='mt-1.5 h-3.5 w-24' />
|
||||
@@ -83,24 +84,33 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
|
||||
const avatarFallback = getUserAvatarFallback(avatarName)
|
||||
const avatarFallbackStyle = getUserAvatarStyle(avatarName)
|
||||
const roleLabel = getRoleLabel(profile.role)
|
||||
const stats = [
|
||||
const stats: {
|
||||
label: string
|
||||
value: string
|
||||
description: string
|
||||
icon: typeof WalletCards
|
||||
tone: IconBadgeTone
|
||||
}[] = [
|
||||
{
|
||||
label: t('Current Balance'),
|
||||
value: formatQuota(profile.quota),
|
||||
description: t('Remaining quota'),
|
||||
icon: WalletCards,
|
||||
tone: 'success',
|
||||
},
|
||||
{
|
||||
label: t('Total Usage'),
|
||||
value: formatQuota(profile.used_quota),
|
||||
description: t('Total consumed quota'),
|
||||
icon: BarChart3,
|
||||
tone: 'info',
|
||||
},
|
||||
{
|
||||
label: t('API Requests'),
|
||||
value: formatCompactNumber(profile.request_count),
|
||||
description: t('Total requests made'),
|
||||
icon: Activity,
|
||||
tone: 'chart-4',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -157,7 +167,9 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
|
||||
{stats.map((item) => (
|
||||
<div key={item.label} className='min-w-0 px-3 py-3 sm:px-5 sm:py-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<item.icon className='text-muted-foreground/60 size-3.5 shrink-0' />
|
||||
<IconBadge tone={item.tone} size='stat'>
|
||||
<item.icon />
|
||||
</IconBadge>
|
||||
<div className='text-muted-foreground truncate text-xs font-medium tracking-wider uppercase'>
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Shield, Key, Trash2 } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { IconBadge } from '@/components/ui/icon-badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TitledCard } from '@/components/ui/titled-card'
|
||||
import { useDialogs } from '@/hooks/use-dialog'
|
||||
@@ -55,8 +56,8 @@ export function ProfileSecurityCard({
|
||||
<Skeleton className='mt-2 h-4 w-48' />
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3 p-3 sm:p-5'>
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<Skeleton key={i} className='h-16 w-full' />
|
||||
{['password', 'token', 'delete'].map((key) => (
|
||||
<Skeleton key={key} className='h-16 w-full' />
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -95,6 +96,7 @@ export function ProfileSecurityCard({
|
||||
title={t('Security')}
|
||||
description={t('Manage your security settings and account access')}
|
||||
icon={<Shield className='h-4 w-4' />}
|
||||
iconTone='success'
|
||||
disableHoverEffect
|
||||
>
|
||||
<div className='grid grid-cols-1 gap-2.5 sm:gap-3 md:grid-cols-3'>
|
||||
@@ -107,15 +109,9 @@ export function ProfileSecurityCard({
|
||||
item.variant === 'destructive' ? 'border-destructive/30' : ''
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`rounded-md p-2 ${
|
||||
item.variant === 'destructive'
|
||||
? 'bg-destructive/10 text-destructive'
|
||||
: 'bg-muted'
|
||||
}`}
|
||||
>
|
||||
<item.icon className='h-5 w-5' />
|
||||
</div>
|
||||
<IconBadge tone='neutral' size='sm'>
|
||||
<item.icon />
|
||||
</IconBadge>
|
||||
<div className='min-w-0 md:contents'>
|
||||
<p className='text-sm font-medium'>{item.title}</p>
|
||||
<p className='text-muted-foreground line-clamp-1 text-xs md:line-clamp-none'>
|
||||
|
||||
@@ -56,8 +56,8 @@ export function ProfileSettingsCard({
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4 p-3 sm:p-5'>
|
||||
<Skeleton className='h-10 w-full' />
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<Skeleton key={i} className='h-20 w-full' />
|
||||
{['bindings', 'preferences', 'notifications'].map((key) => (
|
||||
<Skeleton key={key} className='h-20 w-full' />
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -69,6 +69,7 @@ export function ProfileSettingsCard({
|
||||
title={t('Settings')}
|
||||
description={t('Configure your account preferences and integrations')}
|
||||
icon={<Settings className='h-4 w-4' />}
|
||||
iconTone='info'
|
||||
disableHoverEffect
|
||||
>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { IconBadge } from '@/components/ui/icon-badge'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { api } from '@/lib/api'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
@@ -204,9 +205,9 @@ export function SidebarModulesCard() {
|
||||
<Card data-card-hover='false' className='gap-0 overflow-hidden py-0'>
|
||||
<CardHeader className='border-b p-3 !pb-3 sm:p-5 sm:!pb-5'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='bg-muted flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9'>
|
||||
<LayoutDashboard className='h-4 w-4' />
|
||||
</div>
|
||||
<IconBadge tone='info' size='title'>
|
||||
<LayoutDashboard />
|
||||
</IconBadge>
|
||||
<div className='min-w-0'>
|
||||
<CardTitle className='text-lg tracking-tight sm:text-xl'>
|
||||
{t('Sidebar Personal Settings')}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { IconBadge } from '@/components/ui/icon-badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useDialogs } from '@/hooks/use-dialog'
|
||||
|
||||
@@ -82,9 +83,9 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) {
|
||||
{/* Status Section */}
|
||||
<div className='flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between xl:flex-col 2xl:flex-row'>
|
||||
<div className='flex items-start gap-4'>
|
||||
<div className='bg-muted rounded-md p-2'>
|
||||
<Shield className='h-5 w-5' />
|
||||
</div>
|
||||
<IconBadge tone='success' size='sm'>
|
||||
<Shield />
|
||||
</IconBadge>
|
||||
<div className='space-y-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<p className='font-medium'>{t('Two-Step Verification')}</p>
|
||||
|
||||
Reference in New Issue
Block a user