shinkan-jinkendo/frontend/src/context/AuthContext.jsx

185 lines
4.6 KiB
React
Raw Normal View History

import {
createContext,
useContext,
useState,
useEffect,
useCallback,
useMemo,
useRef,
} from 'react'
2026-05-05 16:12:17 +02:00
import api, { ACTIVE_CLUB_STORAGE_KEY } from '../utils/api'
import { activeClubMemberships } from '../utils/activeClub'
import { clearCoachSessionStorage } from '../utils/trainingPlanUtils'
import { clearMediaGrant, refreshMediaGrant } from '../utils/exerciseMediaUrl'
const AuthContext = createContext(null)
2026-05-05 16:12:17 +02:00
function syncStoredActiveClub(profile) {
const clubs = activeClubMemberships(profile?.clubs)
2026-05-05 16:12:17 +02:00
const ids = new Set(clubs.map((c) => String(c.id)))
const eff = profile?.effective_club_id
if (eff != null && eff !== '' && ids.has(String(eff))) {
localStorage.setItem(ACTIVE_CLUB_STORAGE_KEY, String(eff))
return
}
2026-05-05 16:12:17 +02:00
const stored = localStorage.getItem(ACTIVE_CLUB_STORAGE_KEY)
if (stored && ids.has(stored)) return
const ac =
profile?.active_club_id != null && profile.active_club_id !== ''
? String(profile.active_club_id)
: ''
if (ac && ids.has(ac)) {
localStorage.setItem(ACTIVE_CLUB_STORAGE_KEY, ac)
return
}
if (clubs.length >= 1) {
localStorage.setItem(ACTIVE_CLUB_STORAGE_KEY, String(clubs[0].id))
return
}
try {
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
} catch {
/* ignore */
2026-05-05 16:12:17 +02:00
}
}
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
2026-05-05 16:12:17 +02:00
const userRef = useRef(null)
useEffect(() => {
userRef.current = user
}, [user])
const checkAuth = useCallback(async () => {
const token = localStorage.getItem('authToken')
if (!token) {
setLoading(false)
return
}
try {
const profile = await api.getCurrentProfile()
2026-05-05 16:12:17 +02:00
syncStoredActiveClub(profile)
setUser(profile)
refreshMediaGrant().catch(() => {})
} catch (err) {
try {
const status = await api.getTotpStatus()
if (status?.enrollment_required && !status?.totp_enabled) {
setUser({
totpEnrollmentRequired: true,
email: status.email || null,
role: status.role || null,
})
return
}
} catch {
/* kein Einrichtungs-Token */
}
console.error('Auth check failed:', err)
localStorage.removeItem('authToken')
clearMediaGrant()
setUser(null)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
checkAuth()
}, [checkAuth])
2026-05-05 16:12:17 +02:00
const setActiveClub = useCallback(async (clubId) => {
const cid = Number(clubId)
const uid = userRef.current?.id
if (!Number.isFinite(cid) || cid < 1 || !uid) return
localStorage.setItem(ACTIVE_CLUB_STORAGE_KEY, String(cid))
setUser((prev) =>
prev?.id
? { ...prev, active_club_id: cid, effective_club_id: cid }
: prev
)
2026-05-05 16:12:17 +02:00
try {
await api.updateProfile(uid, { active_club_id: cid })
const profile = await api.getCurrentProfile()
syncStoredActiveClub(profile)
setUser(profile)
2026-05-05 16:12:17 +02:00
} catch (e) {
console.error(e)
try {
const profile = await api.getCurrentProfile()
syncStoredActiveClub(profile)
setUser(profile)
} catch {
/* ignore */
}
2026-05-05 16:12:17 +02:00
}
}, [])
/** Fallback, falls ohne checkAuth gesetzt wird (Legacy / Token-Injektion) */
const login = useCallback((payload) => {
if (payload?.profile != null) {
2026-05-05 16:12:17 +02:00
syncStoredActiveClub(payload.profile)
setUser(payload.profile)
return
}
const p = payload
if (p?.profile_id != null || p?.id != null) {
setUser({
id: p.profile_id ?? p.id,
name: p.name ?? null,
email: p.email ?? null,
role: p.role ?? 'user',
tier: p.tier ?? 'free',
})
return
}
setUser(payload)
}, [])
const logout = useCallback(async () => {
try {
await api.logout()
} catch {
/* Session lokal trotzdem beenden */
}
setUser(null)
localStorage.removeItem('authToken')
2026-05-05 16:12:17 +02:00
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
clearMediaGrant()
clearCoachSessionStorage()
}, [])
const value = useMemo(
() => ({
user,
isAuthenticated: !!user,
loading,
login,
logout,
checkAuth,
setActiveClub,
}),
[user, loading, login, logout, checkAuth, setActiveClub],
)
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
export default AuthContext