File size: 3,014 Bytes
65d68f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"use client";

import { useContext, useEffect, useState } from "react";
import { AuthContext } from "@/lib/auth/AuthProvider";
import { AppLoadingScreen } from "@/components/shared/AppLoadingScreen";
import { PageTransitionLoader } from "@/components/shared/PageTransitionLoader";
import { useRouter, usePathname } from "next/navigation";
import { isOnboardingCompleteEffective } from "@/lib/student-context";
import {
  isProtectedAppPath,
  resolveProtectedRouteRedirect,
  resolveOnboardingRedirect,
  shouldRenderAppChildren,
  shouldShowAppLoadingScreen,
} from "@/lib/ui/loading-visibility";

export function RootLoadingGuard({ children }: { children: React.ReactNode }) {
  const auth = useContext(AuthContext);
  const router = useRouter();
  const pathname = usePathname();
  const [isRedirecting, setIsRedirecting] = useState(false);

  useEffect(() => {
    if (!isProtectedAppPath(pathname)) {
      const t = window.setTimeout(() => setIsRedirecting(false), 0);
      return () => window.clearTimeout(t);
    }
    if (!auth || auth.isLoading) {
      // Avoid sync setState in effect; clear any stale redirect lock.
      const t = window.setTimeout(() => setIsRedirecting(false), 0);
      return () => window.clearTimeout(t);
    }

    const authTarget = resolveProtectedRouteRedirect({
      hasUser: Boolean(auth.user),
      isAuthLoading: auth.isLoading,
      pathname,
    });
    if (authTarget) {
      router.replace(authTarget);
      return;
    }
    if (!auth.user) {
      const t = window.setTimeout(() => setIsRedirecting(false), 0);
      return () => window.clearTimeout(t);
    }

    let cancelled = false;
    void isOnboardingCompleteEffective().then((done) => {
      if (cancelled) return;
      const target = resolveOnboardingRedirect({
        hasUser: Boolean(auth.user),
        isAuthLoading: auth.isLoading,
        pathname,
        onboardingComplete: done,
      });
      if (target) {
        setIsRedirecting(true);
        router.push(target);
      } else {
        setIsRedirecting(false);
      }
    });
    return () => {
      cancelled = true;
    };
  }, [auth, pathname, router]);

  const protectedGuestRedirectPending = Boolean(
    auth &&
      !auth.isLoading &&
      !auth.user &&
      isProtectedAppPath(pathname),
  );
  const loadingInput = {
    hasAuthContext: Boolean(auth),
    isAuthLoading: auth?.isLoading ?? true,
    isRedirecting: isRedirecting || protectedGuestRedirectPending,
    pathname,
  };

  const showLoader = shouldShowAppLoadingScreen(loadingInput);
  const renderChildren = shouldRenderAppChildren(loadingInput);
  const isOnboardingPage = pathname === "/onboarding";

  return (
    <>
      <AppLoadingScreen isVisible={showLoader} />
      {/* Top-bar transitions only once children can paint — avoids stacking
          the progress bar under the full-screen boot loader. */}
      {renderChildren && !isOnboardingPage ? <PageTransitionLoader /> : null}
      {renderChildren ? children : null}
    </>
  );
}