import { Link } from 'react-router-dom' import { Clock } from 'lucide-react' import { useTrialBanner } from '@/hooks/useTrialBanner' import { useBillingStore } from '@/store/billingStore' import { cn } from '@/lib/utils' /** * Topbar billing-state pill. * * Reads `useTrialBanner()` to map subscription state → label + tone. * Returns `null` when there is nothing to display (e.g. subscription not yet * loaded). Clickable variants (expired / past_due / canceled) render as * keyboard-focusable ``s; static variants render as ``. * * Mobile: when the topbar is too narrow, the label collapses to a clock icon * with a `title` tooltip carrying the full text. */ interface PillContent { /** Full label shown on >= sm. */ label: string /** Short label for mobile (sm:hidden); typically a single token / icon. */ shortLabel?: string /** Tailwind classes applied to the pill (color tokens). */ toneClass: string /** When set, render as a clickable Link to this route. */ href?: string /** Extra emphasis (used by `urgent` to differentiate from `warning`). */ emphasized?: boolean } const BASE_CLASS = 'trial-pill inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-colors whitespace-nowrap' export function TrialPill() { const { stage, daysRemaining } = useTrialBanner() const planBilling = useBillingStore((s) => s.planBilling) const content = resolveContent(stage, daysRemaining, planBilling?.display_name ?? null) if (!content) return null const className = cn( BASE_CLASS, content.toneClass, content.emphasized && 'font-semibold', content.href && 'cursor-pointer hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-bg-sidebar', ) const inner = ( <> {content.label} ) if (content.href) { return ( {inner} ) } return ( {inner} ) } function resolveContent( stage: ReturnType['stage'], daysRemaining: number | null, paidDisplayName: string | null, ): PillContent | null { switch (stage) { case null: return null case 'pristine': { const days = daysRemaining ?? 0 return { label: `Pro trial · ${days}d`, toneClass: 'text-info bg-info-dim', } } case 'warning': { const days = daysRemaining ?? 0 return { label: `Pro trial · ${days}d`, toneClass: 'text-warning bg-warning-dim', } } case 'urgent': return { label: 'Pro trial · today', toneClass: 'text-warning bg-warning-dim', emphasized: true, } case 'expired': return { label: 'Trial expired — pick a plan', toneClass: 'text-danger bg-danger-dim', href: '/account/billing/select-plan', } case 'paid': return { label: paidDisplayName ?? 'Pro', toneClass: 'text-muted-foreground bg-elevated', } case 'complimentary': return { label: 'Complimentary Pro', toneClass: 'text-accent bg-accent-dim', } case 'past_due': return { label: 'Payment failed — update card', toneClass: 'text-warning bg-warning-dim', href: '/account/billing', } case 'canceled': return { label: 'Reactivate', toneClass: 'text-warning bg-warning-dim', href: '/account/billing/select-plan', } default: { const _exhaustive: never = stage void _exhaustive return null } } } export default TrialPill