mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 08:17:22 +00:00
feat(ui): add pause/resume toggle and tier badges
- add pause/resume dropdown menu option in account item - display tier badges (ultra/pro/free) with color coding - show paused badge when account is paused - wire onPauseToggle through component tree Refs #282
This commit is contained in:
@@ -22,6 +22,8 @@ import {
|
||||
Loader2,
|
||||
CheckCircle2,
|
||||
HelpCircle,
|
||||
Pause,
|
||||
Play,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
cn,
|
||||
@@ -86,6 +88,7 @@ export function AccountItem({
|
||||
account,
|
||||
onSetDefault,
|
||||
onRemove,
|
||||
onPauseToggle,
|
||||
isRemoving,
|
||||
privacyMode,
|
||||
showQuota,
|
||||
@@ -139,6 +142,28 @@ export function AccountItem({
|
||||
Default
|
||||
</Badge>
|
||||
)}
|
||||
{account.tier && account.tier !== 'unknown' && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'text-[10px] h-4 px-1.5 uppercase',
|
||||
account.tier === 'ultra' && 'border-purple-500 text-purple-600',
|
||||
account.tier === 'pro' && 'border-blue-500 text-blue-600',
|
||||
account.tier === 'free' && 'border-gray-400 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{account.tier}
|
||||
</Badge>
|
||||
)}
|
||||
{account.paused && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] h-4 px-1.5 border-yellow-500 text-yellow-600"
|
||||
>
|
||||
<Pause className="w-2 h-2 mr-0.5" />
|
||||
Paused
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{account.lastUsedAt && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
|
||||
@@ -162,6 +187,21 @@ export function AccountItem({
|
||||
Set as default
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onPauseToggle && (
|
||||
<DropdownMenuItem onClick={() => onPauseToggle(!account.paused)}>
|
||||
{account.paused ? (
|
||||
<>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
Resume account
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pause className="w-4 h-4 mr-2" />
|
||||
Pause account
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={onRemove}
|
||||
|
||||
@@ -15,6 +15,7 @@ interface AccountsSectionProps {
|
||||
onAddAccount: () => void;
|
||||
onSetDefault: (accountId: string) => void;
|
||||
onRemoveAccount: (accountId: string) => void;
|
||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||
isRemovingAccount?: boolean;
|
||||
privacyMode?: boolean;
|
||||
/** Show quota bars for accounts (only applicable for 'agy' provider) */
|
||||
@@ -31,6 +32,7 @@ export function AccountsSection({
|
||||
onAddAccount,
|
||||
onSetDefault,
|
||||
onRemoveAccount,
|
||||
onPauseToggle,
|
||||
isRemovingAccount,
|
||||
privacyMode,
|
||||
showQuota,
|
||||
@@ -65,6 +67,9 @@ export function AccountsSection({
|
||||
account={account}
|
||||
onSetDefault={() => onSetDefault(account.id)}
|
||||
onRemove={() => onRemoveAccount(account.id)}
|
||||
onPauseToggle={
|
||||
onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined
|
||||
}
|
||||
isRemoving={isRemovingAccount}
|
||||
privacyMode={privacyMode}
|
||||
showQuota={showQuota}
|
||||
|
||||
@@ -38,6 +38,7 @@ export function ProviderEditor({
|
||||
onAddAccount,
|
||||
onSetDefault,
|
||||
onRemoveAccount,
|
||||
onPauseToggle,
|
||||
isRemovingAccount,
|
||||
}: ProviderEditorProps) {
|
||||
const [customPresetOpen, setCustomPresetOpen] = useState(false);
|
||||
@@ -200,6 +201,7 @@ export function ProviderEditor({
|
||||
onAddAccount={onAddAccount}
|
||||
onSetDefault={onSetDefault}
|
||||
onRemoveAccount={onRemoveAccount}
|
||||
onPauseToggle={onPauseToggle}
|
||||
isRemovingAccount={isRemovingAccount}
|
||||
privacyMode={privacyMode}
|
||||
isRemoteMode={isRemoteMode}
|
||||
|
||||
@@ -36,6 +36,7 @@ interface ModelConfigTabProps {
|
||||
onAddAccount: () => void;
|
||||
onSetDefault: (accountId: string) => void;
|
||||
onRemoveAccount: (accountId: string) => void;
|
||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||
isRemovingAccount?: boolean;
|
||||
privacyMode?: boolean;
|
||||
/** True if connected to remote CLIProxy (quota not available) */
|
||||
@@ -60,6 +61,7 @@ export function ModelConfigTab({
|
||||
onAddAccount,
|
||||
onSetDefault,
|
||||
onRemoveAccount,
|
||||
onPauseToggle,
|
||||
isRemovingAccount,
|
||||
privacyMode,
|
||||
isRemoteMode,
|
||||
@@ -134,6 +136,7 @@ export function ModelConfigTab({
|
||||
onAddAccount={onAddAccount}
|
||||
onSetDefault={onSetDefault}
|
||||
onRemoveAccount={onRemoveAccount}
|
||||
onPauseToggle={onPauseToggle}
|
||||
isRemovingAccount={isRemovingAccount}
|
||||
privacyMode={privacyMode}
|
||||
showQuota={provider === 'agy' && !isRemoteMode}
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface ProviderEditorProps {
|
||||
onAddAccount: () => void;
|
||||
onSetDefault: (accountId: string) => void;
|
||||
onRemoveAccount: (accountId: string) => void;
|
||||
onPauseToggle?: (accountId: string, paused: boolean) => void;
|
||||
isRemovingAccount?: boolean;
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ export interface AccountItemProps {
|
||||
account: OAuthAccount;
|
||||
onSetDefault: () => void;
|
||||
onRemove: () => void;
|
||||
onPauseToggle?: (paused: boolean) => void;
|
||||
isRemoving?: boolean;
|
||||
privacyMode?: boolean;
|
||||
/** Show quota bar (only for 'agy' provider) */
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
useCliproxyAuth,
|
||||
useSetDefaultAccount,
|
||||
useRemoveAccount,
|
||||
usePauseAccount,
|
||||
useResumeAccount,
|
||||
useDeleteVariant,
|
||||
} from '@/hooks/use-cliproxy';
|
||||
import type { AuthStatus, Variant } from '@/lib/api-client';
|
||||
@@ -179,6 +181,8 @@ export function CliproxyPage() {
|
||||
const { data: variantsData, isFetching } = useCliproxy();
|
||||
const setDefaultMutation = useSetDefaultAccount();
|
||||
const removeMutation = useRemoveAccount();
|
||||
const pauseMutation = usePauseAccount();
|
||||
const resumeMutation = useResumeAccount();
|
||||
const deleteMutation = useDeleteVariant();
|
||||
|
||||
// Selection state: either a provider or a variant
|
||||
@@ -216,6 +220,14 @@ export function CliproxyPage() {
|
||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-auth'] });
|
||||
};
|
||||
|
||||
const handlePauseToggle = (provider: string, accountId: string, paused: boolean) => {
|
||||
if (paused) {
|
||||
pauseMutation.mutate({ provider, accountId });
|
||||
} else {
|
||||
resumeMutation.mutate({ provider, accountId });
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectProvider = (provider: string) => {
|
||||
setSelectedProvider(provider);
|
||||
setSelectedVariant(null);
|
||||
@@ -361,6 +373,9 @@ export function CliproxyPage() {
|
||||
accountId,
|
||||
})
|
||||
}
|
||||
onPauseToggle={(accountId, paused) =>
|
||||
handlePauseToggle(selectedVariantData.provider, accountId, paused)
|
||||
}
|
||||
isRemovingAccount={removeMutation.isPending}
|
||||
/>
|
||||
) : selectedStatus ? (
|
||||
@@ -389,6 +404,9 @@ export function CliproxyPage() {
|
||||
accountId,
|
||||
})
|
||||
}
|
||||
onPauseToggle={(accountId, paused) =>
|
||||
handlePauseToggle(selectedStatus.provider, accountId, paused)
|
||||
}
|
||||
isRemovingAccount={removeMutation.isPending}
|
||||
/>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user