Files
starr-philproperties/src/contexts/AuthContext.jsx
T
kennethobsequio 244aa607f7 add: more things
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-07-06 15:21:36 +08:00

174 lines
6.5 KiB
React

// contexts/AuthContext.jsx
import { createContext, useContext, useState, useCallback, useRef } from 'react'
import api from '../utils/api.util'
const AuthContext = createContext(null)
export const decodeToken = (token) => JSON.parse(atob(token.split('.')[1]))
export function AuthProvider({ children }) {
const [accessToken, _setAccessToken] = useState(null)
const [user, setUser] = useState(null)
const [sessionId, setSessionId] = useState(null)
const [loading, setLoading] = useState(true)
const [sessionRestored, setSessionRestored] = useState(false)
const [authError, setAuthError] = useState(null)
const isRestoring = useRef(false)
const accessTokenRef = useRef(null)
const setAccessToken = useCallback((token) => {
accessTokenRef.current = token
_setAccessToken(token)
}, [])
// Shared by verifyOTP, restoreSession, and login's trusted-device fast path
// — anywhere the backend hands back a fully-authenticated session in one shot.
const applySession = useCallback((data) => {
setAccessToken(data.accessToken)
setUser(data.user)
setSessionId(data.session_id ?? null)
}, [setAccessToken])
// ── Login ──────────────────────────────────────────────────────────────────
// Credentials get you an OTP — unless this device already cleared one
// recently and its trust window is still valid, in which case the backend
// returns otpRequired:false along with a full session, same shape as
// verifyOTP's response.
const login = useCallback(async ({ email, password }) => {
setAuthError(null)
try {
const { data } = await api.post('/auth/login', { email, password })
if (data.data.otpRequired === false) {
applySession(data.data)
return { success: true, otpRequired: false, user: data.data.user }
}
return { success: true, otpRequired: true, email: data.data.email }
} catch (err) {
const message = err.response?.data?.message || 'Login failed. Please try again.'
const errors = err.response?.data?.errors ?? null
setAuthError(message)
return { success: false, message, errors }
}
}, [applySession])
// ── Register ───────────────────────────────────────────────────────────────
const register = useCallback(async (payload) => {
setAuthError(null)
try {
await api.post('/auth/register', payload)
return { success: true }
} catch (err) {
const message = err.response?.data?.message || 'Registration failed. Please try again.'
setAuthError(message)
return { success: false, message }
}
}, [])
// ── Verify OTP (auto-login) ────────────────────────────────────────────────
const verifyOTP = useCallback(async ({ email, otp }) => {
setAuthError(null)
try {
const { data } = await api.post('/auth/verify-otp', { email, otp })
applySession(data.data)
return { success: true, user: data.data.user }
} catch (err) {
const message = err.response?.data?.message || 'OTP verification failed.'
setAuthError(message)
return { success: false, message }
}
}, [applySession])
// ── Resend OTP ─────────────────────────────────────────────────────────────
const resendOTP = useCallback(async ({ email }) => {
try {
await api.post('/auth/resend-otp', { email })
return { success: true }
} catch (err) {
const message = err.response?.data?.message || 'Could not resend OTP.'
return { success: false, message }
}
}, [])
// ── Forgot password (request OTP) ─────────────────────────────────────────
const forgotPassword = useCallback(async ({ email }) => {
try {
const { data } = await api.post('/auth/forgot-password', { email })
return { success: true, email: data.data.email }
} catch (err) {
const message = err.response?.data?.message || 'Could not process request.'
return { success: false, message }
}
}, [])
// ── Reset password (OTP + new password in one step) ───────────────────────
const resetPassword = useCallback(async ({ email, otp, new_password }) => {
try {
await api.post('/auth/reset-password', { email, otp, new_password })
return { success: true }
} catch (err) {
const message = err.response?.data?.message || 'Password reset failed.'
return { success: false, message }
}
}, [])
// ── Logout ─────────────────────────────────────────────────────────────────
const logout = useCallback(async () => {
try {
await api.post('/auth/logout', { session_id: sessionId })
} catch (_) {
// ignore
} finally {
setAccessToken(null)
setUser(null)
setSessionId(null)
}
}, [sessionId])
// ── Restore session ────────────────────────────────────────────────────────
const restoreSession = useCallback(async () => {
if (isRestoring.current) return { success: false }
isRestoring.current = true
try {
if (accessTokenRef.current) return { success: true }
const { data } = await api.post('/auth/refresh')
applySession(data.data)
return { success: true, user: data.data.user }
} catch (_) {
setAccessToken(null)
setUser(null)
setSessionId(null)
return { success: false }
} finally {
setLoading(false)
}
}, [applySession])
return (
<AuthContext.Provider value={{
accessToken,
accessTokenRef,
setAccessToken,
user,
setUser,
sessionId,
login,
register,
verifyOTP,
resendOTP,
forgotPassword,
resetPassword,
logout,
loading,
sessionRestored,
restoreSession,
authError,
setAuthError,
}}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => useContext(AuthContext)