137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
/**
|
|
* 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
|
|
* <RouteGuard config={{ requiredRoles: ['owner', 'admin'] }}>
|
|
* <AdminDashboard />
|
|
* </RouteGuard>
|
|
* ```
|
|
*/
|
|
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 || (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<LoadingState message="Loading..." className="min-h-screen" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Show unauthorized state
|
|
if (!accessState.canAccess) {
|
|
return unauthorizedComponent || (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="bg-iron-gray p-8 rounded-lg border border-charcoal-outline max-w-md text-center">
|
|
<h2 className="text-xl font-bold text-racing-red mb-4">Access Denied</h2>
|
|
<p className="text-gray-300 mb-6">{accessState.reason}</p>
|
|
<button
|
|
onClick={() => router.push('/auth/login')}
|
|
className="px-4 py-2 bg-primary-blue text-white rounded hover:bg-blue-600 transition-colors"
|
|
>
|
|
Go to Login
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 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(),
|
|
};
|
|
} |