Update project files

This commit is contained in:
2026-07-17 09:48:48 +01:00
parent 63e5871e61
commit c6023d8d7b
29 changed files with 1309 additions and 718 deletions

124
src/context/AuthContext.tsx Normal file
View File

@@ -0,0 +1,124 @@
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
import {
authApi,
getStoredSession,
profileApi,
saveSession,
subscribeToSessionChanges,
} from '../api/client'
import type { AuthCredentials, AuthResponse, AuthSession, RegisterPayload, User } from '../types/models'
interface AuthContextValue {
session: AuthSession | null
user: User | null
isAuthenticated: boolean
isSiteAdmin: boolean
initializing: boolean
login: (payload: AuthCredentials) => Promise<AuthResponse>
register: (payload: RegisterPayload) => Promise<AuthResponse>
logout: () => Promise<void>
refreshProfile: () => Promise<User | null>
setCurrentUserProfile: (profile: User) => AuthSession | null
}
interface AuthProviderProps {
children: ReactNode
}
const AuthContext = createContext<AuthContextValue | null>(null)
const SITE_ADMIN_ROLES = new Set(['Site Admin', 'Admin'])
export function AuthProvider({ children }: AuthProviderProps) {
const [session, setSession] = useState(() => getStoredSession())
const [initializing, setInitializing] = useState(() => Boolean(getStoredSession()))
function setCurrentUserProfile(profile: User): AuthSession | null {
const currentSession = getStoredSession()
if (!currentSession) {
return null
}
return saveSession({
...currentSession,
user: profile,
})
}
async function refreshProfile(): Promise<User | null> {
const currentSession = getStoredSession()
if (!currentSession) {
return null
}
const profile = await profileApi.getProfile()
setCurrentUserProfile(profile)
return profile
}
useEffect(() => subscribeToSessionChanges(setSession), [])
useEffect(() => {
let cancelled = false
async function syncStoredSession() {
const existingSession = getStoredSession()
if (!existingSession) {
if (!cancelled) {
setInitializing(false)
}
return
}
try {
const profile = await profileApi.getProfile()
if (!cancelled) {
setCurrentUserProfile(profile)
}
} catch {
// The API client already clears invalid sessions after a failed refresh.
} finally {
if (!cancelled) {
setInitializing(false)
}
}
}
syncStoredSession()
return () => {
cancelled = true
}
}, [])
const user = session?.user ?? null
const userRoles = Array.isArray(user?.roles) ? user.roles : []
const value = {
session,
user,
isAuthenticated: Boolean(session?.accessToken),
isSiteAdmin: userRoles.some(role => SITE_ADMIN_ROLES.has(role)),
initializing,
login: authApi.login,
register: authApi.register,
logout: authApi.logout,
refreshProfile,
setCurrentUserProfile,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export function useAuth() {
const value = useContext(AuthContext)
if (!value) {
throw new Error('useAuth must be used inside an AuthProvider.')
}
return value
}