style(ui): align wallet and profile interactions

This commit is contained in:
t0ng7u
2026-06-16 00:44:33 +08:00
parent b36888ba9c
commit eb86311604
39 changed files with 341 additions and 314 deletions
@@ -350,7 +350,7 @@ export function AccountBindingsTab({
<Button
variant='ghost'
size='sm'
className='text-destructive hover:text-destructive h-7 shrink-0 px-2.5 text-xs'
className='text-destructive h-7 shrink-0 px-2.5 text-xs'
onClick={() => setUnbindTarget(binding)}
>
<Unlink className='mr-1 h-3 w-3' />
@@ -24,8 +24,8 @@ import { ROLE } from '@/lib/roles'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Switch } from '@/components/ui/switch'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { PasswordInput } from '@/components/password-input'
import { updateUserSettings } from '../../api'
import {
@@ -35,13 +35,24 @@ import {
import { parseUserSettings } from '../../lib'
import type { UserProfile, UserSettings, NotifyType } from '../../types'
const NOTIFICATION_ICONS: Record<string, typeof Mail> = {
const NOTIFICATION_ICONS: Record<NotifyType, typeof Mail> = {
email: Mail,
webhook: Webhook,
bark: Bell,
gotify: Server,
}
const NOTIFICATION_VALUES = new Set<NotifyType>(
NOTIFICATION_METHODS.map((method) => method.value)
)
function normalizeNotifyType(value: unknown): NotifyType {
return typeof value === 'string' &&
NOTIFICATION_VALUES.has(value as NotifyType)
? (value as NotifyType)
: 'email'
}
// ============================================================================
// Settings Tab Component
// ============================================================================
@@ -82,7 +93,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
if (profile?.setting) {
const parsed = parseUserSettings(profile.setting)
setSettings({
notify_type: parsed.notify_type || 'email',
notify_type: normalizeNotifyType(parsed.notify_type),
quota_warning_threshold:
parsed.quota_warning_threshold ?? DEFAULT_QUOTA_WARNING_THRESHOLD,
notification_email: parsed.notification_email ?? '',
@@ -119,44 +130,42 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
}
}
const notifyType = normalizeNotifyType(settings.notify_type)
return (
<div className='space-y-4 sm:space-y-6'>
{/* Notification Type */}
<div className='space-y-2.5'>
<Label>{t('Notification Method')}</Label>
<RadioGroup
value={settings.notify_type}
onValueChange={(value) =>
updateField('notify_type', value as NotifyType)
}
className='grid grid-cols-4 gap-1.5 sm:gap-3'
<ToggleGroup
value={[notifyType]}
onValueChange={(value) => {
const nextValue = value.find((item) => item !== notifyType)
if (nextValue)
updateField('notify_type', normalizeNotifyType(nextValue))
}}
aria-label={t('Notification Method')}
variant='outline'
size='lg'
spacing={2}
className='grid w-full grid-cols-2 gap-2 sm:grid-cols-4 sm:gap-3'
>
{NOTIFICATION_METHODS.map((method) => {
const Icon = NOTIFICATION_ICONS[method.value]
const isSelected = settings.notify_type === method.value
return (
<Label
<ToggleGroupItem
key={method.value}
htmlFor={method.value}
className={`flex min-h-16 cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg border p-2 text-center transition-colors sm:min-h-20 sm:gap-2 sm:border-2 sm:p-3 ${
isSelected
? 'border-primary bg-primary/5 text-primary'
: 'border-muted hover:border-muted-foreground/25 hover:bg-muted/50'
}`}
value={method.value}
className='h-auto min-h-14 w-full flex-col gap-1.5 px-3 py-3 sm:min-h-16'
>
<RadioGroupItem
value={method.value}
id={method.value}
className='sr-only'
/>
<Icon className='h-4 w-4 sm:h-5 sm:w-5' />
<span className='max-w-full truncate text-xs font-medium sm:text-sm'>
{t(method.label)}
</span>
</Label>
</ToggleGroupItem>
)
})}
</RadioGroup>
</ToggleGroup>
</div>
{/* Warning Threshold */}
@@ -178,7 +187,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
</div>
{/* Email Settings */}
{settings.notify_type === 'email' && (
{notifyType === 'email' && (
<div className='space-y-1.5'>
<Label htmlFor='notifyEmail'>{t('Notification Email')}</Label>
<Input
@@ -193,7 +202,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)}
{/* Webhook Settings */}
{settings.notify_type === 'webhook' && (
{notifyType === 'webhook' && (
<>
<div className='space-y-1.5'>
<Label htmlFor='webhookUrl'>{t('Webhook URL')}</Label>
@@ -219,7 +228,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)}
{/* Bark Settings */}
{settings.notify_type === 'bark' && (
{notifyType === 'bark' && (
<div className='space-y-1.5'>
<Label htmlFor='barkUrl'>{t('Bark Push URL')}</Label>
<Input
@@ -237,7 +246,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)}
{/* Gotify Settings */}
{settings.notify_type === 'gotify' && (
{notifyType === 'gotify' && (
<>
<div className='space-y-1.5'>
<Label htmlFor='gotifyUrl'>{t('Gotify Server URL')}</Label>
@@ -300,7 +309,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
href='https://gotify.net/'
target='_blank'
rel='noopener noreferrer'
className='text-primary hover:underline'
className='text-primary underline underline-offset-4'
>
{t('Gotify Documentation')}
</a>