| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- "use client";
- import React from "react";
- import { useRouter } from "next/navigation";
- import { getMe } from "@/lib/frontend/apiClient";
- import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
- import { AuthProvider as AuthContextProvider } from "@/components/auth/authContext";
- /**
- * AuthProvider (RHL-020)
- *
- * Responsibilities:
- * - Run a session check via GET /api/auth/me.
- * - Store the result in AuthContext.
- * - Redirect to /login when unauthenticated (reason=expired, next=current URL).
- *
- * Important UX improvement:
- * - We no longer render full-screen "solo spinners" here.
- * - The AppShell stays visible and AuthGate renders the loading/error UI inside main content.
- *
- * Important performance/UX improvement:
- * - We do NOT re-check the session on every route change.
- * - The session check runs:
- * - once on mount
- * - and again only when the user hits "retry"
- *
- * @param {{ children: React.ReactNode }} props
- */
- export default function AuthProvider({ children }) {
- const router = useRouter();
- // Prevent double redirects in quick re-renders.
- const didRedirectRef = React.useRef(false);
- // Auth state exposed via context.
- const [auth, setAuth] = React.useState({
- status: "loading",
- user: null,
- error: null,
- });
- // Retry tick triggers a refetch without tying auth checks to route changes.
- const [retryTick, setRetryTick] = React.useState(0);
- const retry = React.useCallback(() => {
- setRetryTick((n) => n + 1);
- }, []);
- React.useEffect(() => {
- let cancelled = false;
- async function runSessionCheck() {
- setAuth({ status: "loading", user: null, error: null });
- try {
- const res = await getMe();
- if (cancelled) return;
- if (res?.user) {
- // Authenticated session.
- didRedirectRef.current = false;
- setAuth({ status: "authenticated", user: res.user, error: null });
- return;
- }
- // Unauthenticated session (frontend-friendly endpoint returns 200 + user:null).
- setAuth({ status: "unauthenticated", user: null, error: null });
- if (!didRedirectRef.current) {
- didRedirectRef.current = true;
- // Preserve the current URL as "next" so the user returns to the same page after login.
- const next =
- typeof window !== "undefined"
- ? `${window.location.pathname}${window.location.search}`
- : "/";
- const loginUrl = buildLoginUrl({
- reason: LOGIN_REASONS.EXPIRED,
- next,
- });
- router.replace(loginUrl);
- }
- } catch (err) {
- if (cancelled) return;
- setAuth({
- status: "error",
- user: null,
- error: "Sitzung konnte nicht geprüft werden. Bitte erneut versuchen.",
- });
- }
- }
- runSessionCheck();
- return () => {
- cancelled = true;
- };
- }, [router, retryTick]);
- return (
- <AuthContextProvider value={{ ...auth, retry }}>
- {children}
- </AuthContextProvider>
- );
- }
|