All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Refactored the App component to utilize React's lazy loading for page components, enhancing load times and performance. - Introduced a fallback UI with a spinner during component loading, improving user feedback during navigation. - Updated the AuthContext to use useCallback and useMemo for optimized performance in login and logout functions, reducing unnecessary re-renders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
165 lines
4.0 KiB
JavaScript
165 lines
4.0 KiB
JavaScript
import {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
useMemo,
|
|
useRef,
|
|
} from 'react'
|
|
import api, { ACTIVE_CLUB_STORAGE_KEY } from '../utils/api'
|
|
import { activeClubMemberships } from '../utils/activeClub'
|
|
|
|
const AuthContext = createContext(null)
|
|
|
|
function syncStoredActiveClub(profile) {
|
|
const clubs = activeClubMemberships(profile?.clubs)
|
|
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
|
|
}
|
|
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 */
|
|
}
|
|
}
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [user, setUser] = useState(null)
|
|
const [loading, setLoading] = useState(true)
|
|
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()
|
|
syncStoredActiveClub(profile)
|
|
setUser(profile)
|
|
} catch (err) {
|
|
console.error('Auth check failed:', err)
|
|
localStorage.removeItem('authToken')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
checkAuth()
|
|
}, [checkAuth])
|
|
|
|
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
|
|
)
|
|
try {
|
|
await api.updateProfile(uid, { active_club_id: cid })
|
|
const profile = await api.getCurrentProfile()
|
|
syncStoredActiveClub(profile)
|
|
setUser(profile)
|
|
} catch (e) {
|
|
console.error(e)
|
|
try {
|
|
const profile = await api.getCurrentProfile()
|
|
syncStoredActiveClub(profile)
|
|
setUser(profile)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
/** Fallback, falls ohne checkAuth gesetzt wird (Legacy / Token-Injektion) */
|
|
const login = useCallback((payload) => {
|
|
if (payload?.profile != null) {
|
|
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(() => {
|
|
setUser(null)
|
|
localStorage.removeItem('authToken')
|
|
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)
|
|
for (const key of Object.keys(sessionStorage)) {
|
|
if (key.startsWith('sj_coach_')) {
|
|
sessionStorage.removeItem(key)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
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
|