All files / src/components/guards OnboardingGuard.tsx

100% Statements 19/19
100% Branches 23/23
100% Functions 2/2
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                                16x 16x 16x 16x 16x   16x   16x   15x 2x 2x             13x             1x                       16x 5x       11x 3x       8x 1x     7x    
import { useEffect } from "react";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { useAuth } from "../../lib/auth-context";
import { usePro } from "../../lib/pro-context";
import { LoadingSpinner } from "./LoadingSpinner";
 
type OnboardingGuardProps = {
	children: React.ReactNode;
};
 
/**
 * OnboardingGuard wraps protected pro routes.
 * It redirects users to /onboarding if they haven't completed onboarding.
 * This guard should be used AFTER AuthGuard in the component hierarchy.
 */
export function OnboardingGuard({ children }: OnboardingGuardProps) {
	const { isAuthenticated, isLoading: authLoading, hasProAccess } = useAuth();
	const { onboardingRequired, pendingInvitationToken, isLoading: proLoading } = usePro();
	const navigate = useNavigate();
	const routerState = useRouterState();
	const currentPath = routerState.location.pathname;
 
	const isLoading = authLoading || proLoading;
 
	useEffect(() => {
		// Redirect to accept invitation if user has a pending team invitation
		if (!isLoading && isAuthenticated && pendingInvitationToken) {
			navigate({ to: "/accept-invitation", search: { token: pendingInvitationToken } });
			return;
		}
 
		// Only redirect if we have pro access and onboarding is required.
		// Admin-only users don't need onboarding.
		// The `!currentPath.startsWith("/onboarding")` check is the real loop guard:
		// it prevents re-redirect on remount when the user is already on /onboarding.
		if (
			!isLoading &&
			isAuthenticated &&
			hasProAccess &&
			onboardingRequired &&
			!currentPath.startsWith("/onboarding")
		) {
			navigate({ to: "/onboarding" });
		}
	}, [
		isLoading,
		isAuthenticated,
		hasProAccess,
		onboardingRequired,
		pendingInvitationToken,
		currentPath,
		navigate,
	]);
 
	if (isLoading) {
		return <LoadingSpinner />;
	}
 
	// Show spinner while redirecting to accept invitation
	if (pendingInvitationToken) {
		return <LoadingSpinner />;
	}
 
	// Show spinner while redirecting to onboarding
	if (hasProAccess && onboardingRequired && !currentPath.startsWith("/onboarding")) {
		return <LoadingSpinner />;
	}
 
	return <>{children}</>;
}