Sacha VAUDEY d36f6f48e8
Some checks failed
Build / build-check (pull_request) Failing after 57s
update to Tailwind v4
2025-09-13 22:26:20 +02:00

173 lines
5.5 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
import { mergeClasses as cn } from '@/lib/styles/designSystem';
import { useTranslation } from '@/lib/hooks/useTranslation';
import type { AutheliaUser } from '@/lib/services/auth';
interface UserProfileProps {
user: AutheliaUser;
onLogout: () => void;
className?: string;
}
export const UserProfile: React.FC<UserProfileProps> = ({
user,
onLogout,
className,
}) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const { t } = useTranslation();
const defaultAvatarSmall = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=0ea5e9&color=fff&size=32`;
const defaultAvatarLarge = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=0ea5e9&color=fff&size=40`;
const avatarSrc = user.avatar ?? defaultAvatarSmall;
const avatarLargeSrc = user.avatar ?? defaultAvatarLarge;
// Fermer la popup en cliquant à l'extérieur
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
buttonRef.current &&
!dropdownRef.current.contains(event.target as Node) &&
!buttonRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleLogout = async () => {
setIsOpen(false);
onLogout();
};
return (
<div className={cn('relative', className)}>
{/* Bouton Avatar */}
<button
ref={buttonRef}
onClick={() => setIsOpen(!isOpen)}
className={cn(
'flex items-center justify-center w-8 h-8 rounded-full overflow-hidden',
'border-2 border-transparent hover:border-primary-300',
'transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
isOpen && 'ring-2 ring-primary-500 ring-offset-2'
)}
aria-label={t.user.userMenu}
>
<Image
src={avatarSrc}
alt={`Avatar de ${user.name}`}
className="w-full h-full object-cover rounded-full"
width={32}
height={32}
/>
</button>
{/* Popup Menu */}
{isOpen && (
<div
ref={dropdownRef}
className={cn(
'absolute right-0 top-full mt-2 w-64 bg-white rounded-lg shadow-lg',
'border border-gray-200 z-50',
'transform opacity-0 scale-95 animate-in fade-in-0 zoom-in-95',
'duration-200'
)}
style={{
animation: 'fadeInScale 0.2s ease-out forwards',
}}
>
{/* Header avec informations utilisateur */}
<div className="px-4 py-3 border-b border-gray-200">
<div className="flex items-center space-x-3">
<Image
src={avatarLargeSrc}
alt={`Avatar de ${user.name}`}
className="w-10 h-10 rounded-full object-cover"
width={40}
height={40}
/>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">
{user.name}
</p>
<p className="text-xs text-gray-500 truncate">
{user.email}
</p>
</div>
</div>
</div>
{/* Groupes utilisateur (si disponibles) */}
{user.groups && user.groups.length > 0 && (
<div className="px-4 py-3 border-b border-gray-200">
<p className="text-xs font-medium text-gray-700 mb-2">{t.user.groups}</p>
<div className="flex flex-wrap gap-1">
{user.groups.map((group, index) => (
<span
key={index}
className={cn(
'inline-flex px-2 py-1 text-xs font-medium rounded',
'bg-primary-100 text-primary-800'
)}
>
{group}
</span>
))}
</div>
</div>
)}
{/* Actions */}
<div className="py-2">
<button
onClick={handleLogout}
className={cn(
'w-full px-4 py-2 text-left text-sm text-gray-700',
'hover:bg-gray-100 transition-colors duration-150',
'flex items-center space-x-2'
)}
>
<svg
className="w-4 h-4 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
<span>{t.user.logout}</span>
</button>
</div>
</div>
)}
<style jsx>{`
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
`}</style>
</div>
);
};