import { useState, useEffect, useCallback } from 'react'; import pb from '../lib/pocketbase'; import { trackEvent } from '../lib/analytics'; export interface AuthUser { id: string; email: string; verified: boolean; isAdmin: boolean; subscription: string; newsletter: boolean; } function recordToUser(record: { id: string; [key: string]: unknown }): AuthUser { if (typeof record.email !== 'string') { throw new Error('PocketBase record missing email field'); } return { id: record.id, email: record.email, verified: typeof record.verified === 'boolean' ? record.verified : false, isAdmin: typeof record.is_admin === 'boolean' ? record.is_admin : false, subscription: typeof record.subscription === 'string' ? record.subscription : 'free', newsletter: typeof record.newsletter === 'boolean' ? record.newsletter : false, }; } export function useAuth() { const [user, setUser] = useState(() => { if (pb.authStore.isValid && pb.authStore.record) { return recordToUser(pb.authStore.record); } return null; }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Sync with authStore changes (cross-tab, external updates) useEffect(() => { const unsubscribe = pb.authStore.onChange(() => { if (pb.authStore.isValid && pb.authStore.record) { setUser(recordToUser(pb.authStore.record)); } else { setUser(null); } }); return unsubscribe; }, []); const login = useCallback(async (email: string, password: string) => { setLoading(true); setError(null); try { const result = await pb.collection('users').authWithPassword(email, password); setUser(recordToUser(result.record)); trackEvent('Login', { method: 'email' }); } catch (err) { const msg = err instanceof Error ? err.message : 'Login failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const register = useCallback(async (email: string, password: string) => { setLoading(true); setError(null); try { await pb.collection('users').create({ email, password, passwordConfirm: password, }); // Auto-login after registration const result = await pb.collection('users').authWithPassword(email, password); setUser(recordToUser(result.record)); trackEvent('Register'); } catch (err) { const msg = err instanceof Error ? err.message : 'Registration failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const loginWithOAuth = useCallback(async (provider: string) => { setLoading(true); setError(null); try { const result = await pb.collection('users').authWithOAuth2({ provider }); setUser(recordToUser(result.record)); trackEvent('Login', { method: provider }); } catch (err) { const msg = err instanceof Error ? err.message : 'OAuth login failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const logout = useCallback(() => { trackEvent('Logout'); pb.authStore.clear(); setUser(null); }, []); const requestPasswordReset = useCallback(async (email: string) => { setLoading(true); setError(null); try { await pb.collection('users').requestPasswordReset(email); } catch (err) { const msg = err instanceof Error ? err.message : 'Password reset request failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const refreshAuth = useCallback(async () => { setLoading(true); setError(null); try { const result = await pb.collection('users').authRefresh(); setUser(recordToUser(result.record)); } catch (err) { const msg = err instanceof Error ? err.message : 'Auth refresh failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const requestVerification = useCallback(async (email: string) => { setLoading(true); setError(null); try { await pb.collection('users').requestVerification(email); } catch (err) { const msg = err instanceof Error ? err.message : 'Verification request failed'; setError(msg); throw err; } finally { setLoading(false); } }, []); const clearError = useCallback(() => { setError(null); }, []); return { user, loading, error, login, register, loginWithOAuth, logout, requestPasswordReset, requestVerification, refreshAuth, clearError, }; }