From 9a2c09357a2e728894f672d51b0fc9fdb6e26259 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sat, 25 Apr 2026 13:13:14 -0400 Subject: [PATCH] feat(ui): migrate cliproxy page to design system Config archetype Wrap master-detail layout in PageShell + PageHeader + ConfigLayout. PageHeader carries the brand strip (Zap icon + CLIProxy title + account-management description) and refresh button. The existing sidebar (Quick Setup CTA, sectioned providers list, variants list, ProxyStatusWidget, footer connection summary) becomes ConfigLayout's left rail; ProviderEditor and EmptyProviderState become the form pane. Pure layout migration: every sub-flow still mounts correctly -- QuickSetupWizard dialog, AddAccountDialog (URL action=auth deep-link), AccountSafetyWarningCard for gemini/agy, provider selection persisted to localStorage and URL deep-link, all bulk pause/resume/solo/delete mutations untouched. ProviderEditor (which contains the form + raw JSON internally) remains as a single 'form' slot. --- ui/src/pages/cliproxy.tsx | 442 +++++++++++++++++++------------------- 1 file changed, 224 insertions(+), 218 deletions(-) diff --git a/ui/src/pages/cliproxy.tsx b/ui/src/pages/cliproxy.tsx index 962591e2..6b8bbfa4 100644 --- a/ui/src/pages/cliproxy.tsx +++ b/ui/src/pages/cliproxy.tsx @@ -11,6 +11,8 @@ import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Skeleton } from '@/components/ui/skeleton'; import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react'; +import { PageShell, PageHeader } from '@/components/page-shell'; +import { ConfigLayout } from '@/components/config-layout'; import { QuickSetupWizard } from '@/components/quick-setup-wizard'; import { AddAccountDialog } from '@/components/account/add-account-dialog'; import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card'; @@ -352,234 +354,238 @@ export function CliproxyPage() { setSelectedProvider(null); }; - return ( -
- {/* Left Sidebar */} -
- {/* Header */} -
-
-
- -

{updateCheck?.backendLabel ?? 'CLIProxy'}

