UNPKG

backsplash-app

Version:
359 lines (307 loc) 10.5 kB
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, }; }