![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.quebec/private_html/src/components/ |
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface User {
id: string;
name: string;
email?: string;
role?: string;
profilePicture?: string;
title?: string;
specialization?: string;
availability?: string;
lastActive?: string;
bio?: string;
}
interface UserAvatarProps {
user: User;
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
showName?: boolean;
showRole?: boolean;
showTooltip?: boolean;
showStatus?: boolean;
clickable?: boolean;
className?: string;
onClick?: () => void;
}
const sizeClasses = {
xs: 'w-6 h-6 text-xs',
sm: 'w-8 h-8 text-sm',
md: 'w-10 h-10 text-base',
lg: 'w-12 h-12 text-lg',
xl: 'w-16 h-16 text-xl'
};
const UserAvatar: React.FC<UserAvatarProps> = ({
user,
size = 'md',
showName = false,
showRole = false,
showTooltip = false,
showStatus = false,
clickable = false,
className = '',
onClick
}) => {
const [showProfileCard, setShowProfileCard] = useState(false);
const [imageError, setImageError] = useState(false);
const getRoleIcon = (role?: string) => {
switch (role) {
case 'ADMIN':
return '⚖️';
case 'LAWYER':
return '👩⚖️';
default:
return '👤';
}
};
const getRoleName = (role?: string) => {
switch (role) {
case 'ADMIN':
return 'Legal Team';
case 'LAWYER':
return 'Lawyer';
default:
return 'Client';
}
};
const getStatusColor = (availability?: string) => {
switch (availability) {
case 'Available':
return 'bg-green-400';
case 'Busy':
return 'bg-yellow-400';
case 'Away':
return 'bg-orange-400';
case 'Do Not Disturb':
return 'bg-red-400';
default:
return 'bg-gray-400';
}
};
const getInitials = (name: string) => {
return name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2);
};
const isOnline = (lastActive?: string) => {
if (!lastActive) return false;
const lastActiveDate = new Date(lastActive);
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
return lastActiveDate > fiveMinutesAgo;
};
const formatLastActive = (lastActive?: string) => {
if (!lastActive) return 'Unknown';
const date = new Date(lastActive);
const now = new Date();
const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
if (diffInMinutes < 1) return 'Just now';
if (diffInMinutes < 60) return `${diffInMinutes}m ago
if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}h ago
return date.toLocaleDateString();
};
const handleClick = () => {
if (onClick) {
onClick();
} else if (showTooltip) {
setShowProfileCard(!showProfileCard);
}
};
const avatarContent = (
<div className={`relative ${clickable ? 'cursor-pointer' : ''} ${className}
<div
className={
clickable ? 'hover:scale-105 transition-transform' : ''
}
onClick={handleClick}
>
{user.profilePicture && !imageError ? (
<img
src={user.profilePicture}
alt={user.name}
className="w-full h-full object-cover"
onError={() => setImageError(true)}
onLoad={() => setImageError(false)}
/>
) : (
<span className="text-white font-semibold">
{getInitials(user.name)}
</span>
)}
{/* Status indicator */}
{showStatus && (
<div className="absolute -bottom-0.5 -right-0.5">
<div
className={
isOnline(user.lastActive) ? 'bg-green-400' : getStatusColor(user.availability)
}
/>
</div>
)}
</div>
{/* Profile card tooltip */}
<AnimatePresence>
{showProfileCard && showTooltip && (
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 10 }}
className="absolute z-50 top-full mt-2 left-1/2 transform -translate-x-1/2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 w-64"
>
<div className="flex items-start space-x-3">
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center flex-shrink-0">
{user.profilePicture && !imageError ? (
<img
src={user.profilePicture}
alt={user.name}
className="w-full h-full object-cover"
onError={() => setImageError(true)}
onLoad={() => setImageError(false)}
/>
) : (
<span className="text-white font-semibold">
{getInitials(user.name)}
</span>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<h3 className="font-semibold text-gray-900 dark:text-white truncate">
{user.name}
</h3>
<span className="text-sm">{getRoleIcon(user.role)}</span>
</div>
{user.title && (
<p className="text-sm text-gray-600 dark:text-gray-400 truncate">
{user.title}
</p>
)}
{user.specialization && (
<p className="text-xs text-gray-500 dark:text-gray-500 truncate">
{user.specialization}
</p>
)}
<div className="flex items-center space-x-2 mt-2">
<div
className={
isOnline(user.lastActive) ? 'bg-green-400' : getStatusColor(user.availability)
}
/>
<span className="text-xs text-gray-500 dark:text-gray-500">
{isOnline(user.lastActive)
? 'Online'
: user.availability || formatLastActive(user.lastActive)
}
</span>
</div>
{user.bio && (
<p className="text-xs text-gray-600 dark:text-gray-400 mt-2 line-clamp-2">
{user.bio}
</p>
)}
</div>
</div>
{/* Close button */}
<button
onClick={() => setShowProfileCard(false)}
className="absolute top-2 right-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</motion.div>
)}
</AnimatePresence>
</div>
);
if (showName || showRole) {
return (
<div className={`flex items-center space-x-2 ${className}
{avatarContent}
<div className="flex-1 min-w-0">
{showName && (
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
{user.name}
</p>
)}
{showRole && (
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">
{getRoleIcon(user.role)} {getRoleName(user.role)}
</p>
)}
</div>
</div>
);
}
return avatarContent;
};
export default UserAvatar;