Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 23x 23x 23x 5x 5x 5x 3x 3x 2x 2x 2x 2x 23x 52x 52x 3x 3x 3x 3x 3x 3x 3x 3x 3x 52x 3x 2x 2x 3x 3x 52x 564x 564x 564x | import { createContext, useContext, useState } from 'react'
import type { ReactNode } from 'react'
import { errorMessage } from './apiError'
export const TOKEN_KEY = 'auth_token'
const USERNAME_KEY = 'auth_username'
const API_BASE = import.meta.env.VITE_API_BASE ?? ''
async function loginRequest(username: string, password: string): Promise<string> {
Iif (!username.trim() || !password) throw new Error('Enter a username and password')
const res = await fetch(`${API_BASE}/api/v1/users/login`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password }),
})
if (!res.ok) throw new Error(await errorMessage(res))
const data = (await res.json()) as { token: string }
return data.token
}
async function registerRequest(username: string, password: string): Promise<void> {
Iif (!username.trim() || !password) throw new Error('Enter a username and password')
const res = await fetch(`${API_BASE}/api/v1/users/register`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password }),
})
Iif (!res.ok) throw new Error(await errorMessage(res))
}
async function logoutRequest(token: string): Promise<void> {
await fetch(`${API_BASE}/api/v1/users/logout`, {
method: 'POST',
headers: { authorization: `Bearer ${token}` },
})
}
type AuthContextValue = {
token: string | null
username: string | null
signIn: (username: string, password: string) => Promise<void>
register: (username: string, password: string) => Promise<void>
updateUsername: (username: string) => void
signOut: () => void
}
const AuthContext = createContext<AuthContextValue | null>(null)
export function AuthProvider({ children }: { children: ReactNode }) {
const [token, setToken] = useState<string | null>(() => localStorage.getItem(TOKEN_KEY))
const [username, setUsername] = useState<string | null>(() => localStorage.getItem(USERNAME_KEY))
function persistSession(newToken: string, newUsername: string) {
localStorage.setItem(TOKEN_KEY, newToken)
localStorage.setItem(USERNAME_KEY, newUsername)
setToken(newToken)
setUsername(newUsername)
}
function clearSession() {
localStorage.removeItem(TOKEN_KEY)
localStorage.removeItem(USERNAME_KEY)
sessionStorage.clear()
setToken(null)
setUsername(null)
}
const value: AuthContextValue = {
token,
username,
signIn: async (u, p) => persistSession(await loginRequest(u, p), u),
register: async (u, p) => {
await registerRequest(u, p)
persistSession(await loginRequest(u, p), u)
},
updateUsername: (u) => {
if (token) persistSession(token, u)
},
signOut: () => {
if (token) void logoutRequest(token).catch(() => {})
clearSession()
},
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
// eslint-disable-next-line react-refresh/only-export-components
export function useAuth() {
const ctx = useContext(AuthContext)
Iif (!ctx) throw new Error('useAuth must be used within AuthProvider')
return ctx
}
|