import React, { createContext, useContext, useState, useEffect } from 'react'; import { api } from '../api'; import { authLink } from '../lib/mAuth'; export interface UserData { username: string; fullName?: string; full_name?: string; email: string; phone: string; citizen_id: string; [key: string]: any; } interface AuthContextType { user: UserData | null; token: string | null; login: () => Promise; checkAuth: () => Promise; logout: () => void; updateUser: (userData: UserData) => void; isAuthenticated: boolean; isLoading: boolean; } const AuthContext = createContext(null); export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [isLoading, setIsLoading] = useState(true); const updateUser = (newUserData: UserData) => { setUser(newUserData); localStorage.setItem('auth_user', JSON.stringify(newUserData)); }; /** * Calls the backend check-in endpoint to sync the mAuth user * with the local database and get the full user profile. */ const handleCheckIn = async (authToken: string) => { try { const currentOrigin = localStorage.getItem('mworld_origin') || window.location.origin; const res = await api.post('/user/check-in', { mauth_token: authToken, }); if (res.data?.success && res.data.user) { setUser(res.data.user); localStorage.setItem('auth_user', JSON.stringify(res.data.user)); return res.data.user; } } catch (err) { console.error("Check-in failed:", err); logout(); } return null; }; // Public compatibility method for generated onboarding and role guards. // It always reuses the scaffolded check-in path instead of inventing a // second token-validation flow in individual pages. const checkAuth = async (): Promise => { const authToken = token || localStorage.getItem('user_auth_token') || localStorage.getItem('auth_token'); if (!authToken) return null; setToken(authToken); return handleCheckIn(authToken); }; /** * mAuth Native Initializer: * 1. Check for user_auth_token in localStorage (set by /auth route). * 2. If present, call backend check-in to validate & sync user data. * The backend handles mAuth token verification — we must NOT call * fetchUserData here too, as that would consume/invalidate the token * before the backend can use it. * 3. If absent or invalid, clear auth state so the login button is shown. */ useEffect(() => { const initializer = async () => { const origin = window.location.origin.toString(); if (!localStorage.getItem('mworld_origin')) { localStorage.setItem('mworld_origin', origin); } const uat = localStorage.getItem('user_auth_token'); if (uat) { setToken(uat); // Restore cached user data immediately for fast UI const savedUser = localStorage.getItem('auth_user'); if (savedUser && savedUser !== 'undefined') { try { setUser(JSON.parse(savedUser)); } catch { /* ignore parse errors */ } } // Validate token & sync user via backend check-in // (backend calls mAuth internally to verify the token) const freshUser = await handleCheckIn(uat); if (!freshUser) { // Token is invalid or check-in failed — clean up localStorage.removeItem('user_auth_token'); logout(); } } // If no token present, user stays logged out (login button shown) setIsLoading(false); }; initializer(); }, []); /** * Triggers the mAuth native login flow: * Generates an auth link and redirects the user to mAuth in the same window. */ const login = async (): Promise => { setIsLoading(true); try { const currentOrigin = localStorage.getItem('mworld_origin') || window.location.origin; const link = await authLink(currentOrigin); if (!link) { setIsLoading(false); return; } // Native flow: redirect in same window (not window.open) window.location.href = link; } catch (error) { console.error("Login failed:", error); setIsLoading(false); } }; const logout = () => { setUser(null); setToken(null); localStorage.removeItem('auth_user'); localStorage.removeItem('user_auth_token'); }; return ( {children} ); }; export const useAuth = () => { const context = useContext(AuthContext); if (!context) throw new Error('useAuth must be used within AuthProvider'); return context; };