106 lines
3.0 KiB
TypeScript
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 {};
|
||
|
|
},
|
||
|
|
},
|
||
|
|
),
|
||
|
|
);
|