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.
This commit is contained in:
Tam Nhu Tran committed 2026-04-25 13:13:14 -04:00
1 parent 5e3e554693
commit 9a2c09357a
1 file changed
+39 -33
+39 -33
View File
@@ -11,6 +11,8 @@ import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react'; 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 { QuickSetupWizard } from '@/components/quick-setup-wizard';
import { AddAccountDialog } from '@/components/account/add-account-dialog'; import { AddAccountDialog } from '@/components/account/add-account-dialog';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card'; import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
@@ -352,31 +354,10 @@ export function CliproxyPage() {
setSelectedProvider(null); setSelectedProvider(null);
}; };
return ( const sidebar = (
<div className="flex h-full min-h-0 overflow-hidden"> <div className="flex h-full flex-col bg-muted/30">
{/* Left Sidebar */} {/* Header inside the rail: Quick Setup CTA */}
<div className="w-80 border-r flex flex-col bg-muted/30"> <div className="border-b bg-background p-3">
{/* Header */}
<div className="p-4 border-b bg-background">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<Zap className="w-5 h-5 text-primary" />
<h1 className="font-semibold">{updateCheck?.backendLabel ?? 'CLIProxy'}</h1>
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={handleRefresh}
disabled={isFetching}
>
<RefreshCw className={cn('w-4 h-4', isFetching && 'animate-spin')} />
</Button>
</div>
<p className="text-xs text-muted-foreground mb-3">
{t('cliproxyPage.accountManagement')}
</p>
<Button <Button
variant="default" variant="default"
size="sm" size="sm"
@@ -453,12 +434,12 @@ export function CliproxyPage() {
</ScrollArea> </ScrollArea>
{/* Proxy Status Widget */} {/* Proxy Status Widget */}
<div className="p-3 border-t"> <div className="border-t p-3">
<ProxyStatusWidget /> <ProxyStatusWidget />
</div> </div>
{/* Footer Stats */} {/* Footer Stats */}
<div className="p-3 border-t bg-background text-xs text-muted-foreground"> <div className="border-t bg-background p-3 text-xs text-muted-foreground">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span>{t('cliproxyPage.providerCount', { count: providers.length })}</span> <span>{t('cliproxyPage.providerCount', { count: providers.length })}</span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@@ -470,9 +451,10 @@ export function CliproxyPage() {
</div> </div>
</div> </div>
</div> </div>
);
{/* Right Panel */} const detail = (
<div className="flex-1 flex min-w-0 flex-col overflow-hidden bg-background"> <div className="flex h-full min-w-0 flex-col overflow-hidden bg-background">
{selectedVariantData && parentAuthForVariant ? ( {selectedVariantData && parentAuthForVariant ? (
<> <>
<ProviderEditor <ProviderEditor
@@ -517,9 +499,7 @@ export function CliproxyPage() {
handlePauseToggle(selectedVariantData.provider, accountId, paused) handlePauseToggle(selectedVariantData.provider, accountId, paused)
} }
onSoloMode={(accountId) => handleSoloMode(selectedVariantData.provider, accountId)} onSoloMode={(accountId) => handleSoloMode(selectedVariantData.provider, accountId)}
onBulkPause={(accountIds) => onBulkPause={(accountIds) => handleBulkPause(selectedVariantData.provider, accountIds)}
handleBulkPause(selectedVariantData.provider, accountIds)
}
onBulkResume={(accountIds) => onBulkResume={(accountIds) =>
handleBulkResume(selectedVariantData.provider, accountIds) handleBulkResume(selectedVariantData.provider, accountIds)
} }
@@ -580,6 +560,32 @@ export function CliproxyPage() {
<EmptyProviderState onSetup={() => setWizardOpen(true)} /> <EmptyProviderState onSetup={() => setWizardOpen(true)} />
)} )}
</div> </div>
);
return (
<PageShell>
<PageHeader
title={
<span className="flex items-center gap-2">
<Zap className="w-5 h-5 text-primary" />
{updateCheck?.backendLabel ?? 'CLIProxy'}
</span>
}
description={t('cliproxyPage.accountManagement')}
actions={
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={handleRefresh}
disabled={isFetching}
aria-label="Refresh"
>
<RefreshCw className={cn('w-4 h-4', isFetching && 'animate-spin')} />
</Button>
}
/>
<ConfigLayout left={sidebar} form={detail} />
{/* Dialogs */} {/* Dialogs */}
<QuickSetupWizard open={wizardOpen} onClose={() => setWizardOpen(false)} /> <QuickSetupWizard open={wizardOpen} onClose={() => setWizardOpen(false)} />
@@ -595,6 +601,6 @@ export function CliproxyPage() {
} }
isFirstAccount={addAccountProvider?.isFirstAccount || false} isFirstAccount={addAccountProvider?.isFirstAccount || false}
/> />
</div> </PageShell>
); );
} }