diff --git a/web/default/src/components/dialog.tsx b/web/default/src/components/dialog.tsx index 1be98dc740..0e93b17bf6 100644 --- a/web/default/src/components/dialog.tsx +++ b/web/default/src/components/dialog.tsx @@ -54,7 +54,7 @@ export function Dialog({ children, trigger, footer, - contentHeight = 'min(58vh, 520px)', + contentHeight = 'auto', contentClassName, headerClassName, titleClassName, diff --git a/web/default/src/components/layout/components/public-header.tsx b/web/default/src/components/layout/components/public-header.tsx index 5fad63703d..7030a7c540 100644 --- a/web/default/src/components/layout/components/public-header.tsx +++ b/web/default/src/components/layout/components/public-header.tsx @@ -25,15 +25,8 @@ import { useNotifications } from '@/hooks/use-notifications' import { useSystemConfig } from '@/hooks/use-system-config' import { useTopNavLinks } from '@/hooks/use-top-nav-links' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Skeleton } from '@/components/ui/skeleton' +import { Dialog } from '@/components/dialog' import { LanguageSwitcher } from '@/components/language-switcher' import { NotificationPopover } from '@/components/notification-popover' import { ProfileDropdown } from '@/components/profile-dropdown' @@ -427,28 +420,26 @@ export function PublicHeader(props: PublicHeaderProps) { closeAuthPrompt() } }} - > - - - {t('Sign in required')} - - {t('Please sign in to view {{module}}.', { - module: authPromptTarget?.title || '', - })} - - -
- {t('Redirecting to sign in in {{seconds}} seconds.', { - seconds: authPromptSecondsLeft, - })} -
- + title={t('Sign in required')} + description={t('Please sign in to view {{module}}.', { + module: authPromptTarget?.title || '', + })} + contentClassName='sm:max-w-md' + contentHeight='auto' + footer={ + <> - -
+ + } + > +
+ {t('Redirecting to sign in in {{seconds}} seconds.', { + seconds: authPromptSecondsLeft, + })} +
) diff --git a/web/default/src/features/auth/secure-verification/components/secure-verification-dialog.tsx b/web/default/src/features/auth/secure-verification/components/secure-verification-dialog.tsx index 88dda89fde..02e8c16d6e 100644 --- a/web/default/src/features/auth/secure-verification/components/secure-verification-dialog.tsx +++ b/web/default/src/features/auth/secure-verification/components/secure-verification-dialog.tsx @@ -20,16 +20,9 @@ import { useMemo } from 'react' import { ShieldCheck, KeyRound, Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Dialog } from '@/components/dialog' import type { SecureVerificationState, VerificationMethod, @@ -91,122 +84,118 @@ export function SecureVerificationDialog({ (activeMethod === '2fa' && (!state.code.trim() || state.code.length < 6)) return ( - - -
- - - - {title} - - - {description} - - + + + {title} + + } + description={description} + contentClassName='top-[8vh] max-w-[calc(100%-1.5rem)] translate-y-0 overflow-hidden border-none shadow-xl sm:top-1/2 sm:max-w-md sm:translate-y-[-50%] sm:rounded-xl' + headerClassName='border-b pb-4 text-left' + titleClassName='flex items-center gap-2 text-lg font-semibold' + descriptionClassName='text-left' + contentHeight='auto' + bodyClassName='px-1 py-1' + showCloseButton={!state.loading} + footerClassName='bg-muted/30 border-t px-6 py-4 sm:flex-row sm:justify-end' + footer={ + <> + + + + } + > + {availableTabs.length === 0 ? ( +
+
+ +
+

+ {t( + 'Enable Two-factor Authentication or Passkey in your profile to unlock sensitive operations.' + )} +

+
+ ) : ( + onMethodChange(value as VerificationMethod)} + className='gap-4' + > + + {methods.has2FA && ( + {t('Authenticator code')} + )} + {methods.hasPasskey && methods.passkeySupported && ( + {t('Passkey')} + )} + -
- {availableTabs.length === 0 ? ( -
-
- -
-

- {t( - 'Enable Two-factor Authentication or Passkey in your profile to unlock sensitive operations.' - )} -

-
- ) : ( - - onMethodChange(value as VerificationMethod) + +

+ {t( + 'Enter the 6-digit Time-based One-Time Password or 8-character backup code from your authenticator app.' + )} +

+ onCodeChange(event.target.value)} + placeholder={t('Enter verification code')} + disabled={state.loading} + autoFocus={activeMethod === '2fa'} + onKeyDown={(event) => { + if (event.key === 'Enter' && !verifyDisabled) { + event.preventDefault() + handleVerify() } - className='gap-4' - > - - {methods.has2FA && ( - - {t('Authenticator code')} - - )} - {methods.hasPasskey && methods.passkeySupported && ( - {t('Passkey')} - )} - + }} + /> +
- -

+ +

+
+ +
+

+ {t('Use your Passkey')} +

+

{t( - 'Enter the 6-digit Time-based One-Time Password or 8-character backup code from your authenticator app.' + 'We will prompt your device to confirm using biometrics or your hardware key.' )}

- onCodeChange(event.target.value)} - placeholder={t('Enter verification code')} - disabled={state.loading} - autoFocus={activeMethod === '2fa'} - onKeyDown={(event) => { - if (event.key === 'Enter' && !verifyDisabled) { - event.preventDefault() - handleVerify() - } - }} - /> - - - -
-
- -
-

- {t('Use your Passkey')} -

-

- {t( - 'We will prompt your device to confirm using biometrics or your hardware key.' - )} -

-
-
-
- {!methods.passkeySupported && ( -

- {t('This device does not support Passkey verification.')} -

- )} -
- +
+
+
+ {!methods.passkeySupported && ( +

+ {t('This device does not support Passkey verification.')} +

)} -
- - - - - -
-
+ + + )}
) } diff --git a/web/default/src/features/auth/sign-in/components/user-auth-form.tsx b/web/default/src/features/auth/sign-in/components/user-auth-form.tsx index 46591cccf3..1830517c22 100644 --- a/web/default/src/features/auth/sign-in/components/user-auth-form.tsx +++ b/web/default/src/features/auth/sign-in/components/user-auth-form.tsx @@ -32,14 +32,6 @@ import { import { cn } from '@/lib/utils' import { useStatus } from '@/hooks/use-status' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Form, FormControl, @@ -50,6 +42,7 @@ import { } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { Dialog } from '@/components/dialog' import { PasswordInput } from '@/components/password-input' import { Turnstile } from '@/components/turnstile' import { login, wechatLoginByCode } from '@/features/auth/api' @@ -414,43 +407,16 @@ export function UserAuthForm({ - - - {t('WeChat sign in')} - - {t( - 'Scan the QR code to follow the official account and reply with “验证码” to receive your verification code.' - )} - - - - {wechatQrCodeUrl ? ( -
- {t('WeChat -
- ) : ( -

- {t('QR code is not configured. Please contact support.')} -

- )} - -
- - setWeChatCode(event.target.value)} - autoComplete='one-time-code' - /> -
- - + title={t('WeChat sign in')} + description={t( + 'Scan the QR code to follow the official account and reply with “验证码” to receive your verification code.' + )} + contentClassName='max-w-sm' + headerClassName='text-left' + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <>
)} diff --git a/web/default/src/features/auth/sign-up/components/sign-up-form.tsx b/web/default/src/features/auth/sign-up/components/sign-up-form.tsx index 9466c15ec4..94e262a109 100644 --- a/web/default/src/features/auth/sign-up/components/sign-up-form.tsx +++ b/web/default/src/features/auth/sign-up/components/sign-up-form.tsx @@ -26,14 +26,6 @@ import { toast } from 'sonner' import { cn } from '@/lib/utils' import { useStatus } from '@/hooks/use-status' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Form, FormControl, @@ -44,6 +36,7 @@ import { } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { Dialog } from '@/components/dialog' import { PasswordInput } from '@/components/password-input' import { Turnstile } from '@/components/turnstile' import { register, wechatLoginByCode } from '@/features/auth/api' @@ -387,43 +380,16 @@ export function SignUpForm({ - - - {t('WeChat sign in')} - - {t( - 'Scan the QR code to follow the official account and reply with “验证码” to receive your verification code.' - )} - - - - {wechatQrCodeUrl ? ( -
- {t('WeChat -
- ) : ( -

- {t('QR code is not configured. Please contact support.')} -

- )} - -
- - setWeChatCode(event.target.value)} - autoComplete='one-time-code' - /> -
- - + title={t('WeChat sign in')} + description={t( + 'Scan the QR code to follow the official account and reply with “验证码” to receive your verification code.' + )} + contentClassName='max-w-sm' + headerClassName='text-left' + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <>
)} diff --git a/web/default/src/features/channels/components/data-table-bulk-actions.tsx b/web/default/src/features/channels/components/data-table-bulk-actions.tsx index c5d5bd7ff5..31a30b28e6 100644 --- a/web/default/src/features/channels/components/data-table-bulk-actions.tsx +++ b/web/default/src/features/channels/components/data-table-bulk-actions.tsx @@ -22,14 +22,6 @@ import { type Table } from '@tanstack/react-table' import { Power, PowerOff, Tag, Trash2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { @@ -38,6 +30,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip' import { DataTableBulkActions as BulkActionsToolbar } from '@/components/data-table' +import { Dialog } from '@/components/dialog' import { handleBatchDelete, handleBatchDisable, @@ -188,29 +181,21 @@ export function DataTableBulkActions({ {/* Set Tag Dialog */} - - - - {t('Set Tag')} - - {t('Set a tag for')} {selectedIds.length}{' '} - {t('selected channel(s). Leave empty to remove tag.')} - - - -
-
- - setTagValue(e.target.value)} - /> -
-
- - + + {t('Set a tag for')} + {selectedIds.length}{' '} + {t('selected channel(s). Leave empty to remove tag.')} + + } + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <> - -
+ + } + > +
+
+ + setTagValue(e.target.value)} + /> +
+
{/* Delete Confirmation Dialog */} - - - - {t('Delete Channels?')} - - {t('Are you sure you want to delete')} {selectedIds.length}{' '} - {t('channel(s)? This action cannot be undone.')} - - - - + + {t('Are you sure you want to delete')} + {selectedIds.length}{' '} + {t('channel(s)? This action cannot be undone.')} + + } + contentHeight='auto' + footer={ + <> - - + + } + > + {' '} ) diff --git a/web/default/src/features/channels/components/dialogs/balance-query-dialog.tsx b/web/default/src/features/channels/components/dialogs/balance-query-dialog.tsx index f9e39d84d6..32b91f094b 100644 --- a/web/default/src/features/channels/components/dialogs/balance-query-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/balance-query-dialog.tsx @@ -24,14 +24,7 @@ import { toast } from 'sonner' import { formatCurrencyFromUSD } from '@/lib/currency' import { formatTimestampToDate } from '@/lib/format' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' +import { Dialog } from '@/components/dialog' import { getCodexUsage, updateChannelBalance } from '../../api' import { channelsQueryKeys } from '../../lib' import { useChannels } from '../channels-provider' @@ -161,53 +154,55 @@ export function BalanceQueryDialog({ } return ( - - - - {t('Query Balance')} - - {t('Update balance for:')} {currentRow.name} - - - -
- {/* Current Balance Display */} -
-
- - {t('Current Balance')} -
-
- {balance !== null - ? formatBalance(balance) - : formatBalance(currentRow.balance)} -
-
- {t('Last updated:')}{' '} - {formatDate( - balanceUpdatedTime ?? currentRow.balance_updated_time - )} -
-
- - {/* Balance Update Button */} - -
- - + + {t('Update balance for:')} + {currentRow.name} + + } + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <> - -
+ + } + > +
+ {/* Current Balance Display */} +
+
+ + {t('Current Balance')} +
+
+ {balance !== null + ? formatBalance(balance) + : formatBalance(currentRow.balance)} +
+
+ {t('Last updated:')}{' '} + {formatDate(balanceUpdatedTime ?? currentRow.balance_updated_time)} +
+
+ + {/* Balance Update Button */} + +
) } diff --git a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx index d511128472..ae18648fe9 100644 --- a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx @@ -33,14 +33,6 @@ import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' import { useIsMobile } from '@/hooks/use-mobile' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { @@ -75,6 +67,7 @@ import { } from '@/components/ui/tooltip' import { DataTableBulkActions as BulkActionsToolbar } from '@/components/data-table' import { DataTablePagination } from '@/components/data-table/pagination' +import { Dialog } from '@/components/dialog' import { sideDrawerContentClassName, sideDrawerFooterClassName, @@ -529,179 +522,184 @@ export function ChannelTestDialog({ return ( <> - - - - {t('Test Channel Connection')} - - {t('Test connectivity for:')} {currentRow.name} - - - -
-
-
- - -

