import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { getCurrentUser, signIn, signOut as signOutRequest } from '@/shared/api/auth'; import { AuthExpiredError } from '@/shared/api/httpClient'; import { AUTH_EXPIRED_EVENT } from '@/shared/constants/auth'; import { AUTH_MODAL_SIGNIN_CLOSE_DELAY_MS, AUTH_MODAL_SIGNUP_CLOSE_DELAY_MS, } from '@/shared/constants/auth'; import { APP_ROUTE_PATHS } from '@/shared/constants/routes'; import { CurrentUser } from '@/shared/types/auth'; import { UserProfile } from '@/shared/types/app'; import { toUserProfile } from '@/business/auth/mappers'; import { useCampusCatalog } from '@/business/campuses/hooks'; import { AuthActionResult, AuthSessionState } from '@/business/auth/types'; import { getErrorMessage, getOptionalErrorMessage } from '@/shared/errors/errorMessages'; import type { AuthModalDraft, AuthModalMode, AuthModalWorkflowInput, AuthSignupStep, } from '@/business/auth/types'; import { getNextSignupStep, getPreviousSignupStep, getSignupCampusName, validateSignupStepOne, } from '@/business/auth/selectors'; import type { CampusId, UserRole } from '@/shared/types/app'; export function useAuthSession(): AuthSessionState { const navigate = useNavigate(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const clearSession = useCallback(() => { setUser(null); }, []); const redirectToLogin = useCallback(() => { navigate(APP_ROUTE_PATHS.login, { replace: true }); }, [navigate]); const expireSession = useCallback(() => { clearSession(); redirectToLogin(); }, [clearSession, redirectToLogin]); useEffect(() => { window.addEventListener(AUTH_EXPIRED_EVENT, expireSession); return () => { window.removeEventListener(AUTH_EXPIRED_EVENT, expireSession); }; }, [expireSession]); useEffect(() => { let isActive = true; void getCurrentUser() .then((currentUser) => { if (isActive) { setUser(currentUser); } }) .catch((error: unknown) => { if (isActive) { clearSession(); if (error instanceof AuthExpiredError) { expireSession(); } } }) .finally(() => { if (isActive) { setLoading(false); } }); return () => { isActive = false; }; }, [clearSession, expireSession]); const handleSignIn = useCallback(async (email: string, password: string): Promise => { setLoading(true); try { const currentUser = await signIn({ email, password }); setUser(currentUser); return { error: null }; } catch (error) { clearSession(); if (error instanceof AuthExpiredError) { expireSession(); } return { error: getErrorMessage(error, 'Sign in failed') }; } finally { setLoading(false); } }, [clearSession, expireSession]); const signUp = useCallback(async (): Promise => ({ error: 'Account creation is not available until backend product roles and campus assignment are implemented.', }), []); const signOut = useCallback(async (): Promise => { try { await signOutRequest(); clearSession(); return { error: null }; } catch (error) { if (error instanceof AuthExpiredError) { expireSession(); return { error: null }; } return { error: getErrorMessage(error, 'Sign out failed') }; } }, [clearSession, expireSession]); const updateProfile = useCallback(async (): Promise => ({ error: 'Profile updates are handled by the profile page.', }), []); const refreshUser = useCallback(async (): Promise => { try { const currentUser = await getCurrentUser(); setUser(currentUser); } catch (error) { if (error instanceof AuthExpiredError) { expireSession(); } } }, [expireSession]); const profile: UserProfile | null = useMemo(() => (user ? toUserProfile(user) : null), [user]); return { user, profile, loading, signIn: handleSignIn, signUp, signOut, updateProfile, refreshUser, isAuthenticated: Boolean(user), }; } const initialAuthModalDraft: AuthModalDraft = { email: '', password: '', confirmPassword: '', fullName: '', role: 'teacher', campus: '', showPassword: false, }; export function useAuthModalWorkflow({ signIn: signInAction, signUp: signUpAction, onClose, }: AuthModalWorkflowInput) { const campusCatalog = useCampusCatalog(); const [mode, setMode] = useState('signin'); const [signupStep, setSignupStep] = useState(1); const [draft, setDraft] = useState(initialAuthModalDraft); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const resetForm = useCallback(() => { setDraft(initialAuthModalDraft); setError(null); setSuccess(null); setMode('signin'); setSignupStep(1); }, []); const closeAfterSuccess = useCallback((delayMs: number) => { window.setTimeout(() => { onClose(); resetForm(); }, delayMs); }, [onClose, resetForm]); const updateDraft = useCallback((patch: Partial) => { setDraft((currentDraft) => ({ ...currentDraft, ...patch })); }, []); const handleSignIn = useCallback(async () => { setError(null); setSuccess(null); setLoading(true); const result = await signInAction(draft.email, draft.password); setLoading(false); if (result.error) { setError(result.error); return; } setSuccess('Signed in successfully!'); closeAfterSuccess(AUTH_MODAL_SIGNIN_CLOSE_DELAY_MS); }, [closeAfterSuccess, draft.email, draft.password, signInAction]); const handleSignUp = useCallback(async () => { setError(null); setSuccess(null); const campusName = getSignupCampusName(draft.campus, campusCatalog.campuses); if (!campusName) { setError(campusCatalog.error ? 'Campus list is unavailable. Please try again.' : 'Please select your campus mascot'); return; } setLoading(true); const result = await signUpAction(draft.email, draft.password, draft.fullName, draft.role, campusName); setLoading(false); if (result.error) { setError(result.error); return; } setSuccess('Account created! You are now signed in.'); closeAfterSuccess(AUTH_MODAL_SIGNUP_CLOSE_DELAY_MS); }, [ campusCatalog.campuses, campusCatalog.error, closeAfterSuccess, draft.campus, draft.email, draft.fullName, draft.password, draft.role, signUpAction, ]); const goToNextStep = useCallback(() => { setError(null); if (signupStep === 1) { const validationError = validateSignupStepOne(draft); if (validationError) { setError(validationError); return; } setSignupStep(2); return; } if (signupStep === 2) { setSignupStep(3); return; } void handleSignUp(); }, [draft, handleSignUp, signupStep]); const goToPreviousStep = useCallback(() => { setError(null); setSignupStep((currentStep) => getPreviousSignupStep(currentStep)); }, []); const handleClose = useCallback(() => { resetForm(); onClose(); }, [onClose, resetForm]); const switchMode = useCallback((nextMode: AuthModalMode) => { setMode(nextMode); setError(null); setSuccess(null); setSignupStep(1); }, []); const selectedCampusId = draft.campus; return { state: { mode, signupStep, draft, loading, error, success, selectedCampusId, campuses: campusCatalog.campuses, campusesLoading: campusCatalog.isLoading, campusesError: getOptionalErrorMessage(campusCatalog.error), }, actions: { updateDraft, setRole: (role: UserRole) => updateDraft({ role }), setCampus: (campus: CampusId | '') => updateDraft({ campus }), setShowPassword: (showPassword: boolean) => updateDraft({ showPassword }), handleSignIn, goToNextStep, goToPreviousStep, handleClose, switchMode, getNextSignupStep, }, }; }