Files
TalkEdit/frontend/src/store/licenseStore.ts

106 lines
3.0 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export interface LicensePayload {
license_id: string;
customer_email: string;
tier: 'pro' | 'business';
features: string[];
issued_at: number;
expires_at: number;
max_activations: number;
}
export interface TrialState {
started_at: number;
}
export type AppStatus =
| { tag: 'Licensed'; license: LicensePayload }
| { tag: 'Trial'; days_remaining: number; started_at: number }
| { tag: 'Expired' };
interface LicenseState {
status: AppStatus | null;
isLoaded: boolean;
showDialog: boolean;
canEdit: boolean;
}
interface LicenseActions {
setStatus: (status: AppStatus | null) => void;
setShowDialog: (show: boolean) => void;
checkStatus: () => Promise<void>;
activateLicense: (key: string) => Promise<boolean>;
deactivateLicense: () => Promise<void>;
hasFeature: (feature: string) => Promise<boolean>;
}
export const useLicenseStore = create<LicenseState & LicenseActions>()(
persist(
(set) => ({
status: null,
isLoaded: false,
showDialog: false,
canEdit: true,
setStatus: (status) => {
const canEdit = status?.tag === 'Licensed' || status?.tag === 'Trial';
set({ status, isLoaded: true, canEdit });
},
setShowDialog: (show) => set({ showDialog: show }),
checkStatus: async () => {
try {
const status = await window.electronAPI?.getAppStatus();
const canEdit = status?.tag === 'Licensed' || status?.tag === 'Trial';
set({ status: status || { tag: 'Expired' }, isLoaded: true, canEdit });
} catch {
set({ status: { tag: 'Expired' }, isLoaded: true, canEdit: false });
}
},
activateLicense: async (key: string): Promise<boolean> => {
try {
const license = await window.electronAPI?.activateLicense(key);
if (!license) return false;
set({ status: { tag: 'Licensed', license }, showDialog: false, canEdit: true });
return true;
} catch {
return false;
}
},
deactivateLicense: async () => {
try {
await window.electronAPI?.deactivateLicense();
const s = await window.electronAPI?.getAppStatus();
const canEdit = s?.tag === 'Licensed' || s?.tag === 'Trial';
set({ status: s || { tag: 'Expired' }, isLoaded: true, canEdit });
} catch {
set({ status: { tag: 'Expired' }, isLoaded: true, canEdit: false });
}
},
hasFeature: async (feature: string): Promise<boolean> => {
try {
return await window.electronAPI?.hasLicenseFeature(feature) || false;
} catch {
return false;
}
},
}),
{
name: 'talkedit-license',
partialize: (state) => {
// Only persist Licensed status (trial is ephemeral)
if (state.status?.tag === 'Licensed') {
return { status: state.status };
}
return {};
},
},
),
);