AuthContext: - Added setAuthFromToken() for direct token/profile set - Used for email verification auto-login (no /login request) - Properly initializes session with token and profile Verify.jsx: - Fixed auto-login: now uses setAuthFromToken() instead of login() - Added "already_verified" status for better UX - Auto-redirect to /login after 3s if already verified - Shows friendly message instead of error This fixes: - 422 Unprocessable Entity error during auto-login - Empty dashboard page after verification (now redirects correctly) - "Ungültiger Link" error on second click (now shows "bereits bestätigt") Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
3.8 KiB
JavaScript
134 lines
3.8 KiB
JavaScript
import { createContext, useContext, useState, useEffect } from 'react'
|
|
|
|
const AuthContext = createContext(null)
|
|
|
|
const TOKEN_KEY = 'bodytrack_token'
|
|
const PROFILE_KEY = 'bodytrack_active_profile'
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [session, setSession] = useState(null) // {token, profile_id, role}
|
|
const [loading, setLoading] = useState(true)
|
|
const [needsSetup, setNeedsSetup] = useState(false)
|
|
|
|
useEffect(() => {
|
|
checkStatus()
|
|
}, [])
|
|
|
|
const checkStatus = async () => {
|
|
try {
|
|
const r = await fetch('/api/auth/status')
|
|
const status = await r.json()
|
|
|
|
if (status.needs_setup) {
|
|
setNeedsSetup(true)
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
// Try existing token
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
|
if (token) {
|
|
const me = await fetch('/api/auth/me', {
|
|
headers: { 'X-Auth-Token': token }
|
|
})
|
|
if (me.ok) {
|
|
const profile = await me.json()
|
|
setSession({ token, profile_id: profile.id, role: profile.role, profile })
|
|
setLoading(false)
|
|
return
|
|
}
|
|
// Token expired
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
}
|
|
} catch(e) {
|
|
console.error('Auth check failed', e)
|
|
}
|
|
setLoading(false)
|
|
}
|
|
|
|
const login = async (credentials) => {
|
|
// Support both new {email, pin} and legacy {profile_id, pin}
|
|
const body = typeof credentials === 'object' ? credentials : { profile_id: credentials }
|
|
const r = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body)
|
|
})
|
|
if (!r.ok) {
|
|
const err = await r.json()
|
|
throw new Error(err.detail || 'Login fehlgeschlagen')
|
|
}
|
|
const data = await r.json()
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
|
// Fetch full profile
|
|
const me = await fetch('/api/auth/me', { headers: { 'X-Auth-Token': data.token } })
|
|
const profile = await me.json()
|
|
setSession({ token: data.token, profile_id: data.profile_id, role: data.role, profile })
|
|
return data
|
|
}
|
|
|
|
const setup = async (formData) => {
|
|
const r = await fetch('/api/auth/setup', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(formData)
|
|
})
|
|
if (!r.ok) {
|
|
const err = await r.json()
|
|
throw new Error(err.detail || 'Setup fehlgeschlagen')
|
|
}
|
|
const data = await r.json()
|
|
localStorage.setItem(TOKEN_KEY, data.token)
|
|
localStorage.setItem(PROFILE_KEY, data.profile_id)
|
|
setNeedsSetup(false)
|
|
await checkStatus()
|
|
return data
|
|
}
|
|
|
|
const setAuthFromToken = (token, profile) => {
|
|
// Direct token/profile set (for email verification auto-login)
|
|
localStorage.setItem(TOKEN_KEY, token)
|
|
localStorage.setItem(PROFILE_KEY, profile.id)
|
|
setSession({
|
|
token,
|
|
profile_id: profile.id,
|
|
role: profile.role || 'user',
|
|
profile
|
|
})
|
|
}
|
|
|
|
const logout = async () => {
|
|
const token = localStorage.getItem(TOKEN_KEY)
|
|
if (token) {
|
|
await fetch('/api/auth/logout', { method: 'POST', headers: { 'X-Auth-Token': token } })
|
|
}
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
setSession(null)
|
|
}
|
|
|
|
const isAdmin = session?.role === 'admin'
|
|
const canUseAI = session?.profile?.ai_enabled !== 0
|
|
const canExport = session?.profile?.export_enabled !== 0
|
|
|
|
return (
|
|
<AuthContext.Provider value={{
|
|
session, loading, needsSetup,
|
|
login, setup, logout, setAuthFromToken,
|
|
isAdmin, canUseAI, canExport,
|
|
token: session?.token,
|
|
profileId: session?.profile_id,
|
|
}}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useAuth() {
|
|
return useContext(AuthContext)
|
|
}
|
|
|
|
export function getToken() {
|
|
return localStorage.getItem(TOKEN_KEY)
|
|
}
|