153 lines
5.2 KiB
TypeScript
Executable File
153 lines
5.2 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import React from 'react';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import LoadingSpinner from './ui/LoadingSpinner';
|
|
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
|
|
import { useEffect, useRef, useMemo } from 'react';
|
|
|
|
interface ProtectedLayoutProps {
|
|
children: React.ReactNode;
|
|
requiredRoles?: string[];
|
|
requiredPermissions?: string[];
|
|
}
|
|
|
|
const ProtectedLayout: React.FC<ProtectedLayoutProps> = ({
|
|
children,
|
|
requiredRoles = [],
|
|
requiredPermissions = []
|
|
}) => {
|
|
const { isAuthenticated, isLoading, user, hasRole, hasPermission } = useAuth();
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const pathname = usePathname();
|
|
const redirectedRef = useRef(false);
|
|
|
|
// Vérifier s'il y a un code d'autorisation dans l'URL
|
|
// Utiliser useMemo pour éviter les re-rendus inutiles
|
|
const hasAuthCode = useMemo(() => {
|
|
return searchParams.get('code') !== null && pathname === '/dashboard';
|
|
}, [searchParams, pathname]);
|
|
|
|
useEffect(() => {
|
|
console.log('🔍 ProtectedLayout useEffect:', {
|
|
isLoading,
|
|
isAuthenticated,
|
|
hasAuthCode,
|
|
pathname,
|
|
redirected: redirectedRef.current
|
|
});
|
|
|
|
// Ne rediriger qu'une seule fois pour éviter les boucles
|
|
if (redirectedRef.current) {
|
|
console.log('⏭️ ProtectedLayout: Redirection déjà effectuée, skip');
|
|
return;
|
|
}
|
|
|
|
if (!isLoading && !isAuthenticated && !hasAuthCode) {
|
|
// Marquer comme redirigé pour éviter les boucles
|
|
redirectedRef.current = true;
|
|
|
|
// Rediriger vers la page de connexion avec l'URL de retour
|
|
const searchParamsStr = searchParams.toString();
|
|
const currentPath = pathname + (searchParamsStr ? `?${searchParamsStr}` : '');
|
|
console.log('🔒 ProtectedLayout: Redirection vers /api/auth/login');
|
|
window.location.href = `/api/auth/login?redirect=${encodeURIComponent(currentPath)}`;
|
|
} else if (hasAuthCode) {
|
|
console.log('🔓 ProtectedLayout: Code d\'autorisation détecté, pas de redirection');
|
|
} else if (isAuthenticated) {
|
|
console.log('✅ ProtectedLayout: Utilisateur authentifié, pas de redirection');
|
|
} else if (isLoading) {
|
|
console.log('⏳ ProtectedLayout: Chargement en cours, pas de redirection');
|
|
}
|
|
}, [isAuthenticated, isLoading, hasAuthCode, pathname]);
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated && user) {
|
|
// Vérifier les rôles requis
|
|
if (requiredRoles.length > 0) {
|
|
const hasRequiredRole = requiredRoles.some(role => hasRole(role));
|
|
if (!hasRequiredRole) {
|
|
router.push('/');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Vérifier les permissions requises
|
|
if (requiredPermissions.length > 0) {
|
|
const hasRequiredPermission = requiredPermissions.some(permission => hasPermission(permission));
|
|
if (!hasRequiredPermission) {
|
|
router.push('/');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}, [isAuthenticated, user, requiredRoles, requiredPermissions, hasRole, hasPermission, router]);
|
|
|
|
// Affichage pendant le chargement
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<LoadingSpinner size="large" />
|
|
<p className="mt-4 text-gray-600">Chargement...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Si pas authentifié, vérifier s'il y a un code d'autorisation en cours
|
|
if (!isAuthenticated) {
|
|
if (hasAuthCode) {
|
|
console.log('🔓 ProtectedLayout: Autorisant le rendu car code d\'autorisation présent');
|
|
// Laisser le composant se charger pour traiter l'authentification
|
|
} else {
|
|
// Pas de code d'autorisation, afficher le message de redirection
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<LoadingSpinner size="large" />
|
|
<p className="mt-4 text-gray-600">Redirection vers la connexion...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
// Si authentifié mais pas les bonnes permissions, ne rien afficher (redirection en cours)
|
|
if (user) {
|
|
if (requiredRoles.length > 0) {
|
|
const hasRequiredRole = requiredRoles.some(role => hasRole(role));
|
|
if (!hasRequiredRole) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<LoadingSpinner size="large" />
|
|
<p className="mt-4 text-gray-600">Vérification des permissions...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (requiredPermissions.length > 0) {
|
|
const hasRequiredPermission = requiredPermissions.some(permission => hasPermission(permission));
|
|
if (!hasRequiredPermission) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<LoadingSpinner size="large" />
|
|
<p className="mt-4 text-gray-600">Vérification des permissions...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Afficher le contenu si tout est OK
|
|
return <>{children}</>;
|
|
};
|
|
|
|
export default ProtectedLayout;
|