Added Chat Page
This commit is contained in:
@@ -4,6 +4,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import useSWR from 'swr';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
@@ -22,10 +23,37 @@ interface NavItem {
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||
|
||||
export default function DashboardLayout({ user, children }: DashboardLayoutProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showNotifications, setShowNotifications] = useState(false);
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
|
||||
// Récupérer les conversations pour compter les messages non lus
|
||||
const { data: conversations } = useSWR<Array<{ unreadCount: number }>>(
|
||||
'/api/conversations',
|
||||
fetcher,
|
||||
{
|
||||
refreshInterval: 3000, // Rafraîchir toutes les 3 secondes
|
||||
}
|
||||
);
|
||||
|
||||
// Calculer le nombre total de messages non lus
|
||||
const totalUnreadCount = conversations?.reduce((sum, conv) => sum + (conv.unreadCount || 0), 0) || 0;
|
||||
|
||||
const getUserInitials = () => {
|
||||
if (user.name) {
|
||||
const names = user.name.split(' ');
|
||||
if (names.length >= 2) {
|
||||
return `${names[0].charAt(0)}${names[1].charAt(0)}`.toUpperCase();
|
||||
}
|
||||
return user.name.charAt(0).toUpperCase();
|
||||
}
|
||||
return user.email.charAt(0).toUpperCase();
|
||||
};
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
@@ -78,9 +106,11 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
label: 'Messagerie',
|
||||
href: '/dashboard/messagerie',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
||||
</svg>
|
||||
<div className="relative">
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
||||
</svg>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -92,6 +122,15 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Archives',
|
||||
href: '/dashboard/archives',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const handleLogout = async () => {
|
||||
@@ -131,22 +170,28 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
<ul className="space-y-1">
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
const isMessagerie = item.href === '/dashboard/messagerie';
|
||||
const showBadge = isMessagerie && totalUnreadCount > 0;
|
||||
|
||||
return (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors relative ${
|
||||
isActive
|
||||
? 'bg-lblue text-white'
|
||||
: 'text-gray-700 hover:bg-lblue/10'
|
||||
}`}
|
||||
>
|
||||
<span className={isActive ? 'text-white' : 'text-lblue'}>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className={`text-sm font-medium ${isActive ? '' : ''}`}>
|
||||
<span className={isActive ? 'text-white' : 'text-lblue'}>{item.icon}</span>
|
||||
<span className={`text-sm font-medium flex-1 ${isActive ? '' : ''}`}>
|
||||
{item.label}
|
||||
</span>
|
||||
{showBadge && (
|
||||
<span className="absolute top-2 right-2 w-5 h-5 bg-red-500 text-white text-xs font-semibold rounded-full flex items-center justify-center">
|
||||
{totalUnreadCount > 99 ? '99+' : totalUnreadCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
@@ -168,21 +213,101 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-gray-900"></h1>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Notification Icon */}
|
||||
<button className="relative w-10 h-10 rounded-full bg-[#6B46C1] flex items-center justify-center hover:bg-[#5B21B6] transition-colors">
|
||||
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></span>
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Notification Button */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowNotifications(!showNotifications)}
|
||||
className="relative w-10 h-10 rounded-lg bg-gray-50 hover:bg-gray-100 border border-gray-200 flex items-center justify-center transition-all duration-200 hover:shadow-sm group"
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-600 group-hover:text-gray-900 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
{/* Badge de notification */}
|
||||
<span className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Notifications */}
|
||||
{showNotifications && (
|
||||
<div className="absolute right-0 top-12 w-80 bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Notifications</h3>
|
||||
</div>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
<div className="p-4 text-center text-sm text-gray-500">
|
||||
Aucune notification
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Profile Avatar */}
|
||||
<button className="w-10 h-10 rounded-full bg-[#6B46C1] flex items-center justify-center hover:bg-[#5B21B6] transition-colors">
|
||||
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 border border-gray-200 transition-all duration-200 hover:shadow-sm group"
|
||||
>
|
||||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-lblue to-dblue flex items-center justify-center shadow-sm">
|
||||
<span className="text-white text-sm font-semibold">{getUserInitials()}</span>
|
||||
</div>
|
||||
<div className="hidden md:block text-left">
|
||||
<div className="text-sm font-medium text-gray-900">{user.name || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-500">{user.email}</div>
|
||||
</div>
|
||||
<svg className="w-4 h-4 text-gray-400 group-hover:text-gray-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Profile Menu */}
|
||||
{showProfileMenu && (
|
||||
<div className="absolute right-0 top-14 w-56 bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="text-sm font-medium text-gray-900">{user.name || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-500 mt-1">{user.email}</div>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowProfileMenu(false);
|
||||
// TODO: Navigate to profile page
|
||||
}}
|
||||
className="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 rounded-lg transition-colors flex items-center gap-2"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
Mon profil
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowProfileMenu(false);
|
||||
// TODO: Navigate to settings
|
||||
}}
|
||||
className="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 rounded-lg transition-colors flex items-center gap-2"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Paramètres
|
||||
</button>
|
||||
<div className="border-t border-gray-200 my-1"></div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
disabled={loading}
|
||||
className="w-full px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors flex items-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
<svg className="w-4 h-4" 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>
|
||||
Déconnexion
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -192,6 +317,17 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Overlay pour fermer les menus */}
|
||||
{(showNotifications || showProfileMenu) && (
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => {
|
||||
setShowNotifications(false);
|
||||
setShowProfileMenu(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user