Gateprep / frontend /src /context /AuthContext.jsx
banu4prasad's picture
add: tests and code cleanup
05b4327
Raw
History Blame Contribute Delete
3.61 kB
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import api, { AUTH_UNAUTHORIZED_EVENT, startTokenRefresh, stopTokenRefresh } from '../api/client'
import { mutate } from 'swr'
const AuthContext = createContext(null)
export function AuthProvider({ children }) {
const location = useLocation()
const navigate = useNavigate()
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [sessionExpired, setSessionExpired] = useState(false)
useEffect(() => {
api.get('/auth/me')
.then(r => {
setUser(r.data)
startTokenRefresh({ refreshNow: true })
})
.catch(() => {
setUser(null)
})
.finally(() => setLoading(false))
}, [])
useEffect(() => {
const handleUnauthorized = () => {
setSessionExpired(true)
}
window.addEventListener(AUTH_UNAUTHORIZED_EVENT, handleUnauthorized)
return () => window.removeEventListener(AUTH_UNAUTHORIZED_EVENT, handleUnauthorized)
}, [])
useEffect(() => {
return () => stopTokenRefresh()
}, [])
// Called after successful login or registration
const saveUser = useCallback((userData) => {
const u = {
id: userData.id ?? userData.user_id,
email: userData.email,
role: userData.role,
full_name: userData.full_name,
}
setUser(u)
setSessionExpired(false)
startTokenRefresh()
return u
}, [])
const logout = useCallback(async () => {
try { await api.post('/auth/logout') } catch (err) { console.error('Logout API error:', err) }
stopTokenRefresh()
setUser(null)
setSessionExpired(false)
mutate(() => true, undefined, { revalidate: false })
navigate('/login', { replace: true })
}, [navigate])
const confirmSessionExpired = useCallback(() => {
stopTokenRefresh()
setUser(null)
setSessionExpired(false)
mutate(() => true, undefined, { revalidate: false })
navigate('/login', {
replace: true,
state: { from: `${location.pathname}${location.search}` },
})
}, [location.pathname, location.search, navigate])
const value = useMemo(() => ({
user,
loading,
saveUser,
logout,
sessionExpired,
}), [user, loading, saveUser, logout, sessionExpired])
return (
<AuthContext.Provider value={value}>
{children}
{sessionExpired && (
<div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4"
role="presentation"
>
<div
className="gate-card w-full max-w-sm p-5"
role="alertdialog"
aria-modal="true"
aria-labelledby="session-expired-title"
aria-describedby="session-expired-description"
>
<h2 id="session-expired-title" className="text-lg font-bold text-slate-900 dark:text-white">
Session expired
</h2>
<p id="session-expired-description" className="mt-2 text-sm text-slate-500 dark:text-slate-400">
Sign in again to continue.
</p>
<div className="mt-5 flex justify-end">
<button onClick={confirmSessionExpired} className="btn-primary" autoFocus>
Sign in
</button>
</div>
</div>
</div>
)}
</AuthContext.Provider>
)
}
export const useAuth = () => {
const ctx = useContext(AuthContext)
if (!ctx) throw new Error('useAuth must be used within AuthProvider')
return ctx
}