- {t( - 'Override the endpoint used for testing. Leave empty to auto detect.' - )} -

-
-
- -
- - - {isStreamTest ? t('Enabled') : t('Disabled')} - -
-

- {t('Enable streaming mode for the test request.')} -

-
-
- -
-
-
-

{t('Channel models')}

-

- {t('Select models to run batch tests.')} -

-
- setSearchTerm(e.target.value)} - className='sm:w-64' - /> -
- -
-
-
- - - - - - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - - {models.length - ? 'No models matched your search.' - : 'This channel has no configured models.'} - - - )} - -
-
-
- - -
- - -
-
- - + + {t('Test connectivity for:')} + {currentRow.name} + + } + contentClassName='max-h-[90vh] overflow-hidden sm:max-w-3xl' + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <> - -
+ + } + > +
+
+
+ + +

+ {t( + 'Override the endpoint used for testing. Leave empty to auto detect.' + )} +

+
+
+ +
+ + + {isStreamTest ? t('Enabled') : t('Disabled')} + +
+

+ {t('Enable streaming mode for the test request.')} +

+
+
+ +
+
+
+

{t('Channel models')}

+

+ {t('Select models to run batch tests.')} +

+
+ setSearchTerm(e.target.value)} + className='sm:w-64' + /> +
+ +
+
+
+ + + + + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + {models.length + ? 'No models matched your search.' + : 'This channel has no configured models.'} + + + )} + +
+
+
+ + +
+ + +
+
- - - {t('Codex Authorization')} - - {t( - 'Generate a Codex OAuth credential and paste it into the channel key field.' - )} - - - -
- - - {t( - '1) Click "Open authorization page" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click "Generate credential".' - )} - - - -
- - - -
- -
-
{t('Callback URL')}
- - setState((prev) => ({ ...prev, callbackUrl: e.target.value })) - } - placeholder={t( - 'Paste the full callback URL (includes code & state)' - )} - autoComplete='off' - spellCheck={false} - /> -
- {t( - 'Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.' - )} -
-
-
- - + + + + + +
+
{t('Callback URL')}
+ + setState((prev) => ({ ...prev, callbackUrl: e.target.value })) + } + placeholder={t( + 'Paste the full callback URL (includes code & state)' + )} + autoComplete='off' + spellCheck={false} + /> +
+ {t( + 'Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.' + )} +
+
+
) } diff --git a/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx b/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx index 442db3d77d..17440b5588 100644 --- a/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx @@ -31,16 +31,9 @@ import { useTranslation } from 'react-i18next' import dayjs from '@/lib/dayjs' import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Progress } from '@/components/ui/progress' import { ScrollArea } from '@/components/ui/scroll-area' +import { Dialog } from '@/components/dialog' import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge' type CodexRateLimitWindow = { @@ -414,177 +407,23 @@ export function CodexUsageDialog({ }, [response]) return ( - - - - - {t('Codex Account & Usage')} - - - {t('Channel:')} {channelName || '-'}{' '} - {channelId ? `(#${channelId})` : ''} - - - -
- {errorMessage && ( -
- {errorMessage} -
- )} - - {/* Account summary */} -
-
-
- - {statusBadge} - {typeof response?.upstream_status === 'number' && ( - - )} -
- {onRefresh && ( - - )} -
- - {/* Account identity info */} -
- } - label='User ID' - value={payload?.user_id} - mono - /> - } - label={t('Email')} - value={payload?.email} - /> - } - label='Account ID' - value={payload?.account_id} - mono - /> -
-
- - {/* Rate limit windows */} -
-
-
- {t('Rate Limit Windows')} -
-

