| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- "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 / RHL-032)
- *
- * RHL-032 improvement:
- * - When revalidating while already authenticated, we keep:
- * - status="authenticated"
- * - user != null
- * - and only flip `isValidating=true`
- *
- * This prevents AuthGate from rendering the "Sitzung wird geprüft" content card
- * during fast navigations (branch switch), eliminating flicker.
- */
- 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,
- isValidating: false,
- });
- // 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() {
- // If we already have a valid authenticated session, revalidate in the background:
- // keep content stable and only set isValidating=true.
- setAuth((prev) => {
- const hasUser = prev.status === "authenticated" && prev.user;
- if (hasUser) {
- return { ...prev, error: null, isValidating: true };
- }
- return {
- status: "loading",
- user: null,
- error: null,
- isValidating: false,
- };
- });
- try {
- const res = await getMe();
- if (cancelled) return;
- if (res?.user) {
- didRedirectRef.current = false;
- setAuth({
- status: "authenticated",
- user: res.user,
- error: null,
- isValidating: false,
- });
- return;
- }
- // Unauthenticated session (frontend-friendly endpoint returns 200 + user:null).
- setAuth({
- status: "unauthenticated",
- user: null,
- error: null,
- isValidating: false,
- });
- if (!didRedirectRef.current) {
- didRedirectRef.current = true;
- 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;
- // If we were already authenticated, fail open:
- // keep the last known user/session and just end validating.
- setAuth((prev) => {
- const hasUser = prev.status === "authenticated" && prev.user;
- if (hasUser) {
- return { ...prev, isValidating: false };
- }
- // Initial load failed -> show error state (AuthGate handles it).
- return {
- status: "error",
- user: null,
- error:
- "Sitzung konnte nicht geprüft werden. Bitte erneut versuchen.",
- isValidating: false,
- };
- });
- }
- }
- runSessionCheck();
- return () => {
- cancelled = true;
- };
- }, [router, retryTick]);
- return (
- <AuthContextProvider value={{ ...auth, retry }}>
- {children}
- </AuthContextProvider>
- );
- }
|