import { useBillingStore } from '@/store/billingStore' export type TrialBannerStage = | 'pristine' | 'warning' | 'urgent' | 'expired' | 'complimentary' | 'paid' | 'past_due' | 'canceled' export interface TrialBannerResult { stage: TrialBannerStage | null daysRemaining: number | null } const MS_PER_DAY = 24 * 60 * 60 * 1000 /** * Derives the trial-banner display stage from the current subscription. * * Returns `{ stage: null, daysRemaining: null }` when subscription data is * not yet loaded — this prevents the banner flickering on initial render. * * Subscribes to `useBillingStore` so updates from `refetch()` after a Stripe * checkout propagate automatically. */ export function useTrialBanner(): TrialBannerResult { const subscription = useBillingStore((state) => state.subscription) if (!subscription) { return { stage: null, daysRemaining: null } } switch (subscription.status) { case 'complimentary': return { stage: 'complimentary', daysRemaining: null } case 'active': return { stage: 'paid', daysRemaining: null } case 'past_due': return { stage: 'past_due', daysRemaining: null } case 'canceled': return { stage: 'canceled', daysRemaining: null } case 'trialing': { const end = subscription.current_period_end ? new Date(subscription.current_period_end).getTime() : null if (end === null || Number.isNaN(end)) { // Trialing without a period end is malformed; treat as expired so the // upgrade prompt still surfaces rather than silently swallowing it. return { stage: 'expired', daysRemaining: null } } const now = Date.now() if (end <= now) { return { stage: 'expired', daysRemaining: 0 } } const msRemaining = end - now // Use fractional days for stage thresholds so exactly 24h remaining // sits on the warning side (1.0), not urgent. The displayed integer // countdown still uses Math.ceil so "0.5 days" renders as "1 day". const fractionalDays = msRemaining / MS_PER_DAY const daysRemaining = Math.ceil(fractionalDays) // Spec thresholds: // >3 days remaining → pristine // 1–3 days → warning (inclusive of exactly 1) // <1 day → urgent let stage: TrialBannerStage = 'pristine' if (fractionalDays < 1) stage = 'urgent' else if (fractionalDays <= 3) stage = 'warning' return { stage, daysRemaining } } case 'incomplete': // Not in the spec's matrix; surface as null so the banner stays hidden // until checkout actually resolves. return { stage: null, daysRemaining: null } default: { // Defensive fallthrough for unknown statuses — keep the banner hidden. const _exhaustive: never = subscription.status as never void _exhaustive return { stage: null, daysRemaining: null } } } } export default useTrialBanner