backsplash-app
Version:
An AI powered wallpaper app.
359 lines (307 loc) • 10.5 kB
text/typescript
import { useState, useEffect, useCallback } from "react";
import {
LicenseInfo,
LicenseValidationResult,
UsageStats,
FREE_WALLPAPERS_PER_DAY,
FREE_WALLPAPERS_PER_STYLE,
FREE_STYLE_SELECTION_LIMIT,
isStyleFree,
isStylePremium,
} from "@/types/license";
import { ServerChannels } from "@/ipc/channels/serverChannels";
interface UseLicenseReturn {
// License state
licenseInfo: LicenseInfo | null;
loading: boolean;
error: string | null;
// License actions
validateLicense: (licenseKey: string) => Promise<LicenseValidationResult>;
clearLicense: () => void;
// Usage tracking
usageStats: UsageStats | null;
canGenerateWallpaper: (style: string) => { canGenerate: boolean; reason?: string };
recordWallpaperGeneration: (style: string) => void;
getRemainingFreeGenerations: () => number;
getRemainingStyleGenerations: (style: string) => number;
// Style selection limits
canSelectMoreStyles: (currentSelectionCount: number) => { canSelect: boolean; reason?: string };
getRemainingStyleSelections: (currentSelectionCount: number) => number;
maxStyleSelections: number;
// Premium status
isPremium: boolean;
isStyleUnlocked: (style: string) => boolean;
getUpgradeMessage: (style?: string) => string;
openLicensePage: () => void;
}
export function useLicense(): UseLicenseReturn {
const [licenseInfo, setLicenseInfo] = useState<LicenseInfo | null>(null);
const [usageStats, setUsageStats] = useState<UsageStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load initial license and usage data
useEffect(() => {
const loadLicenseData = async () => {
try {
setLoading(true);
setError(null);
// Get license info from IPC
const license = (await window.electron.ipcRenderer.invoke(ServerChannels.GET_LICENSE_INFO)) as LicenseInfo;
setLicenseInfo(license);
// Get usage stats from IPC
const usage = (await window.electron.ipcRenderer.invoke(ServerChannels.GET_USAGE_STATS)) as UsageStats;
setUsageStats(usage);
} catch (err) {
console.error("Failed to load license data:", err);
setError("Failed to load license information");
} finally {
setLoading(false);
}
};
loadLicenseData();
}, []);
// Validate license key
const validateLicense = useCallback(async (licenseKey: string): Promise<LicenseValidationResult> => {
try {
setLoading(true);
setError(null);
const result = (await window.electron.ipcRenderer.invoke(
ServerChannels.VALIDATE_LICENSE,
licenseKey,
)) as LicenseValidationResult;
if (result.isValid) {
// Update local license info
const updatedLicense: LicenseInfo = {
licenseKey,
isValid: true,
isPremium: result.isPremium,
validatedAt: Date.now(),
plan: result.plan,
purchaseDate: result.purchaseDate,
};
setLicenseInfo(updatedLicense);
}
return result;
} catch (err) {
console.error("License validation failed:", err);
const failureResult: LicenseValidationResult = {
isValid: false,
isPremium: false,
message: "Failed to validate license. Please check your connection.",
plan: "free",
};
setError(failureResult.message);
return failureResult;
} finally {
setLoading(false);
}
}, []);
// Clear license
const clearLicense = useCallback(async () => {
try {
await window.electron.ipcRenderer.invoke(ServerChannels.CLEAR_LICENSE);
setLicenseInfo({
licenseKey: null,
isValid: false,
isPremium: false,
validatedAt: 0,
plan: "free",
});
setError(null);
} catch (err) {
console.error("Failed to clear license:", err);
setError("Failed to clear license");
}
}, []);
// Check if user can generate a wallpaper for the given style
const canGenerateWallpaper = useCallback(
(style: string): { canGenerate: boolean; reason?: string } => {
if (!licenseInfo || !usageStats) {
return { canGenerate: false, reason: "Loading license information..." };
}
// Premium users can generate unlimited wallpapers
if (licenseInfo.isPremium) {
return { canGenerate: true };
}
// Free users can only use free styles
if (isStylePremium(style)) {
return {
canGenerate: false,
reason: `${style} requires premium access. Upgrade to unlock all 120+ premium styles!`,
};
}
// Check daily limit for free users
if (usageStats.dailyGenerations >= FREE_WALLPAPERS_PER_DAY) {
return {
canGenerate: false,
reason: `Daily limit reached (${FREE_WALLPAPERS_PER_DAY} wallpapers). Upgrade for unlimited generation!`,
};
}
// Check per-style limit for free users
const styleUsage = usageStats.styleUsage[style] || 0;
if (styleUsage >= FREE_WALLPAPERS_PER_STYLE) {
return {
canGenerate: false,
reason: `You've used all ${FREE_WALLPAPERS_PER_STYLE} free wallpapers for ${style}. Try another free style or upgrade!`,
};
}
return { canGenerate: true };
},
[licenseInfo, usageStats],
);
// Record a wallpaper generation
const recordWallpaperGeneration = useCallback(
async (style: string) => {
if (!licenseInfo || licenseInfo.isPremium) {
return; // Don't track for premium users
}
try {
await window.electron.ipcRenderer.invoke(ServerChannels.RECORD_WALLPAPER_GENERATION, style);
// Update local usage stats
if (usageStats) {
const updatedStats = {
...usageStats,
dailyGenerations: usageStats.dailyGenerations + 1,
styleUsage: {
...usageStats.styleUsage,
[style]: (usageStats.styleUsage[style] || 0) + 1,
},
};
setUsageStats(updatedStats);
}
} catch (err) {
console.error("Failed to record wallpaper generation:", err);
}
},
[licenseInfo, usageStats],
);
// Get remaining free generations for today
const getRemainingFreeGenerations = useCallback((): number => {
if (!licenseInfo || !usageStats) {
return 0;
}
if (licenseInfo.isPremium) {
return Infinity;
}
return Math.max(0, FREE_WALLPAPERS_PER_DAY - usageStats.dailyGenerations);
}, [licenseInfo, usageStats]);
// Get remaining free generations for a specific style
const getRemainingStyleGenerations = useCallback(
(style: string): number => {
if (!licenseInfo || !usageStats) {
return 0;
}
if (licenseInfo.isPremium || isStylePremium(style)) {
return licenseInfo.isPremium ? Infinity : 0;
}
const styleUsage = usageStats.styleUsage[style] || 0;
return Math.max(0, FREE_WALLPAPERS_PER_STYLE - styleUsage);
},
[licenseInfo, usageStats],
);
// Check if style is unlocked
const isStyleUnlocked = useCallback(
(style: string): boolean => {
if (!licenseInfo) {
return false;
}
if (licenseInfo.isPremium) {
return true;
}
return isStyleFree(style);
},
[licenseInfo],
);
// Get upgrade message
const getUpgradeMessage = useCallback(
(style?: string): string => {
if (!licenseInfo) {
return "Loading...";
}
if (licenseInfo.isPremium) {
return "You have premium access!";
}
if (style && isStylePremium(style)) {
return `Upgrade to premium to unlock ${style} and 120+ other premium styles!`;
}
const remaining = getRemainingFreeGenerations();
if (remaining <= 0) {
return `Daily limit reached. Upgrade to premium for unlimited wallpaper generation!`;
}
return `${remaining} free wallpapers remaining today. Upgrade to premium for unlimited access!`;
},
[licenseInfo, getRemainingFreeGenerations],
);
// Open license/upgrade page
const openLicensePage = useCallback(async () => {
try {
console.info("[useLicense] Opening license purchase page in system browser");
const result = await (window.electron as any).license.openExternalUrl(
"https://www.backsplashai.com/api/stripe/license-purchase",
);
if (!result.success) {
console.error("[useLicense] Failed to open license page:", result.error);
}
} catch (error) {
console.error("[useLicense] Error opening license page:", error);
}
}, []);
// Check if user can select more styles
const canSelectMoreStyles = useCallback(
(currentSelectionCount: number): { canSelect: boolean; reason?: string } => {
if (!licenseInfo) {
return { canSelect: false, reason: "Loading license information..." };
}
// Premium users can select unlimited styles
if (licenseInfo.isPremium) {
return { canSelect: true };
}
// Check selection limit for free users
if (currentSelectionCount >= FREE_STYLE_SELECTION_LIMIT) {
return {
canSelect: false,
reason: `Free users can select up to ${FREE_STYLE_SELECTION_LIMIT} styles. Upgrade for unlimited selection!`,
};
}
return { canSelect: true };
},
[licenseInfo],
);
// Get remaining style selections for free users
const getRemainingStyleSelections = useCallback(
(currentSelectionCount: number): number => {
if (!licenseInfo) {
return 0;
}
if (licenseInfo.isPremium) {
return Infinity;
}
return Math.max(0, FREE_STYLE_SELECTION_LIMIT - currentSelectionCount);
},
[licenseInfo],
);
return {
// License state
licenseInfo,
loading,
error,
// License actions
validateLicense,
clearLicense,
// Usage tracking
usageStats,
canGenerateWallpaper,
recordWallpaperGeneration,
getRemainingFreeGenerations,
getRemainingStyleGenerations,
// Style selection limits
canSelectMoreStyles,
getRemainingStyleSelections,
maxStyleSelections: FREE_STYLE_SELECTION_LIMIT,
// Premium status
isPremium: licenseInfo?.isPremium || false,
isStyleUnlocked,
getUpgradeMessage,
openLicensePage,
};
}