feat(ui): improve mobile responsive layouts
This commit is contained in:
+11
-11
@@ -245,14 +245,14 @@ export function AccountBindingsTab({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='grid grid-cols-1 gap-3 sm:grid-cols-2'>
|
||||
<div className='grid grid-cols-1 gap-2.5 sm:grid-cols-2 sm:gap-3'>
|
||||
{bindings.map((binding) => (
|
||||
<div
|
||||
key={binding.id}
|
||||
className='flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between'
|
||||
className='flex items-center justify-between gap-2.5 rounded-lg border p-2.5 sm:gap-3 sm:p-3'
|
||||
>
|
||||
<div className='flex min-w-0 items-center gap-3'>
|
||||
<div className='bg-muted shrink-0 rounded-md p-2'>
|
||||
<div className='flex min-w-0 items-center gap-2.5 sm:gap-3'>
|
||||
<div className='bg-muted shrink-0 rounded-md p-1.5 sm:p-2'>
|
||||
<binding.icon className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
@@ -274,7 +274,7 @@ export function AccountBindingsTab({
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 shrink-0 self-start px-2.5 text-xs sm:self-auto'
|
||||
className='h-7 shrink-0 px-2.5 text-xs'
|
||||
onClick={binding.onBind}
|
||||
disabled={binding.isBound && binding.id !== 'email'}
|
||||
>
|
||||
@@ -295,7 +295,7 @@ export function AccountBindingsTab({
|
||||
<p className='text-muted-foreground mb-3 text-sm font-medium'>
|
||||
{t('Custom OAuth')}
|
||||
</p>
|
||||
<div className='grid grid-cols-1 gap-3 sm:grid-cols-2'>
|
||||
<div className='grid grid-cols-1 gap-2.5 sm:grid-cols-2 sm:gap-3'>
|
||||
{customProviders.map((provider) => {
|
||||
const binding = customBindings.find(
|
||||
(b) => b.provider_id === provider.id
|
||||
@@ -304,10 +304,10 @@ export function AccountBindingsTab({
|
||||
return (
|
||||
<div
|
||||
key={provider.id}
|
||||
className='flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between'
|
||||
className='flex items-center justify-between gap-2.5 rounded-lg border p-2.5 sm:gap-3 sm:p-3'
|
||||
>
|
||||
<div className='flex min-w-0 items-center gap-3'>
|
||||
<div className='bg-muted shrink-0 rounded-md p-2'>
|
||||
<div className='flex min-w-0 items-center gap-2.5 sm:gap-3'>
|
||||
<div className='bg-muted shrink-0 rounded-md p-1.5 sm:p-2'>
|
||||
<Link2 className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
@@ -332,7 +332,7 @@ export function AccountBindingsTab({
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='text-destructive hover:text-destructive h-7 shrink-0 self-start px-2.5 text-xs sm:self-auto'
|
||||
className='text-destructive hover:text-destructive h-7 shrink-0 px-2.5 text-xs'
|
||||
onClick={() => setUnbindTarget(binding)}
|
||||
>
|
||||
<Unlink className='mr-1 h-3 w-3' />
|
||||
@@ -342,7 +342,7 @@ export function AccountBindingsTab({
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 shrink-0 self-start px-2.5 text-xs sm:self-auto'
|
||||
className='h-7 shrink-0 px-2.5 text-xs'
|
||||
onClick={() => handleBindCustomOAuth(provider)}
|
||||
>
|
||||
{t('Bind')}
|
||||
|
||||
@@ -102,16 +102,16 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div className='space-y-4 sm:space-y-6'>
|
||||
{/* Notification Type */}
|
||||
<div className='space-y-3'>
|
||||
<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-2 gap-3 sm:grid-cols-4'
|
||||
className='grid grid-cols-4 gap-1.5 sm:gap-3'
|
||||
>
|
||||
{NOTIFICATION_METHODS.map((method) => {
|
||||
const Icon = NOTIFICATION_ICONS[method.value]
|
||||
@@ -120,7 +120,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
<Label
|
||||
key={method.value}
|
||||
htmlFor={method.value}
|
||||
className={`flex cursor-pointer flex-col items-center gap-2 rounded-lg border-2 p-3 transition-colors ${
|
||||
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'
|
||||
@@ -131,8 +131,10 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
id={method.value}
|
||||
className='sr-only'
|
||||
/>
|
||||
<Icon className='h-5 w-5' />
|
||||
<span className='text-sm font-medium'>{t(method.label)}</span>
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
@@ -140,11 +142,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Warning Threshold */}
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='threshold'>{t('Quota Warning Threshold')}</Label>
|
||||
<Input
|
||||
id='threshold'
|
||||
type='number'
|
||||
className='h-9'
|
||||
value={settings.quota_warning_threshold}
|
||||
onChange={(e) =>
|
||||
updateField('quota_warning_threshold', Number(e.target.value))
|
||||
@@ -158,11 +161,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
|
||||
{/* Email Settings */}
|
||||
{settings.notify_type === 'email' && (
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='notifyEmail'>{t('Notification Email')}</Label>
|
||||
<Input
|
||||
id='notifyEmail'
|
||||
type='email'
|
||||
className='h-9'
|
||||
value={settings.notification_email}
|
||||
onChange={(e) => updateField('notification_email', e.target.value)}
|
||||
placeholder={t('Leave empty to use account email')}
|
||||
@@ -173,17 +177,18 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
{/* Webhook Settings */}
|
||||
{settings.notify_type === 'webhook' && (
|
||||
<>
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='webhookUrl'>{t('Webhook URL')}</Label>
|
||||
<Input
|
||||
id='webhookUrl'
|
||||
type='url'
|
||||
className='h-9'
|
||||
value={settings.webhook_url}
|
||||
onChange={(e) => updateField('webhook_url', e.target.value)}
|
||||
placeholder={t('https://example.com/webhook')}
|
||||
/>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='webhookSecret'>{t('Webhook Secret')}</Label>
|
||||
<PasswordInput
|
||||
id='webhookSecret'
|
||||
@@ -197,11 +202,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
|
||||
{/* Bark Settings */}
|
||||
{settings.notify_type === 'bark' && (
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='barkUrl'>{t('Bark Push URL')}</Label>
|
||||
<Input
|
||||
id='barkUrl'
|
||||
type='url'
|
||||
className='h-9'
|
||||
value={settings.bark_url}
|
||||
onChange={(e) => updateField('bark_url', e.target.value)}
|
||||
placeholder={t('https://api.day.app/yourkey/{{title}}/{{content}}')}
|
||||
@@ -215,11 +221,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
{/* Gotify Settings */}
|
||||
{settings.notify_type === 'gotify' && (
|
||||
<>
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='gotifyUrl'>{t('Gotify Server URL')}</Label>
|
||||
<Input
|
||||
id='gotifyUrl'
|
||||
type='url'
|
||||
className='h-9'
|
||||
value={settings.gotify_url}
|
||||
onChange={(e) => updateField('gotify_url', e.target.value)}
|
||||
placeholder={t('https://gotify.example.com')}
|
||||
@@ -228,7 +235,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
{t('Enter the full URL of your Gotify server')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='gotifyToken'>{t('Gotify Application Token')}</Label>
|
||||
<PasswordInput
|
||||
id='gotifyToken'
|
||||
@@ -240,11 +247,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
{t('Token obtained from your Gotify application')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label htmlFor='gotifyPriority'>{t('Message Priority')}</Label>
|
||||
<Input
|
||||
id='gotifyPriority'
|
||||
type='number'
|
||||
className='h-9'
|
||||
min='0'
|
||||
max='10'
|
||||
value={settings.gotify_priority}
|
||||
@@ -259,8 +267,8 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className='bg-muted/50 rounded-lg border p-4'>
|
||||
<h5 className='mb-2 text-sm font-medium'>
|
||||
<div className='bg-muted/50 rounded-lg border p-3 sm:p-4'>
|
||||
<h5 className='mb-1.5 text-sm font-medium sm:mb-2'>
|
||||
{t('Setup Instructions')}
|
||||
</h5>
|
||||
<ol className='text-muted-foreground space-y-1 text-xs'>
|
||||
@@ -287,7 +295,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
<div className='border-t' />
|
||||
|
||||
{/* Preferences Section */}
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Preferences')}</h4>
|
||||
<p className='text-muted-foreground mt-1 text-xs'>
|
||||
@@ -297,12 +305,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
|
||||
{/* Receive Upstream Model Update Notifications (admin only) */}
|
||||
{isAdmin && (
|
||||
<div className='flex flex-col gap-3 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between'>
|
||||
<div className='flex items-start justify-between gap-3 rounded-lg border p-3 sm:items-center sm:p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<Label htmlFor='upstreamModelUpdateNotify'>
|
||||
{t('Receive Upstream Model Update Notifications')}
|
||||
</Label>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
<p className='text-muted-foreground line-clamp-3 text-xs sm:line-clamp-none sm:text-sm'>
|
||||
{t(
|
||||
'Only available for admins. When enabled, you will receive a summary notification via your selected method when the scheduled model check detects upstream model changes or check failures.'
|
||||
)}
|
||||
@@ -320,12 +328,12 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
)}
|
||||
|
||||
{/* Accept Unset Model Price */}
|
||||
<div className='flex flex-col gap-3 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between'>
|
||||
<div className='flex items-start justify-between gap-3 rounded-lg border p-3 sm:items-center sm:p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<Label htmlFor='acceptUnsetPrice'>
|
||||
{t('Accept Unpriced Models')}
|
||||
</Label>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
<p className='text-muted-foreground text-xs sm:text-sm'>
|
||||
{t('Allow using models without price configuration')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -340,10 +348,10 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Record IP Log */}
|
||||
<div className='flex flex-col gap-3 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between'>
|
||||
<div className='flex items-start justify-between gap-3 rounded-lg border p-3 sm:items-center sm:p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<Label htmlFor='recordIp'>{t('Record IP Address')}</Label>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
<p className='text-muted-foreground text-xs sm:text-sm'>
|
||||
{t('Log IP address for usage and error logs')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user