mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 00:17:47 +00:00
fix(accounts-ui): condense sync guidance and remove redundant controls
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@/components/ui/alert-dialog';
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Check, CheckCheck, Link2, Pencil, RotateCcw, Trash2, Unlink } from 'lucide-react';
|
import { Check, CheckCheck, Pencil, RotateCcw, Trash2 } from 'lucide-react';
|
||||||
import { EditAccountContextDialog } from '@/components/account/edit-account-context-dialog';
|
import { EditAccountContextDialog } from '@/components/account/edit-account-context-dialog';
|
||||||
import {
|
import {
|
||||||
useSetDefaultAccount,
|
useSetDefaultAccount,
|
||||||
@@ -137,51 +137,22 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
deleteMutation.isPending ||
|
deleteMutation.isPending ||
|
||||||
updateContextMutation.isPending;
|
updateContextMutation.isPending;
|
||||||
const isCliproxy = row.original.type === 'cliproxy';
|
const isCliproxy = row.original.type === 'cliproxy';
|
||||||
const isShared = row.original.context_mode === 'shared';
|
|
||||||
const hasLegacyInference =
|
const hasLegacyInference =
|
||||||
row.original.context_inferred || row.original.continuity_inferred;
|
row.original.context_inferred || row.original.continuity_inferred;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{!isCliproxy && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 px-2"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={() => setContextTarget(row.original)}
|
|
||||||
title="Edit context mode"
|
|
||||||
>
|
|
||||||
<Pencil className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isCliproxy && (
|
{!isCliproxy && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 px-2"
|
className="h-8 px-2"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
onClick={() =>
|
onClick={() => setContextTarget(row.original)}
|
||||||
updateContextMutation.mutate({
|
title="Edit sync mode, group, and continuity depth"
|
||||||
name: row.original.name,
|
|
||||||
context_mode: isShared ? 'isolated' : 'shared',
|
|
||||||
context_group: isShared ? undefined : row.original.context_group || 'default',
|
|
||||||
continuity_mode: isShared ? undefined : 'standard',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
title={isShared ? 'Switch to isolated mode' : 'Switch to shared mode'}
|
|
||||||
>
|
>
|
||||||
{isShared ? (
|
<Pencil className="w-3.5 h-3.5 mr-1" />
|
||||||
<>
|
Sync
|
||||||
<Unlink className="w-3 h-3 mr-1" />
|
|
||||||
Unlink
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Link2 className="w-3 h-3 mr-1" />
|
|
||||||
Link
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isCliproxy && hasLegacyInference && (
|
{!isCliproxy && hasLegacyInference && (
|
||||||
@@ -270,7 +241,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
|||||||
created: 'w-[150px]',
|
created: 'w-[150px]',
|
||||||
last_used: 'w-[150px]',
|
last_used: 'w-[150px]',
|
||||||
context: 'w-[170px]',
|
context: 'w-[170px]',
|
||||||
actions: 'w-[340px]',
|
actions: 'w-[290px]',
|
||||||
}[header.id] || 'w-auto';
|
}[header.id] || 'w-auto';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowRightLeft,
|
ArrowRightLeft,
|
||||||
|
ChevronDown,
|
||||||
Layers3,
|
Layers3,
|
||||||
Link2,
|
Link2,
|
||||||
Unlink,
|
Unlink,
|
||||||
@@ -8,7 +10,9 @@ import {
|
|||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface HistorySyncLearningMapProps {
|
interface HistorySyncLearningMapProps {
|
||||||
@@ -17,51 +21,49 @@ interface HistorySyncLearningMapProps {
|
|||||||
deeperSharedCount: number;
|
deeperSharedCount: number;
|
||||||
sharedGroups: string[];
|
sharedGroups: string[];
|
||||||
legacyTargetCount: number;
|
legacyTargetCount: number;
|
||||||
|
cliproxyCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type StageTone = 'isolated' | 'shared' | 'deeper';
|
type StageTone = 'isolated' | 'shared' | 'deeper';
|
||||||
|
|
||||||
function StageCard({
|
function StageTile({
|
||||||
title,
|
title,
|
||||||
count,
|
count,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
tone,
|
tone,
|
||||||
description,
|
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
count: number;
|
count: number;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
tone: StageTone;
|
tone: StageTone;
|
||||||
description: string;
|
|
||||||
}) {
|
}) {
|
||||||
const toneClasses: Record<StageTone, { card: string; icon: string; count: string }> = {
|
const toneClasses: Record<StageTone, { border: string; icon: string; count: string }> = {
|
||||||
isolated: {
|
isolated: {
|
||||||
card: 'border-blue-300/60 bg-blue-50/40 dark:border-blue-900/40 dark:bg-blue-900/10',
|
border: 'border-blue-300/60 bg-blue-50/40 dark:border-blue-900/40 dark:bg-blue-900/10',
|
||||||
icon: 'text-blue-700 dark:text-blue-400',
|
icon: 'text-blue-700 dark:text-blue-400',
|
||||||
count: 'text-blue-700 dark:text-blue-400',
|
count: 'text-blue-700 dark:text-blue-400',
|
||||||
},
|
},
|
||||||
shared: {
|
shared: {
|
||||||
card: 'border-emerald-300/60 bg-emerald-50/40 dark:border-emerald-900/40 dark:bg-emerald-900/10',
|
border:
|
||||||
|
'border-emerald-300/60 bg-emerald-50/40 dark:border-emerald-900/40 dark:bg-emerald-900/10',
|
||||||
icon: 'text-emerald-700 dark:text-emerald-400',
|
icon: 'text-emerald-700 dark:text-emerald-400',
|
||||||
count: 'text-emerald-700 dark:text-emerald-400',
|
count: 'text-emerald-700 dark:text-emerald-400',
|
||||||
},
|
},
|
||||||
deeper: {
|
deeper: {
|
||||||
card: 'border-indigo-300/60 bg-indigo-50/40 dark:border-indigo-900/40 dark:bg-indigo-900/10',
|
border:
|
||||||
|
'border-indigo-300/60 bg-indigo-50/40 dark:border-indigo-900/40 dark:bg-indigo-900/10',
|
||||||
icon: 'text-indigo-700 dark:text-indigo-400',
|
icon: 'text-indigo-700 dark:text-indigo-400',
|
||||||
count: 'text-indigo-700 dark:text-indigo-400',
|
count: 'text-indigo-700 dark:text-indigo-400',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('rounded-md border p-3', toneClasses[tone].card)}>
|
<div className={cn('rounded-md border p-2.5', toneClasses[tone].border)}>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<p className="text-sm font-semibold">{title}</p>
|
<p className="text-xs font-semibold">{title}</p>
|
||||||
<Icon className={cn('h-4 w-4', toneClasses[tone].icon)} />
|
<Icon className={cn('h-3.5 w-3.5', toneClasses[tone].icon)} />
|
||||||
</div>
|
</div>
|
||||||
<p className={cn('mt-1 text-2xl font-mono font-semibold', toneClasses[tone].count)}>
|
<p className={cn('mt-1 text-lg font-mono font-semibold', toneClasses[tone].count)}>{count}</p>
|
||||||
{count}
|
|
||||||
</p>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">{description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -72,91 +74,93 @@ export function HistorySyncLearningMap({
|
|||||||
deeperSharedCount,
|
deeperSharedCount,
|
||||||
sharedGroups,
|
sharedGroups,
|
||||||
legacyTargetCount,
|
legacyTargetCount,
|
||||||
|
cliproxyCount,
|
||||||
}: HistorySyncLearningMapProps) {
|
}: HistorySyncLearningMapProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const groupsToShow = sharedGroups.length > 0 ? sharedGroups : ['default'];
|
const groupsToShow = sharedGroups.length > 0 ? sharedGroups : ['default'];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-dashed">
|
<Card className="border-dashed">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-2">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<CardTitle className="text-base">How History Sync Works</CardTitle>
|
<div>
|
||||||
|
<CardTitle className="text-base">How History Sync Works</CardTitle>
|
||||||
|
<CardDescription className="mt-1">
|
||||||
|
Isolated -> Shared -> Deeper. Use <code>Sync</code> per row for all changes.
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
<Badge variant="outline">Learning Map</Badge>
|
<Badge variant="outline">Learning Map</Badge>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>
|
|
||||||
Accounts can move between modes at any time. Link/unlink is instant; Edit gives full
|
|
||||||
control of group and continuity depth.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid gap-3 lg:grid-cols-[1fr_auto_1fr_auto_1fr] lg:items-center">
|
|
||||||
<StageCard
|
|
||||||
title="Isolated"
|
|
||||||
count={isolatedCount}
|
|
||||||
icon={Unlink}
|
|
||||||
tone="isolated"
|
|
||||||
description="No shared history with other accounts."
|
|
||||||
/>
|
|
||||||
<div className="hidden lg:flex justify-center text-muted-foreground">
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<StageCard
|
|
||||||
title="Shared (Standard)"
|
|
||||||
count={sharedStandardCount}
|
|
||||||
icon={Link2}
|
|
||||||
tone="shared"
|
|
||||||
description="Shared project context only."
|
|
||||||
/>
|
|
||||||
<div className="hidden lg:flex justify-center text-muted-foreground">
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<StageCard
|
|
||||||
title="Shared (Deeper)"
|
|
||||||
count={deeperSharedCount}
|
|
||||||
icon={Waves}
|
|
||||||
tone="deeper"
|
|
||||||
description="Adds continuity data: session-env/file-history/todos/shell-snapshots."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-3 lg:grid-cols-2">
|
<CardContent className="space-y-3">
|
||||||
<div className="rounded-md border bg-muted/20 p-3">
|
{cliproxyCount > 0 && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="rounded-md border border-blue-300/60 bg-blue-50/40 px-3 py-2 text-xs text-blue-800 dark:border-blue-900/40 dark:bg-blue-900/10 dark:text-blue-300">
|
||||||
<ArrowRightLeft className="h-4 w-4 text-muted-foreground" />
|
{cliproxyCount} CLIProxy Claude pool account{cliproxyCount > 1 ? 's are' : ' is'}
|
||||||
<p className="text-sm font-semibold">Mode Switch Actions</p>
|
managed in Action Center / CLIProxy page.
|
||||||
</div>
|
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
|
||||||
<Badge variant="secondary">Link: Isolated -> Shared</Badge>
|
|
||||||
<Badge variant="secondary">Unlink: Shared -> Isolated</Badge>
|
|
||||||
<Badge variant="secondary">Edit: Group + Deeper</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-md border bg-muted/20 p-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Layers3 className="h-4 w-4 text-muted-foreground" />
|
|
||||||
<p className="text-sm font-semibold">History Sync Group</p>
|
|
||||||
</div>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
|
||||||
Accounts in the same group share the same project context bucket. Group names are
|
|
||||||
user-defined lanes, with <code>default</code> as fallback.
|
|
||||||
</p>
|
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
|
||||||
{groupsToShow.map((group) => (
|
|
||||||
<Badge key={group} variant="outline" className="font-mono text-[11px]">
|
|
||||||
{group}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{legacyTargetCount > 0 && (
|
|
||||||
<div className="rounded-md border border-amber-500/50 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-300">
|
|
||||||
{legacyTargetCount} legacy account
|
|
||||||
{legacyTargetCount > 1 ? 's still need' : ' still needs'} explicit confirmation. Use{' '}
|
|
||||||
<strong>Confirm Legacy Policies</strong> in Action Center.
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="grid gap-2 sm:grid-cols-[1fr_auto_1fr_auto_1fr] sm:items-center">
|
||||||
|
<StageTile title="Isolated" count={isolatedCount} icon={Unlink} tone="isolated" />
|
||||||
|
<div className="hidden sm:flex justify-center text-muted-foreground">
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<StageTile title="Shared" count={sharedStandardCount} icon={Link2} tone="shared" />
|
||||||
|
<div className="hidden sm:flex justify-center text-muted-foreground">
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<StageTile title="Deeper" count={deeperSharedCount} icon={Waves} tone="deeper" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapsible open={open} onOpenChange={setOpen}>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-auto w-full justify-between px-0 py-0 text-xs">
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
Show details: groups, switching, and legacy policy
|
||||||
|
</span>
|
||||||
|
<ChevronDown className={cn('h-4 w-4 transition-transform', open && 'rotate-180')} />
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="pt-2">
|
||||||
|
<div className="grid gap-2 lg:grid-cols-2">
|
||||||
|
<div className="rounded-md border bg-muted/20 p-2.5 text-xs">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ArrowRightLeft className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
<p className="font-semibold">Mode Switch</p>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-muted-foreground">
|
||||||
|
Sync dialog lets users move between isolated/shared and choose deeper continuity.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-md border bg-muted/20 p-2.5 text-xs">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Layers3 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
<p className="font-semibold">History Sync Group</p>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-muted-foreground">
|
||||||
|
Same group means shared project context lane. Default fallback is{' '}
|
||||||
|
<code>default</code>.
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||||
|
{groupsToShow.map((group) => (
|
||||||
|
<Badge key={group} variant="outline" className="font-mono text-[10px]">
|
||||||
|
{group}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{legacyTargetCount > 0 && (
|
||||||
|
<div className="mt-2 rounded-md border border-amber-500/50 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-300">
|
||||||
|
{legacyTargetCount} legacy account
|
||||||
|
{legacyTargetCount > 1 ? 's still need' : ' still needs'} explicit confirmation.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
+19
-142
@@ -10,11 +10,8 @@ import {
|
|||||||
ArrowRight,
|
ArrowRight,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
Link2,
|
|
||||||
Plus,
|
Plus,
|
||||||
Unlink,
|
|
||||||
Users,
|
Users,
|
||||||
Waves,
|
|
||||||
Zap,
|
Zap,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { AccountsTable } from '@/components/account/accounts-table';
|
import { AccountsTable } from '@/components/account/accounts-table';
|
||||||
@@ -23,82 +20,11 @@ import { HistorySyncLearningMap } from '@/components/account/history-sync-learni
|
|||||||
import { CopyButton } from '@/components/ui/copy-button';
|
import { CopyButton } from '@/components/ui/copy-button';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { useAccounts, useConfirmLegacyAccountPolicies } from '@/hooks/use-accounts';
|
import { useAccounts, useConfirmLegacyAccountPolicies } from '@/hooks/use-accounts';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
|
||||||
|
|
||||||
type MetricTone = 'default' | 'shared' | 'deeper' | 'isolated';
|
|
||||||
|
|
||||||
function MetricTile({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
icon: Icon,
|
|
||||||
tone = 'default',
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value: number;
|
|
||||||
icon: LucideIcon;
|
|
||||||
tone?: MetricTone;
|
|
||||||
}) {
|
|
||||||
const toneClasses: Record<MetricTone, { border: string; icon: string }> = {
|
|
||||||
default: {
|
|
||||||
border: 'border-border',
|
|
||||||
icon: 'text-primary',
|
|
||||||
},
|
|
||||||
shared: {
|
|
||||||
border: 'border-emerald-300/60 dark:border-emerald-900/40',
|
|
||||||
icon: 'text-emerald-700 dark:text-emerald-400',
|
|
||||||
},
|
|
||||||
deeper: {
|
|
||||||
border: 'border-indigo-300/60 dark:border-indigo-900/40',
|
|
||||||
icon: 'text-indigo-700 dark:text-indigo-400',
|
|
||||||
},
|
|
||||||
isolated: {
|
|
||||||
border: 'border-blue-300/60 dark:border-blue-900/40',
|
|
||||||
icon: 'text-blue-700 dark:text-blue-400',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn('rounded-md border bg-card px-3 py-2.5', toneClasses[tone].border)}>
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
|
||||||
<span className="text-[11px] uppercase tracking-wide text-muted-foreground">{label}</span>
|
|
||||||
<Icon className={cn('h-3.5 w-3.5', toneClasses[tone].icon)} />
|
|
||||||
</div>
|
|
||||||
<p className={cn('mt-1 text-xl font-mono font-semibold', toneClasses[tone].icon)}>{value}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function StrategyCard({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
variant,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
variant: 'auth' | 'pool';
|
|
||||||
}) {
|
|
||||||
const variantClasses =
|
|
||||||
variant === 'auth'
|
|
||||||
? 'border-emerald-300/70 bg-emerald-50/40 dark:border-emerald-900/40 dark:bg-emerald-900/10'
|
|
||||||
: 'border-blue-300/70 bg-blue-50/40 dark:border-blue-900/40 dark:bg-blue-900/10';
|
|
||||||
const titleClasses =
|
|
||||||
variant === 'auth'
|
|
||||||
? 'text-emerald-800 dark:text-emerald-300'
|
|
||||||
: 'text-blue-800 dark:text-blue-300';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn('rounded-md border px-3 py-2.5', variantClasses)}>
|
|
||||||
<p className={cn('text-sm font-semibold', titleClasses)}>{title}</p>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">{description}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AccountsPage() {
|
export function AccountsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -164,40 +90,7 @@ export function AccountsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="p-3 space-y-4">
|
<div className="p-3 space-y-3">
|
||||||
<section className="space-y-2">
|
|
||||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
||||||
Snapshot
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<MetricTile label="Total" value={authAccounts.length} icon={Users} />
|
|
||||||
<MetricTile label="Shared" value={sharedCount} icon={Link2} tone="shared" />
|
|
||||||
<MetricTile label="Deeper" value={deeperSharedCount} icon={Waves} tone="deeper" />
|
|
||||||
<MetricTile
|
|
||||||
label="Isolated"
|
|
||||||
value={isolatedCount}
|
|
||||||
icon={Unlink}
|
|
||||||
tone="isolated"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="space-y-2">
|
|
||||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
||||||
Strategy Lanes
|
|
||||||
</p>
|
|
||||||
<StrategyCard
|
|
||||||
variant="auth"
|
|
||||||
title="Lane A: ccs auth continuity"
|
|
||||||
description="Isolation-first accounts with explicit shared/deeper controls per profile."
|
|
||||||
/>
|
|
||||||
<StrategyCard
|
|
||||||
variant="pool"
|
|
||||||
title="Lane B: CLIProxy Claude pool"
|
|
||||||
description="OAuth pool routing for lower manual account switching."
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{hasLegacyFollowUp && (
|
{hasLegacyFollowUp && (
|
||||||
<section className="space-y-2">
|
<section className="space-y-2">
|
||||||
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||||
@@ -238,19 +131,14 @@ export function AccountsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!hasLegacyFollowUp && (
|
||||||
|
<div className="rounded-md border bg-background px-3 py-2 text-xs text-muted-foreground">
|
||||||
|
No legacy follow-up pending. Manage pool auth from Action Center.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
<div className="p-3 border-t bg-background text-xs text-muted-foreground">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span>Standard shared</span>
|
|
||||||
<span className="font-mono">{sharedStandardCount}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between mt-1">
|
|
||||||
<span>CLIProxy hidden</span>
|
|
||||||
<span className="font-mono">{cliproxyCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main workspace */}
|
{/* Main workspace */}
|
||||||
@@ -275,39 +163,26 @@ export function AccountsPage() {
|
|||||||
This table is intentionally scoped to
|
This table is intentionally scoped to
|
||||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">ccs auth</code>
|
<code className="mx-1 rounded bg-muted px-1 py-0.5">ccs auth</code>
|
||||||
accounts. Use
|
accounts. Use
|
||||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">Link</code>/
|
<code className="mx-1 rounded bg-muted px-1 py-0.5">Sync</code>
|
||||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">Unlink</code>
|
for mode/group/depth changes.
|
||||||
for quick policy changes and pencil edit for advanced group/deeper settings.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 p-5 space-y-4 overflow-y-auto">
|
<div className="flex-1 min-h-0 p-5 space-y-4 overflow-y-auto">
|
||||||
{cliproxyCount > 0 && (
|
|
||||||
<Alert variant="info">
|
|
||||||
<Zap className="h-4 w-4" />
|
|
||||||
<AlertTitle>CLIProxy pool accounts are managed in their own page</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
{cliproxyCount} OAuth account{cliproxyCount > 1 ? 's are' : ' is'} available in
|
|
||||||
CLIProxy. This table only covers local
|
|
||||||
<code className="mx-1 rounded bg-muted px-1 py-0.5">ccs auth</code>
|
|
||||||
profiles.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HistorySyncLearningMap
|
<HistorySyncLearningMap
|
||||||
isolatedCount={isolatedCount}
|
isolatedCount={isolatedCount}
|
||||||
sharedStandardCount={sharedStandardCount}
|
sharedStandardCount={sharedStandardCount}
|
||||||
deeperSharedCount={deeperSharedCount}
|
deeperSharedCount={deeperSharedCount}
|
||||||
sharedGroups={sharedGroups}
|
sharedGroups={sharedGroups}
|
||||||
legacyTargetCount={legacyTargetCount}
|
legacyTargetCount={legacyTargetCount}
|
||||||
|
cliproxyCount={cliproxyCount}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card className="flex flex-col">
|
<Card className="flex flex-col">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-lg">Account Matrix</CardTitle>
|
<CardTitle className="text-lg">Account Matrix</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Shared total: {sharedCount}. Actions now include quick link/unlink plus legacy
|
Shared total: {sharedCount}. Actions include Sync settings and legacy
|
||||||
confirmation.
|
confirmation.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -493,12 +368,14 @@ export function AccountsPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<HistorySyncLearningMap
|
||||||
<MetricTile label="Total" value={authAccounts.length} icon={Users} />
|
isolatedCount={isolatedCount}
|
||||||
<MetricTile label="Shared" value={sharedCount} icon={Link2} tone="shared" />
|
sharedStandardCount={sharedStandardCount}
|
||||||
<MetricTile label="Deeper" value={deeperSharedCount} icon={Waves} tone="deeper" />
|
deeperSharedCount={deeperSharedCount}
|
||||||
<MetricTile label="Isolated" value={isolatedCount} icon={Unlink} tone="isolated" />
|
sharedGroups={sharedGroups}
|
||||||
</div>
|
legacyTargetCount={legacyTargetCount}
|
||||||
|
cliproxyCount={cliproxyCount}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user