|
|
@@ -9,24 +9,16 @@ import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
|
|
|
import { AuthProvider as AuthContextProvider } from "@/components/auth/authContext";
|
|
|
|
|
|
/**
|
|
|
- * AuthProvider (RHL-020)
|
|
|
+ * AuthProvider (RHL-020 / RHL-032)
|
|
|
*
|
|
|
- * 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).
|
|
|
+ * RHL-032 improvement:
|
|
|
+ * - When revalidating while already authenticated, we keep:
|
|
|
+ * - status="authenticated"
|
|
|
+ * - user != null
|
|
|
+ * - and only flip `isValidating=true`
|
|
|
*
|
|
|
- * 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
|
|
|
+ * 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();
|
|
|
@@ -39,6 +31,7 @@ export default function AuthProvider({ children }) {
|
|
|
status: "loading",
|
|
|
user: null,
|
|
|
error: null,
|
|
|
+ isValidating: false,
|
|
|
});
|
|
|
|
|
|
// Retry tick triggers a refetch without tying auth checks to route changes.
|
|
|
@@ -52,26 +45,47 @@ export default function AuthProvider({ children }) {
|
|
|
let cancelled = false;
|
|
|
|
|
|
async function runSessionCheck() {
|
|
|
- setAuth({ status: "loading", user: null, error: null });
|
|
|
+ // 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) {
|
|
|
- // Authenticated session.
|
|
|
didRedirectRef.current = false;
|
|
|
- setAuth({ status: "authenticated", user: res.user, error: null });
|
|
|
+ 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 });
|
|
|
+ setAuth({
|
|
|
+ status: "unauthenticated",
|
|
|
+ user: null,
|
|
|
+ error: null,
|
|
|
+ isValidating: false,
|
|
|
+ });
|
|
|
|
|
|
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}`
|
|
|
@@ -87,10 +101,22 @@ export default function AuthProvider({ children }) {
|
|
|
} catch (err) {
|
|
|
if (cancelled) return;
|
|
|
|
|
|
- setAuth({
|
|
|
- status: "error",
|
|
|
- user: null,
|
|
|
- error: "Sitzung konnte nicht geprüft werden. Bitte erneut versuchen.",
|
|
|
+ // 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,
|
|
|
+ };
|
|
|
});
|
|
|
}
|
|
|
}
|