- {t( - 'Tracks current account base limits and additional metered usage on Codex upstream.' - )} -

- -
- - {additionalRateLimits.length > 0 && ( -
-
-
- {t('Additional Limits')} -
-

- {t( - 'Per-feature metered windows split by model or capability.' - )} -

-
-
- {additionalRateLimits.map((item, index) => { - const limitName = - item.limit_name || - item.metered_feature || - `${t('Additional Limit')} ${index + 1}` - return ( -
0 ? 'border-t pt-4' : ''} - > - -
- ) - })} -
-
- )} -
- - {/* Raw JSON collapsible */} -
- - {showRawJson && ( - <> -
- -
- -
-                    {rawJsonText || '-'}
-                  
-
- - )} -
-
- - + + {t('Channel:')} + {channelName || '-'}{' '} + {channelId ? `(#${channelId})` : ''} + + } + contentClassName='sm:max-w-3xl' + titleClassName='flex items-center gap-2' + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <> - -
+ + } + > +
+ {errorMessage && ( +
+ {errorMessage} +
+ )} + + {/* Account summary */} +
+
+
+ + {statusBadge} + {typeof response?.upstream_status === 'number' && ( + + )} +
+ {onRefresh && ( + + )} +
+ + {/* Account identity info */} +
+ } + label='User ID' + value={payload?.user_id} + mono + /> + } + label={t('Email')} + value={payload?.email} + /> + } + label='Account ID' + value={payload?.account_id} + mono + /> +
+
+ + {/* Rate limit windows */} +
+
+
+ {t('Rate Limit Windows')} +
+

