motorstate-frontend/components/billing/subscription-context.tsx
2025-12-26 13:12:37 +00:00

95 lines
2.8 KiB
TypeScript

'use client';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
type SubscriptionState = {
active: boolean; // true if paid subscription is active
trialEndsAt?: string | null; // ISO string when the trial ends
};
type SubscriptionContextValue = {
state: SubscriptionState;
isTrialActive: boolean;
daysLeftInTrial: number;
isEntitled: boolean; // active OR valid trial
startTrial: (days?: number) => void;
endTrial: () => void;
purchase: () => void; // placeholder “pay” success
cancel: () => void; // cancels paid sub (trial untouched)
reset: () => void; // dev helper
};
const STORAGE_KEYS = {
ACTIVE: 'SUB_ACTIVE',
TRIAL_END: 'SUB_TRIAL_ENDS_AT',
};
const SubscriptionContext = createContext<SubscriptionContextValue | null>(null);
export function SubscriptionProvider({ children }: { children: React.ReactNode }) {
const [active, setActive] = useState<boolean>(false);
const [trialEndsAt, setTrialEndsAt] = useState<string | null>(null);
// Load from localStorage
useEffect(() => {
try {
const a = localStorage.getItem(STORAGE_KEYS.ACTIVE);
const t = localStorage.getItem(STORAGE_KEYS.TRIAL_END);
setActive(a === '1');
setTrialEndsAt(t || null);
} catch {}
}, []);
// Persist changes
useEffect(() => {
try {
localStorage.setItem(STORAGE_KEYS.ACTIVE, active ? '1' : '0');
if (trialEndsAt) localStorage.setItem(STORAGE_KEYS.TRIAL_END, trialEndsAt);
else localStorage.removeItem(STORAGE_KEYS.TRIAL_END);
} catch {}
}, [active, trialEndsAt]);
const now = Date.now();
const trialMsLeft = useMemo(() => {
if (!trialEndsAt) return 0;
const end = new Date(trialEndsAt).getTime();
return Math.max(0, end - now);
}, [trialEndsAt, now]);
const daysLeftInTrial = Math.ceil(trialMsLeft / (1000 * 60 * 60 * 24));
const isTrialActive = trialMsLeft > 0;
const isEntitled = active || isTrialActive;
const startTrial = (days = 7) => {
const end = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
setTrialEndsAt(end);
};
const endTrial = () => setTrialEndsAt(null);
// Placeholder purchase: mark as active
const purchase = () => setActive(true);
// Cancel paid sub (keeps trial data as-is)
const cancel = () => setActive(false);
const reset = () => {
setActive(false);
setTrialEndsAt(null);
};
return (
<SubscriptionContext.Provider
value={{ state: { active, trialEndsAt }, isTrialActive, daysLeftInTrial, isEntitled, startTrial, endTrial, purchase, cancel, reset }}
>
{children}
</SubscriptionContext.Provider>
);
}
export function useSubscription() {
const ctx = useContext(SubscriptionContext);
if (!ctx) throw new Error('useSubscription must be used within SubscriptionProvider');
return ctx;
}