feat(ui): add visible pause toggle button to account item cards

- add pause/play button left of user avatar

- paused accounts show play icon (green) and dimmed card

- active accounts show pause icon

- includes loading spinner and tooltip
This commit is contained in:
kaitranntt
2026-01-24 12:33:37 -05:00
parent 118ce46e72
commit 56dfb2429b
@@ -6,8 +6,6 @@
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 { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Switch } from '@/components/ui/switch';
import { Checkbox } from '@/components/ui/checkbox';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -25,10 +23,10 @@ import {
CheckCircle2, CheckCircle2,
HelpCircle, HelpCircle,
Pause, Pause,
Play,
AlertCircle, AlertCircle,
AlertTriangle, AlertTriangle,
FolderCode, FolderCode,
Radio,
} from 'lucide-react'; } from 'lucide-react';
import { import {
cn, cn,
@@ -95,15 +93,10 @@ export function AccountItem({
onSetDefault, onSetDefault,
onRemove, onRemove,
onPauseToggle, onPauseToggle,
onSoloMode,
isRemoving, isRemoving,
isPausingAccount, isPausingAccount,
isSoloingAccount,
privacyMode, privacyMode,
showQuota, showQuota,
selectable,
selected,
onSelectChange,
}: AccountItemProps) { }: AccountItemProps) {
// Fetch runtime stats to get actual lastUsedAt (more accurate than file state) // Fetch runtime stats to get actual lastUsedAt (more accurate than file state)
const { data: stats } = useCliproxyStats(showQuota); const { data: stats } = useCliproxyStats(showQuota);
@@ -131,18 +124,37 @@ export function AccountItem({
className={cn( className={cn(
'flex flex-col gap-2 p-3 rounded-lg border transition-colors', 'flex flex-col gap-2 p-3 rounded-lg border transition-colors',
account.isDefault ? 'border-primary/30 bg-primary/5' : 'border-border hover:bg-muted/30', account.isDefault ? 'border-primary/30 bg-primary/5' : 'border-border hover:bg-muted/30',
account.paused && 'opacity-50 border-muted' account.paused && 'opacity-75'
)} )}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Multi-select checkbox */} {/* Pause/Resume toggle button - visible left of avatar */}
{selectable && ( {onPauseToggle && (
<Checkbox <TooltipProvider>
checked={selected} <Tooltip>
onCheckedChange={(checked) => onSelectChange?.(!!checked)} <TooltipTrigger asChild>
aria-label={`Select ${account.email || account.id}`} <Button
/> variant="ghost"
size="icon"
className="h-7 w-7 shrink-0"
onClick={() => onPauseToggle(!account.paused)}
disabled={isPausingAccount}
>
{isPausingAccount ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : account.paused ? (
<Play className="w-4 h-4 text-emerald-500" />
) : (
<Pause className="w-4 h-4 text-muted-foreground hover:text-foreground" />
)}
</Button>
</TooltipTrigger>
<TooltipContent side="top">
{account.paused ? 'Resume account' : 'Pause account'}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)} )}
<div <div
className={cn( className={cn(
@@ -242,80 +254,47 @@ export function AccountItem({
</div> </div>
</div> </div>
{/* Inline controls: Solo button + Toggle switch */} <DropdownMenu>
<div className="flex items-center gap-1.5"> <DropdownMenuTrigger asChild>
{/* Solo mode button */} <Button variant="ghost" size="icon" className="h-7 w-7">
{onSoloMode && ( <MoreHorizontal className="w-4 h-4" />
<TooltipProvider> </Button>
<Tooltip> </DropdownMenuTrigger>
<TooltipTrigger asChild> <DropdownMenuContent align="end">
<Button {!account.isDefault && (
variant="ghost" <DropdownMenuItem onClick={onSetDefault}>
size="icon" <Star className="w-4 h-4 mr-2" />
className="h-6 w-6" Set as default
onClick={onSoloMode}
disabled={isSoloingAccount || account.paused}
aria-label="Activate only this account"
>
{isSoloingAccount ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Radio className="h-3.5 w-3.5" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>Activate only this account</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* Pause/Resume toggle switch */}
{onPauseToggle && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
<Switch
checked={!account.paused}
onCheckedChange={(checked) => onPauseToggle(!checked)}
disabled={isPausingAccount}
aria-label={account.paused ? 'Resume account' : 'Pause account'}
className="scale-90"
/>
</div>
</TooltipTrigger>
<TooltipContent>
{account.paused ? 'Resume account' : 'Pause account'}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* Dropdown menu for other actions */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-7 w-7">
<MoreHorizontal className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{!account.isDefault && (
<DropdownMenuItem onClick={onSetDefault}>
<Star className="w-4 h-4 mr-2" />
Set as default
</DropdownMenuItem>
)}
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={onRemove}
disabled={isRemoving}
>
<Trash2 className="w-4 h-4 mr-2" />
{isRemoving ? 'Removing...' : 'Remove account'}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> )}
</DropdownMenu> {onPauseToggle && (
</div> <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}
disabled={isRemoving}
>
<Trash2 className="w-4 h-4 mr-2" />
{isRemoving ? 'Removing...' : 'Remove account'}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
{/* Quota bar - supports all providers with quota API */} {/* Quota bar - supports all providers with quota API */}