Enhance Authentication and Feature Usage Handling
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Failing after 1m15s

- Refactored the logout function in AuthContext to handle asynchronous logout operations, improving session management.
- Updated the FeatureUsageBadge component to display error messages when feature data retrieval fails, enhancing user feedback.
- Replaced lazy loading of OnboardingPage with lazyWithRetry for improved loading reliability.
- Adjusted the EntitlementsContext to determine club ID using utility functions for better governance form handling.
This commit is contained in:
Lars 2026-06-07 07:05:02 +02:00
parent 91dae7b614
commit 8718cf5c70
5 changed files with 55 additions and 11 deletions

View File

@ -1,4 +1,6 @@
import React, { Suspense, lazy } from 'react'
import LoginPage from './pages/LoginPage'
import { lazyWithRetry } from './utils/lazyWithRetry'
import {
RouterProvider,
createBrowserRouter,
@ -21,8 +23,7 @@ import ActiveClubSwitcher from './components/ActiveClubSwitcher'
import InactiveMembershipBanner from './components/InactiveMembershipBanner'
import './app.css'
const LoginPage = lazy(() => import('./pages/LoginPage'))
const OnboardingPage = lazy(() => import('./pages/OnboardingPage'))
const OnboardingPage = lazyWithRetry(() => import('./pages/OnboardingPage'))
const VerifyPage = lazy(() => import('./pages/VerifyPage'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const AccountSettingsPage = lazy(() => import('./pages/AccountSettingsPage'))
@ -125,11 +126,10 @@ function Nav({ showAdminNav, onboardingOnly }) {
function ProtectedLayout() {
const { isAuthenticated, loading, user, logout } = useAuth()
const handleLogout = () => {
if (confirm('Wirklich abmelden?')) {
logout()
window.location.href = '/'
}
const handleLogout = async () => {
if (!confirm('Wirklich abmelden?')) return
await logout()
window.location.href = '/login'
}
if (loading) {

View File

@ -5,7 +5,7 @@ import { useEntitlements } from '../context/EntitlementsContext'
* Unbegrenzt (limit null) nichts rendern.
*/
export default function FeatureUsageBadge({ featureId = 'ai_calls', label = 'KI-Kontingent' }) {
const { entitlements, loading, getFeature } = useEntitlements()
const { entitlements, loading, error, getFeature } = useEntitlements()
const feat = getFeature(featureId)
if (loading && !feat) {
@ -16,9 +16,23 @@ export default function FeatureUsageBadge({ featureId = 'ai_calls', label = 'KI-
)
}
if (!feat) return null
if (!feat) {
if (error) {
return (
<span
className="feature-usage-badge muted"
style={{ fontSize: '0.8rem', color: 'var(--text3)' }}
title={error}
>
{label}:
</span>
)
}
return null
}
const { used = 0, limit, remaining, allowed } = feat
// limit === 0 (z. B. Free-Plan ai_calls) anzeigen; nur echtes Unbegrenzt (null) ausblenden
if (limit == null) return null
const tone = !allowed || remaining === 0 ? 'var(--danger)' : 'var(--text2)'

View File

@ -122,7 +122,12 @@ export function AuthProvider({ children }) {
setUser(payload)
}, [])
const logout = useCallback(() => {
const logout = useCallback(async () => {
try {
await api.logout()
} catch {
/* Session lokal trotzdem beenden */
}
setUser(null)
localStorage.removeItem('authToken')
localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY)

View File

@ -1,5 +1,9 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { getMeEntitlements } from '../utils/api'
import {
getDefaultClubIdForGovernanceForms,
getResolvedActiveClubIdForUi,
} from '../utils/activeClub'
import { useAuth } from './AuthContext'
const EntitlementsContext = createContext(null)
@ -10,7 +14,8 @@ export function EntitlementsProvider({ children }) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const clubId = user?.effective_club_id ?? null
const clubId =
getResolvedActiveClubIdForUi(user) ?? getDefaultClubIdForGovernanceForms(user)
const refreshEntitlements = useCallback(async () => {
if (!isAuthenticated) {

View File

@ -0,0 +1,20 @@
import { lazy } from 'react'
/**
* Lazy-Import mit Reload bei fehlendem Chunk (nach Deploy alte Hashes im Browser-Cache).
*/
export function lazyWithRetry(importFn) {
return lazy(() =>
importFn().catch((err) => {
const key = 'sj_chunk_reload'
const reloaded = sessionStorage.getItem(key)
if (!reloaded) {
sessionStorage.setItem(key, '1')
window.location.reload()
return new Promise(() => {})
}
sessionStorage.removeItem(key)
throw err
}),
)
}