init project
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useSyncExternalStore,
|
||||
} from 'react'
|
||||
import { setStoredToken, getStoredToken } from '../api/http'
|
||||
import type { AuthUser } from '../api/types'
|
||||
import { loginRequest } from '../api/session'
|
||||
|
||||
export type { AuthUser }
|
||||
|
||||
type AuthState = {
|
||||
token: string | null
|
||||
user: AuthUser | null
|
||||
}
|
||||
|
||||
const STORAGE_USER = 'alrahma_api_user'
|
||||
|
||||
function readUser(): AuthUser | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_USER)
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw) as AuthUser
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeUser(user: AuthUser | null): void {
|
||||
if (user) localStorage.setItem(STORAGE_USER, JSON.stringify(user))
|
||||
else localStorage.removeItem(STORAGE_USER)
|
||||
}
|
||||
|
||||
let memory: AuthState = {
|
||||
token: getStoredToken(),
|
||||
user: readUser(),
|
||||
}
|
||||
|
||||
const listeners = new Set<() => void>()
|
||||
|
||||
function emit(): void {
|
||||
listeners.forEach((fn) => fn())
|
||||
}
|
||||
|
||||
function setAuth(next: AuthState): void {
|
||||
memory = next
|
||||
emit()
|
||||
}
|
||||
|
||||
function subscribe(listener: () => void): () => void {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
}
|
||||
|
||||
function getSnapshot(): AuthState {
|
||||
return memory
|
||||
}
|
||||
|
||||
export function hydrateAuthFromStorage(): void {
|
||||
memory = {
|
||||
token: getStoredToken(),
|
||||
user: readUser(),
|
||||
}
|
||||
emit()
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
||||
|
||||
const login = useCallback(async (email: string, password: string) => {
|
||||
const result = await loginRequest(email, password)
|
||||
if (result.status === false) {
|
||||
return {
|
||||
ok: false as const,
|
||||
message: result.message ?? 'Login failed.',
|
||||
}
|
||||
}
|
||||
setStoredToken(result.token)
|
||||
writeUser(result.user)
|
||||
setAuth({ token: result.token, user: result.user })
|
||||
return { ok: true as const }
|
||||
}, [])
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setStoredToken(null)
|
||||
writeUser(null)
|
||||
setAuth({ token: null, user: null })
|
||||
}, [])
|
||||
|
||||
const setSession = useCallback((token: string, user: AuthUser) => {
|
||||
setStoredToken(token)
|
||||
writeUser(user)
|
||||
setAuth({ token, user })
|
||||
}, [])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
login,
|
||||
logout,
|
||||
setSession,
|
||||
token: state.token,
|
||||
user: state.user,
|
||||
isAuthenticated: Boolean(state.token && state.user),
|
||||
}),
|
||||
[state.token, state.user, login, logout, setSession],
|
||||
)
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||
}
|
||||
|
||||
type AuthContextValue = {
|
||||
login: (
|
||||
email: string,
|
||||
password: string,
|
||||
) => Promise<{ ok: true } | { ok: false; message: string }>
|
||||
logout: () => void
|
||||
setSession: (token: string, user: AuthUser) => void
|
||||
token: string | null
|
||||
user: AuthUser | null
|
||||
isAuthenticated: boolean
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | null>(null)
|
||||
|
||||
export function useAuth(): AuthContextValue {
|
||||
const ctx = useContext(AuthContext)
|
||||
if (!ctx) throw new Error('useAuth must be used within AuthProvider')
|
||||
return ctx
|
||||
}
|
||||
Reference in New Issue
Block a user