+ {t( + 'Tracks current account base limits and additional metered usage on Codex upstream.' + )} +

+ +
+ + {additionalRateLimits.length > 0 && ( +
+
+
+ {t('Additional Limits')} +
+

+ {t( + 'Per-feature metered windows split by model or capability.' + )} +

+
+
+ {additionalRateLimits.map((item, index) => { + const limitName = + item.limit_name || + item.metered_feature || + `${t('Additional Limit')} ${index + 1}` + return ( +
0 ? 'border-t pt-4' : ''} + > + +
+ ) + })} +
+
+ )} +
+ + {/* Raw JSON collapsible */} +
+ + {showRawJson && ( + <> +
+ +
+ +
+                  {rawJsonText || '-'}
+                
+
+ + )} +
+
) } diff --git a/web/default/src/features/channels/components/dialogs/copy-channel-dialog.tsx b/web/default/src/features/channels/components/dialogs/copy-channel-dialog.tsx index 36a1a8e38e..9edaaaebe3 100644 --- a/web/default/src/features/channels/components/dialogs/copy-channel-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/copy-channel-dialog.tsx @@ -22,16 +22,9 @@ import { Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { Dialog } from '@/components/dialog' import { handleCopyChannel } from '../../lib' import { useChannels } from '../channels-provider' @@ -74,45 +67,20 @@ export function CopyChannelDialog({ } return ( - - - - {t('Copy Channel')} - - {t('Create a copy of:')} {currentRow.name} - - - -
-
- - setSuffix(e.target.value)} - disabled={isCopying} - /> -

- {t('New name will be:')} {currentRow.name} - {suffix} -

-
- -
- setResetBalance(!!checked)} - disabled={isCopying} - /> - -
-
- - + + {t('Create a copy of:')} + {currentRow.name} + + } + contentHeight='auto' + bodyClassName='space-y-4' + footer={ + <> - -
+ + } + > +
+
+ + setSuffix(e.target.value)} + disabled={isCopying} + /> +

