feat: add frontend auth shell
This commit is contained in:
92
frontend/src/features/auth/AuthProvider.tsx
Normal file
92
frontend/src/features/auth/AuthProvider.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import * as authApi from './api'
|
||||
import type { CurrentUser } from './types'
|
||||
|
||||
type AuthContextValue = {
|
||||
user: CurrentUser | null
|
||||
loading: boolean
|
||||
authenticated: boolean
|
||||
login: (username: string, password: string) => Promise<void>
|
||||
logout: () => Promise<void>
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
const AuthContext = React.createContext<AuthContextValue | null>(null)
|
||||
|
||||
type AuthProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const [user, setUser] = React.useState<CurrentUser | null>(null)
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
|
||||
const refresh = React.useCallback(async () => {
|
||||
try {
|
||||
const currentUser = await authApi.getCurrentUser()
|
||||
setUser(currentUser)
|
||||
} catch {
|
||||
setUser(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
authApi
|
||||
.getCurrentUser()
|
||||
.then((currentUser) => {
|
||||
if (mounted) {
|
||||
setUser(currentUser)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (mounted) {
|
||||
setUser(null)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (mounted) {
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleLogin = React.useCallback(async (username: string, password: string) => {
|
||||
const currentUser = await authApi.login(username, password)
|
||||
setUser(currentUser)
|
||||
}, [])
|
||||
|
||||
const handleLogout = React.useCallback(async () => {
|
||||
await authApi.logout()
|
||||
setUser(null)
|
||||
window.location.hash = '/'
|
||||
}, [])
|
||||
|
||||
const value = React.useMemo<AuthContextValue>(
|
||||
() => ({
|
||||
user,
|
||||
loading,
|
||||
authenticated: Boolean(user),
|
||||
login: handleLogin,
|
||||
logout: handleLogout,
|
||||
refresh
|
||||
}),
|
||||
[handleLogin, handleLogout, loading, refresh, user]
|
||||
)
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = React.useContext(AuthContext)
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
Reference in New Issue
Block a user