"use client"; import { useState, useCallback, useMemo, memo } from "react"; import { Calendar, MessageSquare, BotIcon as Robot, Bell, Users, LogOut, UserCog, Clock, PenLine, Video, Radio as RadioIcon, Megaphone, Heart, Target, Mail, Telescope, Lightbulb, Circle, Menu, } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { Sidebar } from "./sidebar"; import { useSession, signIn, signOut } from "next-auth/react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { format } from 'date-fns'; import { fr } from 'date-fns/locale'; import { NotificationBadge } from './notification-badge'; import { NotesDialog } from './notes-dialog'; import { useUnreadAnnouncements } from '@/hooks/use-unread-announcements'; const requestNotificationPermission = async () => { try { const permission = await Notification.requestPermission(); return permission === "granted"; } catch (error) { console.error("Error requesting notification permission:", error); return false; } }; // Use React.memo to memoize the entire component export const MainNav = memo(function MainNav() { const [isSidebarOpen, setIsSidebarOpen] = useState(false); const { data: session, status } = useSession(); const [userStatus, setUserStatus] = useState<'online' | 'busy' | 'away'>('online'); const [isNotesDialogOpen, setIsNotesDialogOpen] = useState(false); // Use the unread announcements hook with memo to prevent unnecessary re-renders const { hasUnread } = useUnreadAnnouncements(); // Updated function to get user initials - memoize this const getUserInitials = useCallback(() => { if (session?.user?.name) { // Split the full name and get initials const names = session.user.name.split(' '); if (names.length >= 2) { return `${names[0][0]}${names[names.length - 1][0]}`.toUpperCase(); } // If only one name, use first two letters return names[0].slice(0, 2).toUpperCase(); } return "?"; }, [session?.user?.name]); // Function to get display name - memoize this const getDisplayName = useCallback(() => { return session?.user?.name || "User"; }, [session?.user?.name]); // Function to get user role - memoize this const getUserRole = useCallback(() => { if (session?.user?.role) { if (Array.isArray(session.user.role)) { // Filter out technical roles and format remaining ones return session.user.role .filter(role => !['offline_access', 'uma_authorization', 'default-roles-cercle'].includes(role) ) .map(role => { // Transform role names switch(role) { case 'ROLE_Mentors': return 'Mentor'; case 'ROLE_apprentice': return 'Apprentice'; case 'ROLE_Admin': return 'Admin'; default: return role.replace('ROLE_', ''); } }) .join(', '); } return session.user.role; } return ""; }, [session?.user?.role]); // Function to check if user has a specific role - memoize this const hasRole = useCallback((requiredRoles: string[]) => { if (!session?.user?.role) { return false; } const userRoles = Array.isArray(session.user.role) ? session.user.role : [session.user.role]; // Clean up user roles by removing prefixes and converting to lowercase const cleanUserRoles = userRoles.map(role => role.replace(/^[\/]/, '') // Remove leading slash .replace(/^ROLE_/, '') // Remove ROLE_ prefix .toLowerCase() ); // Clean required roles const cleanRequiredRoles = requiredRoles.map(role => role.toLowerCase()); // Check if user has any of the required roles const hasAnyRole = cleanRequiredRoles.some(role => cleanUserRoles.includes(role)); return hasAnyRole; }, [session?.user?.role]); // Status configurations const statusConfig = useMemo(() => ({ online: { color: 'text-green-500', label: 'Online', notifications: true }, busy: { color: 'text-orange-500', label: 'Busy', notifications: false }, away: { color: 'text-gray-500', label: 'Away', notifications: false }, }), []); // Handle status change const handleStatusChange = useCallback(async (newStatus: 'online' | 'busy' | 'away') => { setUserStatus(newStatus); if (newStatus !== 'online') { // If status is busy or away, check and request notification permission if needed const hasPermission = await requestNotificationPermission(); if (hasPermission) { // Disable notifications if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.ready; await registration.pushManager.getSubscription()?.then(subscription => { if (subscription) { subscription.unsubscribe(); } }); } } } else { // Re-enable notifications if going back online requestNotificationPermission(); } }, []); // Base menu items (available for everyone) const baseMenuItems = useMemo(() => [ { title: "QG", icon: Target, href: '/qg', }, ], []); // Role-specific menu items const roleSpecificItems = useMemo(() => [ { title: "ShowCase", icon: Lightbulb, href: '/showcase', requiredRoles: ["expression"], }, { title: "Equipes", icon: UserCog, href: '/equipes', requiredRoles: ["admin", "entrepreneurship"], }, { title: "TheMessage", icon: Mail, href: '/the-message', requiredRoles: ["mediation", "expression"], }, ], []); // Get visible menu items based on user roles const visibleMenuItems = useMemo(() => [ ...baseMenuItems, ...roleSpecificItems.filter(item => hasRole(item.requiredRoles)) ], [baseMenuItems, roleSpecificItems, hasRole]); // Format current date and time const dateTimeDisplay = useMemo(() => { const now = new Date(); const formattedDate = format(now, "d MMMM yyyy", { locale: fr }); const formattedTime = format(now, "HH:mm"); return { formattedDate, formattedTime }; }, []); // Handle sidebar toggle const toggleSidebar = useCallback(() => { setIsSidebarOpen(prev => !prev); }, []); // Handle notes dialog const toggleNotesDialog = useCallback(() => { setIsNotesDialogOpen(prev => !prev); }, []); // Handle logout const handleLogout = useCallback(async () => { try { // First sign out from NextAuth await signOut({ callbackUrl: '/signin', redirect: false }); // Then redirect to Keycloak logout with proper parameters const keycloakLogoutUrl = new URL( `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout` ); // Add required parameters keycloakLogoutUrl.searchParams.append( 'post_logout_redirect_uri', window.location.origin ); keycloakLogoutUrl.searchParams.append( 'id_token_hint', session?.accessToken || '' ); // Redirect to Keycloak logout window.location.href = keycloakLogoutUrl.toString(); } catch (error) { console.error('Error during logout:', error); // Fallback to simple redirect if something goes wrong window.location.href = '/signin'; } }, [session?.accessToken]); return ( <>