+ {t('New name will be:')} {currentRow.name} + {suffix} +

+
+ +
+ setResetBalance(!!checked)} + disabled={isCopying} + /> + +
+
) } diff --git a/web/default/src/features/channels/components/dialogs/edit-tag-dialog.tsx b/web/default/src/features/channels/components/dialogs/edit-tag-dialog.tsx index 30fa660f96..9967fa98c8 100644 --- a/web/default/src/features/channels/components/dialogs/edit-tag-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/edit-tag-dialog.tsx @@ -22,14 +22,6 @@ import { Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { ScrollArea } from '@/components/ui/scroll-area' @@ -43,6 +35,7 @@ import { } from '@/components/ui/select' import { Separator } from '@/components/ui/separator' import { Textarea } from '@/components/ui/textarea' +import { Dialog } from '@/components/dialog' import { GroupBadge } from '@/components/group-badge' import { StatusBadge } from '@/components/status-badge' import { @@ -222,216 +215,23 @@ export function EditTagDialog({ open, onOpenChange }: EditTagDialogProps) { if (!currentTag) return null return ( - - - - - {t('Edit Tag:')} {currentTag} - - - {t( - 'Batch edit all channels with this tag. Leave fields empty to keep current values.' - )} - - - - -
- {/* Tag Name */} -
- - setNewTag(e.target.value)} - placeholder={t('Enter new tag name or leave empty')} - /> -
- - - - {/* Models */} -
- - - {isLoadingTagModels ? ( -
- - - {t('Loading current models...')} - -
- ) : ( - <> -
- {selectedModels.length > 0 ? ( - selectedModels.map((model) => ( - handleRemoveModel(model)} - > - {model} × - - )) - ) : ( - - {t('No models selected')} - - )} -
- -
- - items={[ - ...availableModels.map((model) => ({ - value: model, - label: model, - })), - ]} - onValueChange={(value) => { - if (value === null) return - if (!selectedModels.includes(value)) { - setSelectedModels([...selectedModels, value]) - } - }} - > - - - - - - - {availableModels.map((model) => ( - - {model} - - ))} - - - - -
- -
- setCustomModel(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault() - handleAddCustomModel() - } - }} - /> - -
- - )} -
- - - - {/* Model Mapping */} -
- -