feat: auth hook and login page (Task 9)
- useAuth: session token from URL/localStorage, /api/auth/me validation, logout - LoginPage: email form with POST /api/auth/request, error handling - App.tsx: use useAuth hook, LoginPage, 'sent' confirmation route - Fix: auto-route to game covers both 'login' and 'sent' routes when authenticated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
type Profile = { id: number; email: string; display_name: string | null }
|
||||
type AuthState = { profile: Profile | null; token: string | null; loading: boolean }
|
||||
|
||||
export function useAuth() {
|
||||
const [state, setState] = useState<AuthState>({ profile: null, token: null, loading: true })
|
||||
|
||||
const validate = useCallback(async (token: string) => {
|
||||
const r = await fetch('/api/auth/me', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!r.ok) throw new Error('invalid session')
|
||||
const profile: Profile = await r.json()
|
||||
return profile
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Check URL for ?session= from magic link redirect
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const urlToken = params.get('session')
|
||||
if (urlToken) {
|
||||
localStorage.setItem('session', urlToken)
|
||||
window.history.replaceState({}, '', window.location.pathname)
|
||||
}
|
||||
|
||||
const token = urlToken || localStorage.getItem('session')
|
||||
if (!token) {
|
||||
setState({ profile: null, token: null, loading: false })
|
||||
return
|
||||
}
|
||||
|
||||
validate(token)
|
||||
.then(profile => setState({ profile, token, loading: false }))
|
||||
.catch(() => {
|
||||
localStorage.removeItem('session')
|
||||
setState({ profile: null, token: null, loading: false })
|
||||
})
|
||||
}, [validate])
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
const token = localStorage.getItem('session')
|
||||
if (token) {
|
||||
await fetch('/api/auth/session', {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).catch(() => {})
|
||||
localStorage.removeItem('session')
|
||||
}
|
||||
setState({ profile: null, token: null, loading: false })
|
||||
}, [])
|
||||
|
||||
return { ...state, logout }
|
||||
}
|
||||
Reference in New Issue
Block a user