mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(ui): add pause toggle to flow viz account cards, remove dropdown redundancy
- Add visible pause/resume toggle button to AccountCard in flow-viz (appears on hover or when paused, with tooltip) - Pass pause toggle props from AuthMonitor through AccountFlowViz - Remove redundant pause/resume from account-item dropdown menu (keeps only "Set as default" and "Remove account") - Add paused state to AccountData type with opacity indication
This commit is contained in:
@@ -10,9 +10,10 @@ import {
|
||||
getMinClaudeQuota,
|
||||
} from '@/lib/utils';
|
||||
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
||||
import { GripVertical, Loader2, Clock, Pause } from 'lucide-react';
|
||||
import { GripVertical, Loader2, Clock, Pause, Play } from 'lucide-react';
|
||||
import { useAccountQuota } from '@/hooks/use-cliproxy-stats';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
import type { AccountData, DragOffset } from './types';
|
||||
import { cleanEmail } from './utils';
|
||||
@@ -34,6 +35,8 @@ interface AccountCardProps {
|
||||
onPointerDown: (e: React.PointerEvent) => void;
|
||||
onPointerMove: (e: React.PointerEvent) => void;
|
||||
onPointerUp: () => void;
|
||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||
isPausingAccount?: boolean;
|
||||
}
|
||||
|
||||
const BORDER_SIDE_MAP: Record<Zone, string> = {
|
||||
@@ -77,6 +80,8 @@ export function AccountCard({
|
||||
onPointerDown,
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
onPauseToggle,
|
||||
isPausingAccount,
|
||||
}: AccountCardProps) {
|
||||
const borderSide = BORDER_SIDE_MAP[zone];
|
||||
const borderColor = getBorderColorStyle(zone, account.color);
|
||||
@@ -109,13 +114,48 @@ export function AccountCard({
|
||||
borderSide,
|
||||
'select-none touch-none',
|
||||
isHovered && 'bg-muted/50 dark:bg-zinc-800/60',
|
||||
isDragging && 'cursor-grabbing shadow-xl scale-105 z-50'
|
||||
isDragging && 'cursor-grabbing shadow-xl scale-105 z-50',
|
||||
account.paused && 'opacity-60'
|
||||
)}
|
||||
style={{
|
||||
...borderColor,
|
||||
transform: `translate(${offset.x}px, ${offset.y}px)${isDragging ? ' scale(1.05)' : ''}`,
|
||||
}}
|
||||
>
|
||||
{/* Pause toggle button - visible on hover or when paused */}
|
||||
{onPauseToggle && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'absolute top-1.5 left-1.5 h-5 w-5 z-10',
|
||||
'opacity-0 group-hover/card:opacity-100 transition-opacity',
|
||||
account.paused && 'opacity-100'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onPauseToggle(account.id, !account.paused);
|
||||
}}
|
||||
disabled={isPausingAccount}
|
||||
>
|
||||
{isPausingAccount ? (
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
) : account.paused ? (
|
||||
<Play className="w-3 h-3 text-emerald-500" />
|
||||
) : (
|
||||
<Pause className="w-3 h-3 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="text-xs">
|
||||
{account.paused ? 'Resume account' : 'Pause account'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<GripVertical className="absolute top-2 right-2 w-4 h-4 text-muted-foreground/40" />
|
||||
<div className="flex justify-between items-start mb-1 mr-4">
|
||||
<span
|
||||
|
||||
@@ -22,7 +22,12 @@ import { FlowVizHeader } from './flow-viz-header';
|
||||
// Re-export types for backward compatibility
|
||||
export type { AccountData, ProviderData, AccountFlowVizProps, ConnectionEvent } from './types';
|
||||
|
||||
export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
|
||||
export function AccountFlowViz({
|
||||
providerData,
|
||||
onBack,
|
||||
onPauseToggle,
|
||||
isPausingAccount,
|
||||
}: AccountFlowVizProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const [hoveredAccount, setHoveredAccount] = useState<number | null>(null);
|
||||
@@ -112,6 +117,8 @@ export function AccountFlowViz({ providerData, onBack }: AccountFlowVizProps) {
|
||||
onPointerDown={(e) => handlePointerDown(account.id, e)}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPauseToggle={onPauseToggle}
|
||||
isPausingAccount={isPausingAccount}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface AccountData {
|
||||
failureCount: number;
|
||||
lastUsedAt?: string;
|
||||
color: string;
|
||||
paused?: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderData {
|
||||
@@ -28,6 +29,8 @@ export interface ProviderData {
|
||||
export interface AccountFlowVizProps {
|
||||
providerData: ProviderData;
|
||||
onBack?: () => void;
|
||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||
isPausingAccount?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionEvent {
|
||||
|
||||
@@ -267,24 +267,6 @@ export function AccountItem({
|
||||
Set as default
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onPauseToggle && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => onPauseToggle(!account.paused)}
|
||||
disabled={isPausingAccount}
|
||||
>
|
||||
{account.paused ? (
|
||||
<>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
{isPausingAccount ? 'Resuming...' : 'Resume account'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pause className="w-4 h-4 mr-2" />
|
||||
{isPausingAccount ? 'Pausing...' : 'Pause account'}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={onRemove}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { STATUS_COLORS } from '@/lib/utils';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { AccountFlowViz } from '@/components/account-flow-viz';
|
||||
import { usePrivacy } from '@/contexts/privacy-context';
|
||||
import { usePauseAccount, useResumeAccount } from '@/hooks/use-cliproxy';
|
||||
import { Activity, CheckCircle2, XCircle, Radio } from 'lucide-react';
|
||||
|
||||
import { useAuthMonitorData } from './hooks';
|
||||
@@ -33,11 +34,24 @@ export function AuthMonitor() {
|
||||
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
||||
const [hoveredProvider, setHoveredProvider] = useState<string | null>(null);
|
||||
|
||||
// Account control mutations for flow viz
|
||||
const pauseMutation = usePauseAccount();
|
||||
const resumeMutation = useResumeAccount();
|
||||
|
||||
// Get selected provider data for detail view
|
||||
const selectedProviderData = selectedProvider
|
||||
? providerStats.find((ps) => ps.provider === selectedProvider)
|
||||
: null;
|
||||
|
||||
const handlePauseToggle = (accountId: string, paused: boolean) => {
|
||||
if (!selectedProvider || pauseMutation.isPending || resumeMutation.isPending) return;
|
||||
if (paused) {
|
||||
pauseMutation.mutate({ provider: selectedProvider, accountId });
|
||||
} else {
|
||||
resumeMutation.mutate({ provider: selectedProvider, accountId });
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="rounded-xl border border-border overflow-hidden font-mono text-[13px] bg-card/50 dark:bg-zinc-900/60 backdrop-blur-sm">
|
||||
@@ -121,6 +135,8 @@ export function AuthMonitor() {
|
||||
<AccountFlowViz
|
||||
providerData={selectedProviderData}
|
||||
onBack={() => setSelectedProvider(null)}
|
||||
onPauseToggle={handlePauseToggle}
|
||||
isPausingAccount={pauseMutation.isPending || resumeMutation.isPending}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-6">
|
||||
|
||||
Reference in New Issue
Block a user