mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
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:
1 parent
d868dc4c32
commit
8f5c006f07
2 files changed
+124
-34
No files matched your search
@@ -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
@@ -16,8 +16,8 @@ import {
|
|||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Plus, Check, X, User, ChevronDown, Star, Trash2, Sparkles } from 'lucide-react';
|
import { Plus, Check, X, User, ChevronDown, Star, Trash2, Sparkles } from 'lucide-react';
|
||||||
import { CliproxyTable } from '@/components/cliproxy-table';
|
import { CliproxyTable } from '@/components/cliproxy-table';
|
||||||
import { CliproxyDialog } from '@/components/cliproxy-dialog';
|
|
||||||
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
|
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
|
||||||
|
import { AddAccountDialog } from '@/components/add-account-dialog';
|
||||||
import {
|
import {
|
||||||
useCliproxy,
|
useCliproxy,
|
||||||
useCliproxyAuth,
|
useCliproxyAuth,
|
||||||
@@ -85,10 +85,12 @@ function ProviderRow({
|
|||||||
status,
|
status,
|
||||||
setDefaultMutation,
|
setDefaultMutation,
|
||||||
removeMutation,
|
removeMutation,
|
||||||
|
onAddAccount,
|
||||||
}: {
|
}: {
|
||||||
status: AuthStatus;
|
status: AuthStatus;
|
||||||
setDefaultMutation: ReturnType<typeof useSetDefaultAccount>;
|
setDefaultMutation: ReturnType<typeof useSetDefaultAccount>;
|
||||||
removeMutation: ReturnType<typeof useRemoveAccount>;
|
removeMutation: ReturnType<typeof useRemoveAccount>;
|
||||||
|
onAddAccount: () => void;
|
||||||
}) {
|
}) {
|
||||||
const accounts = status.accounts || [];
|
const accounts = status.accounts || [];
|
||||||
|
|
||||||
@@ -148,35 +150,22 @@ function ProviderRow({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!status.authenticated && (
|
{/* Show Add Account button for all - opens dialog with instructions */}
|
||||||
<div className="text-xs font-mono bg-muted px-2 py-1 rounded text-muted-foreground select-all">
|
<Button variant="outline" size="sm" className="h-7 text-xs gap-1" onClick={onAddAccount}>
|
||||||
ccs {status.provider} --auth
|
<Plus className="w-3 h-3" />
|
||||||
</div>
|
Add Account
|
||||||
)}
|
</Button>
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CliproxyPage() {
|
export function CliproxyPage() {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
|
||||||
const [wizardOpen, setWizardOpen] = useState(false);
|
const [wizardOpen, setWizardOpen] = useState(false);
|
||||||
|
const [addAccountProvider, setAddAccountProvider] = useState<{
|
||||||
|
provider: string;
|
||||||
|
displayName: string;
|
||||||
|
} | null>(null);
|
||||||
const { data, isLoading } = useCliproxy();
|
const { data, isLoading } = useCliproxy();
|
||||||
const { data: authData, isLoading: authLoading } = useCliproxyAuth();
|
const { data: authData, isLoading: authLoading } = useCliproxyAuth();
|
||||||
const setDefaultMutation = useSetDefaultAccount();
|
const setDefaultMutation = useSetDefaultAccount();
|
||||||
@@ -191,16 +180,10 @@ export function CliproxyPage() {
|
|||||||
Manage OAuth-based provider variants and multi-account configurations
|
Manage OAuth-based provider variants and multi-account configurations
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<Button onClick={() => setWizardOpen(true)}>
|
||||||
<Button variant="outline" onClick={() => setWizardOpen(true)}>
|
<Sparkles className="w-4 h-4 mr-2" />
|
||||||
<Sparkles className="w-4 h-4 mr-2" />
|
Quick Setup
|
||||||
Quick Setup
|
</Button>
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setDialogOpen(true)}>
|
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
|
||||||
Create Variant
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Built-in Profiles with Account Management */}
|
{/* Built-in Profiles with Account Management */}
|
||||||
@@ -226,6 +209,12 @@ export function CliproxyPage() {
|
|||||||
status={status}
|
status={status}
|
||||||
setDefaultMutation={setDefaultMutation}
|
setDefaultMutation={setDefaultMutation}
|
||||||
removeMutation={removeMutation}
|
removeMutation={removeMutation}
|
||||||
|
onAddAccount={() =>
|
||||||
|
setAddAccountProvider({
|
||||||
|
provider: status.provider,
|
||||||
|
displayName: status.displayName,
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -252,8 +241,13 @@ export function CliproxyPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CliproxyDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
|
|
||||||
<QuickSetupWizard open={wizardOpen} onClose={() => setWizardOpen(false)} />
|
<QuickSetupWizard open={wizardOpen} onClose={() => setWizardOpen(false)} />
|
||||||
|
<AddAccountDialog
|
||||||
|
open={addAccountProvider !== null}
|
||||||
|
onClose={() => setAddAccountProvider(null)}
|
||||||
|
provider={addAccountProvider?.provider || ''}
|
||||||
|
displayName={addAccountProvider?.displayName || ''}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user