import { useCallback } from "preact/hooks"; import { useT, useI18n } from "../../../shared/i18n/context"; import type { TranslationKey } from "../../../shared/i18n/translations"; import { formatNumber, formatResetTime, formatWindowDuration } from "../../../shared/utils/format"; import type { Account, ProxyEntry } from "../../../shared/types"; const avatarColors = [ ["bg-purple-100 dark:bg-[#2a1a3f]", "text-purple-600 dark:text-purple-400"], ["bg-amber-100 dark:bg-[#3d2c16]", "text-amber-600 dark:text-amber-500"], ["bg-blue-100 dark:bg-[#1a2a3f]", "text-blue-600 dark:text-blue-400"], ["bg-emerald-100 dark:bg-[#112a1f]", "text-emerald-600 dark:text-emerald-400"], ["bg-red-100 dark:bg-[#3f1a1a]", "text-red-600 dark:text-red-400"], ]; const statusStyles: Record = { active: [ "bg-green-100 text-green-700 border-green-200 dark:bg-[#11281d] dark:text-primary dark:border-[#1a442e]", "active", ], expired: [ "bg-red-100 text-red-600 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800/30", "expired", ], rate_limited: [ "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800/30", "rateLimited", ], refreshing: [ "bg-blue-100 text-blue-600 border-blue-200 dark:bg-blue-900/20 dark:text-blue-400 dark:border-blue-800/30", "refreshing", ], disabled: [ "bg-slate-100 text-slate-500 border-slate-200 dark:bg-slate-800/30 dark:text-slate-400 dark:border-slate-700/30", "disabled", ], banned: [ "bg-rose-100 text-rose-700 border-rose-300 dark:bg-rose-900/30 dark:text-rose-400 dark:border-rose-800/40", "banned", ], }; interface AccountCardProps { account: Account; index: number; onDelete: (id: string) => Promise; proxies?: ProxyEntry[]; onProxyChange?: (accountId: string, proxyId: string) => void; selected?: boolean; onToggleSelect?: (id: string) => void; } export function AccountCard({ account, index, onDelete, proxies, onProxyChange, selected, onToggleSelect }: AccountCardProps) { const t = useT(); const { lang } = useI18n(); const email = account.email || "Unknown"; const initial = email.charAt(0).toUpperCase(); const [bgColor, textColor] = avatarColors[index % avatarColors.length]; const usage = account.usage || {}; const requests = usage.request_count ?? 0; const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0); const winRequests = usage.window_request_count ?? 0; const winTokens = (usage.window_input_tokens ?? 0) + (usage.window_output_tokens ?? 0); const plan = account.planType || t("freeTier"); const windowSec = account.quota?.rate_limit?.limit_window_seconds; const windowDur = windowSec ? formatWindowDuration(windowSec, lang === "zh") : null; const [statusCls, statusKey] = statusStyles[account.status] || statusStyles.disabled; const handleDelete = useCallback(async () => { if (!confirm(t("removeConfirm"))) return; const err = await onDelete(account.id); if (err) alert(err); }, [account.id, onDelete, t]); // Quota — primary window const q = account.quota; const rl = q?.rate_limit; const pct = rl?.used_percent != null ? Math.round(rl.used_percent) : null; const barColor = pct == null ? "bg-primary" : pct >= 90 ? "bg-red-500" : pct >= 60 ? "bg-amber-500" : "bg-primary"; const pctColor = pct == null ? "text-primary" : pct >= 90 ? "text-red-500" : pct >= 60 ? "text-amber-600 dark:text-amber-500" : "text-primary"; const resetAt = rl?.reset_at ? formatResetTime(rl.reset_at, lang === "zh") : null; // Quota — secondary window (e.g. weekly) const srl = q?.secondary_rate_limit; const sPct = srl?.used_percent != null ? Math.round(srl.used_percent) : null; const sBarColor = sPct == null ? "bg-indigo-500" : sPct >= 90 ? "bg-red-500" : sPct >= 60 ? "bg-amber-500" : "bg-indigo-500"; const sPctColor = sPct == null ? "text-indigo-500" : sPct >= 90 ? "text-red-500" : sPct >= 60 ? "text-amber-600 dark:text-amber-500" : "text-indigo-500"; const sResetAt = srl?.reset_at ? formatResetTime(srl.reset_at, lang === "zh") : null; const sWindowSec = srl?.limit_window_seconds; const sWindowDur = sWindowSec ? formatWindowDuration(sWindowSec, lang === "zh") : null; const handleToggle = useCallback(() => { onToggleSelect?.(account.id); }, [account.id, onToggleSelect]); return (
{/* Header */}
{onToggleSelect && ( )}
{initial}

{email}

{plan} {windowDur && ( {windowDur} )}

{t(statusKey as TranslationKey)}
{/* Stats */}
{t("windowRequests")} {formatNumber(winRequests)}
{t("windowTokens")} {formatNumber(winTokens)}
{t("totalAll")} {formatNumber(requests)} req · {formatNumber(tokens)} tok
{/* Proxy selector */} {proxies && onProxyChange && (
{t("proxyAssignment")}
)} {/* Quota bars */} {(rl || srl) && (
{/* Primary window */} {rl && (
{t("rateLimit")} {windowDur && ( ({windowDur}) )} {rl.limit_reached ? ( {t("limitReached")} ) : pct != null ? ( {pct}% {t("used")} ) : ( {t("ok")} )}
{pct != null && (
)} {resetAt && (

{t("resetsAt")} {resetAt}

)}
)} {/* Secondary window (e.g. weekly) */} {srl && (
{t("secondaryRateLimit")} {sWindowDur && ( ({sWindowDur}) )} {srl.limit_reached ? ( {t("limitReached")} ) : sPct != null ? ( {sPct}% {t("used")} ) : ( {t("ok")} )}
{sPct != null && (
)} {sResetAt && (

{t("resetsAt")} {sResetAt}

)}
)}
)}
); }