mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
174 lines
6.5 KiB
React
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) |