From 2a3632e5a5efa825cdee0e03ffc26410b3c42ee9 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 14 Apr 2026 21:35:07 -0400 Subject: [PATCH] fix(accounts): show one codex plan badge --- .../account/flow-viz/account-card.tsx | 136 +++++++-------- .../account/shared/account-surface-card.tsx | 165 +++++++++--------- .../setup/wizard/steps/account-step.tsx | 24 ++- .../setup/wizard/steps/variant-step.tsx | 26 ++- ui/src/lib/account-identity.ts | 26 +++ .../account/flow-viz/account-card.test.tsx | 9 +- .../shared/account-surface-card.test.tsx | 6 +- 7 files changed, 226 insertions(+), 166 deletions(-) diff --git a/ui/src/components/account/flow-viz/account-card.tsx b/ui/src/components/account/flow-viz/account-card.tsx index dc10aea9..a07958fa 100644 --- a/ui/src/components/account/flow-viz/account-card.tsx +++ b/ui/src/components/account/flow-viz/account-card.tsx @@ -6,6 +6,7 @@ import { AccountSurfaceCard } from '@/components/account/shared/account-surface- import { QuotaTooltipContent } from '@/components/shared/quota-tooltip-content'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { getCodexIdentityBadge, type CodexIdentityBadge } from '@/lib/account-identity'; import { cn, formatQuotaPercent, @@ -88,68 +89,56 @@ function getCompactQuotaColor(percentage: number) { return 'bg-red-500'; } -function getCodexPlanAudience(quota: unknown): AccountAudience { +function getCodexQuotaBadge(quota: unknown): CodexIdentityBadge { if (!quota || typeof quota !== 'object' || !('planType' in quota)) { - return 'unknown'; + return { audience: 'unknown', label: null }; } const planType = (quota as { planType?: unknown }).planType; if (typeof planType !== 'string' || planType.trim().length === 0) { - return 'unknown'; + return { audience: 'unknown', label: null }; } - if (planType === 'team') return 'business'; - if (planType === 'free') return 'free'; - if (planType === 'plus' || planType === 'pro') return 'personal'; - return 'unknown'; + if (planType === 'team') { + return { audience: 'business', label: 'Business' }; + } + + if (planType === 'free') { + return { audience: 'free', label: 'Free' }; + } + + if (planType === 'plus') { + return { audience: 'personal', label: 'Plus' }; + } + + if (planType === 'pro') { + return { audience: 'personal', label: 'Pro' }; + } + + return { audience: 'unknown', label: null }; } -function resolveCodexAudience( - identityAudience: AccountAudience, - planAudience: AccountAudience -): AccountAudience { - if (planAudience === 'unknown') { - return identityAudience; +function resolveCodexBadge( + identityBadge: CodexIdentityBadge, + quotaBadge: CodexIdentityBadge +): CodexIdentityBadge { + if (!quotaBadge.label) { + return identityBadge; } - if (planAudience === 'business') { - return 'business'; + if ( + quotaBadge.label === 'Business' || + quotaBadge.label === 'Plus' || + quotaBadge.label === 'Pro' + ) { + return quotaBadge; } - if (identityAudience === 'business') { - return 'business'; + if (quotaBadge.label === 'Free' && identityBadge.label && identityBadge.label !== 'Free') { + return identityBadge; } - if (planAudience === 'personal') { - return 'personal'; - } - - if (planAudience === 'free') { - return identityAudience === 'unknown' || identityAudience === 'free' - ? 'free' - : identityAudience; - } - - return identityAudience; -} - -function getCodexPlanDetailLabel(quota: unknown): string | null { - if (!quota || typeof quota !== 'object' || !('planType' in quota)) { - return null; - } - - const planType = (quota as { planType?: unknown }).planType; - if (typeof planType !== 'string' || planType.trim().length === 0) { - return null; - } - - if (planType === 'free' || planType === 'team') { - return null; - } - - return planType === 'plus' || planType === 'pro' - ? planType[0].toUpperCase() + planType.slice(1) - : null; + return quotaBadge; } function getVariantDetailLabel( @@ -160,9 +149,32 @@ function getVariantDetailLabel( }, quota?: unknown ) { - return ( - variant.detailLabel ?? variant.compactDetailLabel ?? getCodexPlanDetailLabel(quota) ?? null - ); + return resolveCodexBadge( + getCodexIdentityBadge({ + audience: variant.audience, + detailLabel: variant.detailLabel, + compactDetailLabel: variant.compactDetailLabel, + }), + getCodexQuotaBadge(quota) + ).label; +} + +function getVariantBadgeAudience( + variant: { + audience: AccountAudience; + detailLabel?: string | null; + compactDetailLabel?: string | null; + }, + quota?: unknown +) { + return resolveCodexBadge( + getCodexIdentityBadge({ + audience: variant.audience, + detailLabel: variant.detailLabel, + compactDetailLabel: variant.compactDetailLabel, + }), + getCodexQuotaBadge(quota) + ).audience; } function getVariantCompactDetailLabel( @@ -173,9 +185,7 @@ function getVariantCompactDetailLabel( }, quota?: unknown ) { - return ( - variant.compactDetailLabel ?? variant.detailLabel ?? getCodexPlanDetailLabel(quota) ?? null - ); + return getVariantDetailLabel(variant, quota); } function getDetailedAudienceLabel(audience: AccountAudience): string | null { @@ -191,7 +201,7 @@ function getVariantAudience( }, quota?: unknown ) { - return resolveCodexAudience(variant.audience, getCodexPlanAudience(quota)); + return getVariantBadgeAudience(variant, quota); } function getVariantInlineLabel( @@ -204,14 +214,7 @@ function getVariantInlineLabel( }, quota?: unknown ) { - const detailLabel = getVariantDetailLabel(variant, quota); - const audience = getVariantAudience(variant, quota); - const audienceLabel = - variant.audience === audience - ? (variant.audienceLabel ?? getDetailedAudienceLabel(audience)) - : getDetailedAudienceLabel(audience); - const composedLabel = [audienceLabel, detailLabel].filter(Boolean).join(' · '); - return composedLabel || variant.inlineLabel || null; + return getVariantDetailLabel(variant, quota) || variant.inlineLabel || null; } function getVariantMarkerLabel( @@ -227,8 +230,7 @@ function getVariantMarkerLabel( const audience = getVariantAudience(variant, quota); const compactDetailLabel = getVariantCompactDetailLabel(variant, quota); if (audience === 'business') { - const businessVariantCount = audienceCounts.get('business') ?? 0; - return businessVariantCount > 1 && compactDetailLabel ? compactDetailLabel : 'Biz'; + return 'Biz'; } if (audience === 'free') { return compactDetailLabel ?? 'Free'; @@ -237,11 +239,7 @@ function getVariantMarkerLabel( return compactDetailLabel ?? 'Pers'; } - const normalizedFallback = - compactDetailLabel?.trim() || - getDetailedAudienceLabel(audience) || - variant.audienceLabel?.trim() || - variant.detailLabel?.trim(); + const normalizedFallback = compactDetailLabel?.trim() || getDetailedAudienceLabel(audience); return normalizedFallback?.[0]?.toUpperCase() ?? '?'; } diff --git a/ui/src/components/account/shared/account-surface-card.tsx b/ui/src/components/account/shared/account-surface-card.tsx index 3de92fe5..ad61b231 100644 --- a/ui/src/components/account/shared/account-surface-card.tsx +++ b/ui/src/components/account/shared/account-surface-card.tsx @@ -2,7 +2,11 @@ import type { ReactNode } from 'react'; import { Badge } from '@/components/ui/badge'; import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; import type { UnifiedQuotaResult } from '@/hooks/use-cliproxy-stats'; -import { formatAccountVariantPart, getAccountIdentityPresentation } from '@/lib/account-identity'; +import { + getAccountIdentityPresentation, + getCodexIdentityBadge, + type CodexIdentityBadge, +} from '@/lib/account-identity'; import { cn } from '@/lib/utils'; import { Pause, Star, User } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -66,13 +70,6 @@ function getCompactAudienceBadgeLabel(audience: AccountAudience, t: (key: string return '?'; } -function getDetailedAudienceLabel(audience: AccountAudience): string | null { - if (audience === 'business') return 'Business'; - if (audience === 'free') return 'Free'; - if (audience === 'personal') return 'Personal'; - return null; -} - function getCompactDetailBadgeClass(audience: AccountAudience) { if (audience === 'business') { return 'border-sky-500/30 bg-sky-500/10 text-sky-700 dark:border-sky-400/30 dark:bg-sky-500/15 dark:text-sky-200'; @@ -102,56 +99,51 @@ function resolveEffectiveTier( return tier; } -function getCodexPlanAudience(quota: UnifiedQuotaResult | undefined): AccountAudience { +function getCodexQuotaBadge(quota: UnifiedQuotaResult | undefined): CodexIdentityBadge { if (!quota || !('planType' in quota) || !quota.planType) { - return 'unknown'; + return { audience: 'unknown', label: null }; } - if (quota.planType === 'team') return 'business'; - if (quota.planType === 'free') return 'free'; - if (quota.planType === 'plus' || quota.planType === 'pro') return 'personal'; - return 'unknown'; + if (quota.planType === 'team') { + return { audience: 'business', label: 'Business' }; + } + + if (quota.planType === 'free') { + return { audience: 'free', label: 'Free' }; + } + + if (quota.planType === 'plus') { + return { audience: 'personal', label: 'Plus' }; + } + + if (quota.planType === 'pro') { + return { audience: 'personal', label: 'Pro' }; + } + + return { audience: 'unknown', label: null }; } -function resolveCodexAudience( - identityAudience: AccountAudience, - planAudience: AccountAudience -): AccountAudience { - if (planAudience === 'unknown') { - return identityAudience; +function resolveCodexBadge( + identityBadge: CodexIdentityBadge, + quotaBadge: CodexIdentityBadge +): CodexIdentityBadge { + if (!quotaBadge.label) { + return identityBadge; } - if (planAudience === 'business') { - return 'business'; + if ( + quotaBadge.label === 'Business' || + quotaBadge.label === 'Plus' || + quotaBadge.label === 'Pro' + ) { + return quotaBadge; } - if (identityAudience === 'business') { - return 'business'; + if (quotaBadge.label === 'Free' && identityBadge.label && identityBadge.label !== 'Free') { + return identityBadge; } - if (planAudience === 'personal') { - return 'personal'; - } - - if (planAudience === 'free') { - return identityAudience === 'unknown' || identityAudience === 'free' - ? 'free' - : identityAudience; - } - - return identityAudience; -} - -function getCodexPlanDetailLabel(quota: UnifiedQuotaResult | undefined): string | null { - if (!quota || !('planType' in quota) || !quota.planType) { - return null; - } - - if (quota.planType === 'free' || quota.planType === 'team') { - return null; - } - - return formatAccountVariantPart(quota.planType); + return quotaBadge; } export function AccountSurfaceCard({ @@ -182,22 +174,10 @@ export function AccountSurfaceCard({ const title = displayEmail || identity.email || accountId; const normalizedProvider = provider.toLowerCase(); const effectiveTier = resolveEffectiveTier(tier, quota); - const codexPlanAudience = - normalizedProvider === 'codex' ? getCodexPlanAudience(quota) : 'unknown'; - const codexPlanDetailLabel = - normalizedProvider === 'codex' ? getCodexPlanDetailLabel(quota) : null; - const effectiveAudience = + const effectiveCodexBadge = normalizedProvider === 'codex' - ? resolveCodexAudience(identity.audience, codexPlanAudience) - : identity.audience !== 'unknown' - ? identity.audience - : codexPlanAudience; - const effectiveAudienceLabel = - identity.audience === effectiveAudience - ? (identity.audienceLabel ?? getDetailedAudienceLabel(effectiveAudience)) - : getDetailedAudienceLabel(effectiveAudience); - const resolvedDetailLabel = identity.detailLabel ?? codexPlanDetailLabel; - const resolvedCompactDetailLabel = identity.compactDetailLabel ?? codexPlanDetailLabel; + ? resolveCodexBadge(getCodexIdentityBadge(identity), getCodexQuotaBadge(quota)) + : null; const showTierBadge = (normalizedProvider === 'agy' || normalizedProvider === 'antigravity' || @@ -218,26 +198,38 @@ export function AccountSurfaceCard({ {effectiveTier} )} - {effectiveAudienceLabel && ( - + {effectiveCodexBadge.label} + + ) + : identity.audienceLabel && ( + + {getCompactAudienceBadgeLabel(identity.audience, t)} + )} - > - {getCompactAudienceBadgeLabel(effectiveAudience, t)} - - )} - {resolvedCompactDetailLabel && ( + {normalizedProvider !== 'codex' && identity.compactDetailLabel && ( - {resolvedCompactDetailLabel} + {identity.compactDetailLabel} )} {paused && ( @@ -295,20 +287,31 @@ export function AccountSurfaceCard({ {title} {isCompact && (compactMetaBadges ?? defaultCompactMetaBadges)} - {!isCompact && effectiveAudienceLabel && ( + {!isCompact && normalizedProvider === 'codex' && effectiveCodexBadge?.label && ( - {effectiveAudienceLabel} + {effectiveCodexBadge.label} )} - {!isCompact && resolvedDetailLabel && ( + {!isCompact && normalizedProvider !== 'codex' && identity.audienceLabel && ( + + {identity.audienceLabel} + + )} + {!isCompact && normalizedProvider !== 'codex' && identity.detailLabel && ( - {resolvedDetailLabel} + {identity.detailLabel} )} {!isCompact && isDefault && ( diff --git a/ui/src/components/setup/wizard/steps/account-step.tsx b/ui/src/components/setup/wizard/steps/account-step.tsx index 7e89b232..dc9b5930 100644 --- a/ui/src/components/setup/wizard/steps/account-step.tsx +++ b/ui/src/components/setup/wizard/steps/account-step.tsx @@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ChevronRight, ArrowLeft, User, ExternalLink } from 'lucide-react'; -import { getAccountIdentityPresentation } from '@/lib/account-identity'; +import { getAccountIdentityPresentation, getCodexIdentityBadge } from '@/lib/account-identity'; import { cn } from '@/lib/utils'; import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; import type { AccountStepProps } from '../types'; @@ -30,6 +30,8 @@ export function AccountStep({
{accounts.map((acc) => { const identity = getAccountIdentityPresentation(acc.id, acc.email, acc.tokenFile); + const codexBadge = + acc.provider?.toLowerCase() === 'codex' ? getCodexIdentityBadge(identity) : null; return (