-
- + const sidebar = ( +
+ {/* Header inside the rail: Quick Setup CTA */} +
+ +
+ + {/* Providers List */} + +
+
+ {t('cliproxyPage.providers')}
-

- {t('cliproxyPage.accountManagement')} -

- - -
- - {/* Providers List */} - -
-
- {t('cliproxyPage.providers')} + {authLoading ? ( +
+ {[1, 2, 3, 4].map((i) => ( + + ))}
- {authLoading ? ( -
- {[1, 2, 3, 4].map((i) => ( - - ))} -
- ) : ( -
- {providerSections.map((section) => ( -
-
-
- {t(section.labelKey)} -
-

- {t(section.hintKey)} -

-
-
- {section.items.map((status) => ( - handleSelectProvider(status.provider)} - /> - ))} + ) : ( +
+ {providerSections.map((section) => ( +
+
+
+ {t(section.labelKey)}
+

+ {t(section.hintKey)} +

+
+ {section.items.map((status) => ( + handleSelectProvider(status.provider)} + /> + ))} +
+
+ ))} +
+ )} + + {/* Variants Section */} + {variants.length > 0 && ( + <> +
+ + {t('cliproxyPage.variants')} +
+
+ {variants.map((variant) => ( + p.provider === variant.provider)} + isSelected={selectedVariant === variant.name} + onSelect={() => handleSelectVariant(variant.name)} + onDelete={() => deleteMutation.mutate(variant.name)} + isDeleting={deleteMutation.isPending} + /> ))}
- )} - - {/* Variants Section */} - {variants.length > 0 && ( - <> -
- - {t('cliproxyPage.variants')} -
-
- {variants.map((variant) => ( - p.provider === variant.provider)} - isSelected={selectedVariant === variant.name} - onSelect={() => handleSelectVariant(variant.name)} - onDelete={() => deleteMutation.mutate(variant.name)} - isDeleting={deleteMutation.isPending} - /> - ))} -
- - )} -
- - - {/* Proxy Status Widget */} -
- + + )}
+ - {/* Footer Stats */} -
-
- {t('cliproxyPage.providerCount', { count: providers.length })} - - - {t('cliproxyPage.connectedCount', { - count: providers.filter((p) => p.authenticated).length, - })} - -
-
+ {/* Proxy Status Widget */} +
+
- {/* Right Panel */} -
- {selectedVariantData && parentAuthForVariant ? ( - <> - +
+ {t('cliproxyPage.providerCount', { count: providers.length })} + + + {t('cliproxyPage.connectedCount', { + count: providers.filter((p) => p.authenticated).length, + })} + +
+
+
+ ); + + const detail = ( +
+ {selectedVariantData && parentAuthForVariant ? ( + <> + + ) : undefined + } + onAddAccount={() => + setAddAccountProvider({ provider: selectedVariantData.provider, - })} - authStatus={parentAuthForVariant} - catalog={catalogs[selectedVariantData.provider]} - routing={routingHints[selectedVariantData.provider]} - logoProvider={selectedVariantData.provider} - baseProvider={selectedVariantData.provider} - defaultTarget={selectedVariantData.target} - isRemoteMode={isRemoteMode} - port={selectedVariantData.port} - topNotice={ - showAccountSafetyWarning ? ( - - ) : undefined - } - onAddAccount={() => - setAddAccountProvider({ - provider: selectedVariantData.provider, - displayName: parentAuthForVariant.displayName, - isFirstAccount: (parentAuthForVariant.accounts?.length || 0) === 0, - }) - } - onSetDefault={(accountId) => - setDefaultMutation.mutate({ - provider: selectedVariantData.provider, - accountId, - }) - } - onRemoveAccount={(accountId) => - removeMutation.mutate({ - provider: selectedVariantData.provider, - accountId, - }) - } - onPauseToggle={(accountId, paused) => - handlePauseToggle(selectedVariantData.provider, accountId, paused) - } - onSoloMode={(accountId) => handleSoloMode(selectedVariantData.provider, accountId)} - onBulkPause={(accountIds) => - handleBulkPause(selectedVariantData.provider, accountIds) - } - onBulkResume={(accountIds) => - handleBulkResume(selectedVariantData.provider, accountIds) - } - isRemovingAccount={removeMutation.isPending} - isPausingAccount={pauseMutation.isPending || resumeMutation.isPending} - isSoloingAccount={soloMutation.isPending} - isBulkPausing={bulkPauseMutation.isPending} - isBulkResuming={bulkResumeMutation.isPending} - /> - - ) : selectedStatus ? ( - <> - - ) : undefined - } - onAddAccount={() => - setAddAccountProvider({ - provider: selectedStatus.provider, - displayName: selectedStatus.displayName, - isFirstAccount: (selectedStatus.accounts?.length || 0) === 0, - }) - } - onSetDefault={(accountId) => - setDefaultMutation.mutate({ - provider: selectedStatus.provider, - accountId, - }) - } - onRemoveAccount={(accountId) => - removeMutation.mutate({ - provider: selectedStatus.provider, - accountId, - }) - } - onPauseToggle={(accountId, paused) => - handlePauseToggle(selectedStatus.provider, accountId, paused) - } - onSoloMode={(accountId) => handleSoloMode(selectedStatus.provider, accountId)} - onBulkPause={(accountIds) => handleBulkPause(selectedStatus.provider, accountIds)} - onBulkResume={(accountIds) => handleBulkResume(selectedStatus.provider, accountIds)} - isRemovingAccount={removeMutation.isPending} - isPausingAccount={pauseMutation.isPending || resumeMutation.isPending} - isSoloingAccount={soloMutation.isPending} - isBulkPausing={bulkPauseMutation.isPending} - isBulkResuming={bulkResumeMutation.isPending} - /> - - ) : ( - setWizardOpen(true)} /> - )} -
+ displayName: parentAuthForVariant.displayName, + isFirstAccount: (parentAuthForVariant.accounts?.length || 0) === 0, + }) + } + onSetDefault={(accountId) => + setDefaultMutation.mutate({ + provider: selectedVariantData.provider, + accountId, + }) + } + onRemoveAccount={(accountId) => + removeMutation.mutate({ + provider: selectedVariantData.provider, + accountId, + }) + } + onPauseToggle={(accountId, paused) => + handlePauseToggle(selectedVariantData.provider, accountId, paused) + } + onSoloMode={(accountId) => handleSoloMode(selectedVariantData.provider, accountId)} + onBulkPause={(accountIds) => handleBulkPause(selectedVariantData.provider, accountIds)} + onBulkResume={(accountIds) => + handleBulkResume(selectedVariantData.provider, accountIds) + } + isRemovingAccount={removeMutation.isPending} + isPausingAccount={pauseMutation.isPending || resumeMutation.isPending} + isSoloingAccount={soloMutation.isPending} + isBulkPausing={bulkPauseMutation.isPending} + isBulkResuming={bulkResumeMutation.isPending} + /> + + ) : selectedStatus ? ( + <> + + ) : undefined + } + onAddAccount={() => + setAddAccountProvider({ + provider: selectedStatus.provider, + displayName: selectedStatus.displayName, + isFirstAccount: (selectedStatus.accounts?.length || 0) === 0, + }) + } + onSetDefault={(accountId) => + setDefaultMutation.mutate({ + provider: selectedStatus.provider, + accountId, + }) + } + onRemoveAccount={(accountId) => + removeMutation.mutate({ + provider: selectedStatus.provider, + accountId, + }) + } + onPauseToggle={(accountId, paused) => + handlePauseToggle(selectedStatus.provider, accountId, paused) + } + onSoloMode={(accountId) => handleSoloMode(selectedStatus.provider, accountId)} + onBulkPause={(accountIds) => handleBulkPause(selectedStatus.provider, accountIds)} + onBulkResume={(accountIds) => handleBulkResume(selectedStatus.provider, accountIds)} + isRemovingAccount={removeMutation.isPending} + isPausingAccount={pauseMutation.isPending || resumeMutation.isPending} + isSoloingAccount={soloMutation.isPending} + isBulkPausing={bulkPauseMutation.isPending} + isBulkResuming={bulkResumeMutation.isPending} + /> + + ) : ( + setWizardOpen(true)} /> + )} +
+ ); + + return ( + + + + {updateCheck?.backendLabel ?? 'CLIProxy'} + + } + description={t('cliproxyPage.accountManagement')} + actions={ + + } + /> + {/* Dialogs */} setWizardOpen(false)} /> @@ -595,6 +601,6 @@ export function CliproxyPage() { } isFirstAccount={addAccountProvider?.isFirstAccount || false} /> -
+ ); }