feat(ui): simplify CLIProxy page UX with dedicated Add Account dialog

- Remove redundant "Create Variant" button (Quick Setup is more comprehensive)
- Add AddAccountDialog component for per-provider account addition
- Keep "Quick Setup" for full variant creation wizard
- Keep "Add Account" per-row for adding accounts only
This commit is contained in:
kaitranntt
2025-12-09 16:51:24 -05:00
parent d868dc4c32
commit 8f5c006f07
2 changed files with 124 additions and 34 deletions
+96
View File
@@ -0,0 +1,96 @@
/**
* Add Account Dialog Component
* Simple dialog to add another OAuth account to a provider
*
* Shows auth command + refresh button (no variant creation)
*/
import { useState } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Copy, Check, RefreshCw, Terminal } from 'lucide-react';
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
interface AddAccountDialogProps {
open: boolean;
onClose: () => void;
provider: string;
displayName: string;
}
export function AddAccountDialog({ open, onClose, provider, displayName }: AddAccountDialogProps) {
const [copied, setCopied] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const { refetch } = useCliproxyAuth();
const authCommand = `ccs ${provider} --auth --add`;
const copyCommand = async () => {
await navigator.clipboard.writeText(authCommand);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleRefresh = async () => {
setIsRefreshing(true);
await refetch();
setIsRefreshing(false);
onClose();
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Add {displayName} Account</DialogTitle>
<DialogDescription>
Run the command below in your terminal to authenticate a new account
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<Card>
<CardContent className="p-4 space-y-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Terminal className="w-4 h-4" />
Run this command:
</div>
<div className="flex items-center gap-2">
<code className="flex-1 px-3 py-2 bg-muted rounded-md font-mono text-sm">
{authCommand}
</code>
<Button variant="outline" size="icon" onClick={copyCommand}>
{copied ? (
<Check className="w-4 h-4 text-green-500" />
) : (
<Copy className="w-4 h-4" />
)}
</Button>
</div>
<div className="text-xs text-muted-foreground">
This will open your browser to authenticate with {displayName}
</div>
</CardContent>
</Card>
<div className="flex items-center justify-end gap-2">
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleRefresh} disabled={isRefreshing}>
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
{isRefreshing ? 'Checking...' : 'I ran the command'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}
+28 -34
View File
@@ -16,8 +16,8 @@ import {
} from '@/components/ui/dropdown-menu';
import { Plus, Check, X, User, ChevronDown, Star, Trash2, Sparkles } from 'lucide-react';
import { CliproxyTable } from '@/components/cliproxy-table';
import { CliproxyDialog } from '@/components/cliproxy-dialog';
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
import { AddAccountDialog } from '@/components/add-account-dialog';
import {
useCliproxy,
useCliproxyAuth,
@@ -85,10 +85,12 @@ function ProviderRow({
status,
setDefaultMutation,
removeMutation,
onAddAccount,
}: {
status: AuthStatus;
setDefaultMutation: ReturnType<typeof useSetDefaultAccount>;
removeMutation: ReturnType<typeof useRemoveAccount>;
onAddAccount: () => void;
}) {
const accounts = status.accounts || [];
@@ -148,35 +150,22 @@ function ProviderRow({
</div>
<div className="flex items-center gap-2">
{!status.authenticated && (
<div className="text-xs font-mono bg-muted px-2 py-1 rounded text-muted-foreground select-all">
ccs {status.provider} --auth
</div>
)}
{status.authenticated && (
<Button
variant="outline"
size="sm"
className="h-7 text-xs gap-1"
onClick={() => {
// This is a placeholder since we can't actually run the auth command from UI easily without a terminal
// But we can show the command to run
navigator.clipboard.writeText(`ccs ${status.provider} --auth`);
}}
title="Copy auth command"
>
<Plus className="w-3 h-3" />
Add Account
</Button>
)}
{/* Show Add Account button for all - opens dialog with instructions */}
<Button variant="outline" size="sm" className="h-7 text-xs gap-1" onClick={onAddAccount}>
<Plus className="w-3 h-3" />
Add Account
</Button>
</div>
</div>
);
}
export function CliproxyPage() {
const [dialogOpen, setDialogOpen] = useState(false);
const [wizardOpen, setWizardOpen] = useState(false);
const [addAccountProvider, setAddAccountProvider] = useState<{
provider: string;
displayName: string;
} | null>(null);
const { data, isLoading } = useCliproxy();
const { data: authData, isLoading: authLoading } = useCliproxyAuth();
const setDefaultMutation = useSetDefaultAccount();
@@ -191,16 +180,10 @@ export function CliproxyPage() {
Manage OAuth-based provider variants and multi-account configurations
</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => setWizardOpen(true)}>
<Sparkles className="w-4 h-4 mr-2" />
Quick Setup
</Button>
<Button onClick={() => setDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Create Variant
</Button>
</div>
<Button onClick={() => setWizardOpen(true)}>
<Sparkles className="w-4 h-4 mr-2" />
Quick Setup
</Button>
</div>
{/* Built-in Profiles with Account Management */}
@@ -226,6 +209,12 @@ export function CliproxyPage() {
status={status}
setDefaultMutation={setDefaultMutation}
removeMutation={removeMutation}
onAddAccount={() =>
setAddAccountProvider({
provider: status.provider,
displayName: status.displayName,
})
}
/>
))}
</div>
@@ -252,8 +241,13 @@ export function CliproxyPage() {
)}
</div>
<CliproxyDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
<QuickSetupWizard open={wizardOpen} onClose={() => setWizardOpen(false)} />
<AddAccountDialog
open={addAccountProvider !== null}
onClose={() => setAddAccountProvider(null)}
provider={addAccountProvider?.provider || ''}
displayName={addAccountProvider?.displayName || ''}
/>
</div>
);
}