/** * Component: RouteGuard * * Higher-order component that protects routes using Gateways and Blockers. * Follows clean architecture by separating concerns: * - Gateway handles access logic * - Blocker handles prevention logic * - Component handles UI rendering */ 'use client'; import { ReactNode, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/lib/auth/AuthContext'; import { AuthGateway, AuthGatewayConfig } from './AuthGateway'; import { LoadingState } from '@/components/shared/LoadingState'; interface RouteGuardProps { children: ReactNode; config?: AuthGatewayConfig; /** * Custom loading component (optional) */ loadingComponent?: ReactNode; /** * Custom unauthorized component (optional) */ unauthorizedComponent?: ReactNode; } /** * RouteGuard Component * * Protects child components based on authentication and authorization rules. * Uses Gateway pattern for access control. * * Usage: * ```tsx * * * * ``` */ export function RouteGuard({ children, config = {}, loadingComponent, unauthorizedComponent, }: RouteGuardProps) { const router = useRouter(); const authContext = useAuth(); const [gateway] = useState(() => new AuthGateway(authContext, config)); const [accessState, setAccessState] = useState(gateway.getAccessState()); // Update gateway when auth context changes useEffect(() => { gateway.refresh(); setAccessState(gateway.getAccessState()); }, [authContext.session, authContext.loading, gateway]); // Handle redirects useEffect(() => { if (!accessState.canAccess && !accessState.isLoading) { if (config.redirectOnUnauthorized !== false) { const redirectPath = gateway.getUnauthorizedRedirectPath(); // Use a small delay to show unauthorized message briefly const timer = setTimeout(() => { router.push(redirectPath); }, 500); return () => clearTimeout(timer); } } }, [accessState, gateway, router, config.redirectOnUnauthorized]); // Show loading state if (accessState.isLoading) { return loadingComponent || (
); } // Show unauthorized state if (!accessState.canAccess) { return unauthorizedComponent || (

Access Denied

{accessState.reason}

); } // Render protected content return <>{children}; } /** * useRouteGuard Hook * * Hook for programmatic access control within components. * * Usage: * ```tsx * const { canAccess, reason, isLoading } = useRouteGuard({ requiredRoles: ['admin'] }); * ``` */ export function useRouteGuard(config: AuthGatewayConfig = {}) { const authContext = useAuth(); const [gateway] = useState(() => new AuthGateway(authContext, config)); const [state, setState] = useState(gateway.getAccessState()); useEffect(() => { gateway.refresh(); setState(gateway.getAccessState()); }, [authContext.session, authContext.loading, gateway]); return { canAccess: state.canAccess, reason: state.reason, isLoading: state.isLoading, isAuthenticated: state.isAuthenticated, enforceAccess: () => gateway.enforceAccess(), redirectIfUnauthorized: () => gateway.redirectIfUnauthorized(), }; }