95 lines
2.8 KiB
TypeScript
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;
|
|
}
|