"use client"; import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"; interface User { id: string; email: string; name: string; isPremium: boolean; isPro: boolean; flhBalance: number; dailyMsgCount: number; preferredName?: string; coachPersona?: string; madhab?: string; coachingGoals?: string; createdAt?: string; trialEndsAt?: string; } interface StreakData { currentStreak: number; longestStreak: number; lastClaimDate: string | null; canClaim: boolean; streakFreeze: number; todayReward: number; } interface XpData { level: number; xp: number; xpToNext: number; totalXp: number; } interface AuthContextType { user: User | null; token: string | null; loading: boolean; login: (email: string, password: string) => Promise; register: (email: string, name: string, password: string) => Promise; oauthLogin: (provider: string) => Promise; logout: () => void; refreshUser: () => Promise; streak: StreakData | null; xp: XpData | null; refreshStreak: () => Promise; refreshXp: () => Promise; } const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [loading, setLoading] = useState(true); const [streak, setStreak] = useState(null); const [xp, setXp] = useState(null); useEffect(() => { const stored = localStorage.getItem("flh_token"); if (stored) { setToken(stored); fetchUser(stored); } else { setLoading(false); } // Listen for token changes (e.g., from OAuth popup) const handleStorage = (e: StorageEvent) => { if (e.key === "flh_token" && e.newValue) { setToken(e.newValue); fetchUser(e.newValue); } }; window.addEventListener("storage", handleStorage); return () => window.removeEventListener("storage", handleStorage); }, []); const fetchUser = async (t: string) => { try { const res = await fetch("/mobile/api/auth/me", { headers: { Authorization: `Bearer ${t}` }, }); if (res.ok) { const data = await res.json(); setUser(data.user); } else { localStorage.removeItem("flh_token"); setToken(null); } } catch { // ignore } finally { setLoading(false); } }; const login = useCallback(async (email: string, password: string) => { const res = await fetch("/mobile/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Login failed"); } const data = await res.json(); localStorage.setItem("flh_token", data.token); setToken(data.token); setUser(data.user); }, []); const register = useCallback(async (email: string, name: string, password: string) => { const res = await fetch("/mobile/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, name, password }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Registration failed"); } const data = await res.json(); localStorage.setItem("flh_token", data.token); setToken(data.token); setUser(data.user); }, []); const oauthLogin = useCallback(async (provider: string) => { // Open a popup window for OAuth const width = 600; const height = 700; const left = window.screenX + (window.innerWidth - width) / 2; const top = window.screenY + (window.innerHeight - height) / 2; const popup = window.open( `/mobile/api/auth/${provider}`, `oauth-${provider}`, `width=${width},height=${height},left=${left},top=${top},popup=1` ); if (!popup) { // Popup blocked — fall back to direct navigation window.location.href = `/mobile/api/auth/${provider}`; } // The popup will close itself after successful auth via the callback page }, []); const logout = useCallback(() => { localStorage.removeItem("flh_token"); setToken(null); setUser(null); setStreak(null); setXp(null); }, []); const refreshUser = useCallback(async () => { if (!token) return; await fetchUser(token); }, [token]); const refreshStreak = useCallback(async () => { if (!token) return; try { const res = await fetch("/mobile/api/gamification/streak", { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); setStreak(data); } } catch { // ignore } }, [token]); const refreshXp = useCallback(async () => { if (!token) return; try { const res = await fetch("/mobile/api/gamification/xp", { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); setXp(data); } } catch { // ignore } }, [token]); // Fetch streak and XP when token becomes available useEffect(() => { if (token && user) { refreshStreak(); refreshXp(); } }, [token, user, refreshStreak, refreshXp]); return ( {children} ); } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx; }