/** * Global state management with Zustand */ import { create } from 'zustand' import { api } from './api' interface User { id: number email: string name: string | null is_admin?: boolean is_verified?: boolean } interface Domain { id: number name: string status: string is_available: boolean registrar: string | null expiration_date: string | null deletion_date?: string | null notify_on_available: boolean created_at: string last_checked: string | null status_checked_at?: string | null status_source?: string | null } interface Subscription { tier: string tier_name?: string status?: string domain_limit: number domains_used: number portfolio_limit?: number check_frequency?: string history_days?: number features?: { email_alerts: boolean priority_alerts: boolean full_whois: boolean expiration_tracking: boolean domain_valuation: boolean market_insights: boolean api_access: boolean webhooks: boolean bulk_tools: boolean seo_metrics: boolean } } interface AppState { // Auth user: User | null isAuthenticated: boolean isLoading: boolean // Domains domains: Domain[] domainsTotal: number domainsPage: number // Subscription subscription: Subscription | null // Actions login: (email: string, password: string) => Promise register: (email: string, password: string, name?: string, ref?: string) => Promise logout: () => void checkAuth: () => Promise fetchDomains: (page?: number) => Promise addDomain: (name: string) => Promise deleteDomain: (id: number) => Promise refreshDomain: (id: number) => Promise updateDomain: (id: number, updates: Partial) => void fetchSubscription: () => Promise } export const useStore = create((set, get) => ({ // Initial state user: null, isAuthenticated: false, isLoading: true, domains: [], domainsTotal: 0, domainsPage: 1, subscription: null, // Auth actions login: async (email, password) => { await api.login(email, password) const user = await api.getMe() set({ user, isAuthenticated: true, isLoading: false }) // Fetch user data (only once after login) await Promise.all([ get().fetchDomains(), get().fetchSubscription() ]) }, register: async (email, password, name, ref) => { await api.register(email, password, name, ref) // Note: No auto-login after registration // User should verify email first (verification email is sent) // They can then log in manually via the login page }, logout: async () => { try { // Call backend to clear HttpOnly cookie await api.logout() } catch { // Continue with client-side cleanup even if backend call fails } // Clear all client-side state set({ user: null, isAuthenticated: false, domains: [], subscription: null, isLoading: false, }) // Clear ALL client-side storage if (typeof window !== 'undefined') { // Clear localStorage try { localStorage.clear() } catch { /* ignore */ } // Clear sessionStorage try { sessionStorage.clear() } catch { /* ignore */ } // Clear any cookies we can access from JS (non-HttpOnly) document.cookie.split(';').forEach(cookie => { const name = cookie.split('=')[0].trim() if (name) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/` document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.pounce.ch` } }) // Force redirect to landing page with cache-busting window.location.href = '/?logout=' + Date.now() } }, checkAuth: async () => { // Skip if already authenticated and have data (prevents redundant fetches) const state = get() if (state.isAuthenticated && state.user && state.subscription) { set({ isLoading: false }) return } set({ isLoading: true }) try { // Cookie-based auth: if cookie is present and valid, /auth/me succeeds. const user = await api.getMe() set({ user, isAuthenticated: true }) // Fetch in parallel for speed await Promise.all([ get().fetchDomains(), get().fetchSubscription() ]) } catch { set({ user: null, isAuthenticated: false }) } finally { set({ isLoading: false }) } }, // Domain actions fetchDomains: async (page = 1) => { try { const response = await api.getDomains(page) set({ domains: response.domains, domainsTotal: response.total, domainsPage: response.page, }) } catch (error) { console.error('Failed to fetch domains:', error) } }, addDomain: async (name) => { await api.addDomain(name) await get().fetchDomains(get().domainsPage) await get().fetchSubscription() }, deleteDomain: async (id) => { await api.deleteDomain(id) await get().fetchDomains(get().domainsPage) await get().fetchSubscription() }, refreshDomain: async (id) => { const updated = await api.refreshDomain(id) const domains = get().domains.map((d) => d.id === id ? { ...d, ...updated } : d ) set({ domains }) }, updateDomain: (id, updates) => { const domains = get().domains.map((d) => d.id === id ? { ...d, ...updates } : d ) set({ domains }) }, // Subscription actions fetchSubscription: async () => { try { const sub = await api.getSubscription() set({ subscription: { tier: sub.tier, tier_name: sub.tier_name, domain_limit: sub.domain_limit, domains_used: sub.domains_used, portfolio_limit: sub.portfolio_limit, check_frequency: sub.check_frequency, history_days: sub.history_days, features: sub.features, }, }) } catch { // User might not have subscription } }, }))