NeahOpti/components/main-nav.tsx
2025-04-22 12:49:37 +02:00

398 lines
14 KiB
TypeScript

"use client";
import { useState } 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';
const requestNotificationPermission = async () => {
try {
const permission = await Notification.requestPermission();
return permission === "granted";
} catch (error) {
console.error("Error requesting notification permission:", error);
return false;
}
};
export function MainNav() {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const { data: session, status } = useSession();
const [userStatus, setUserStatus] = useState<'online' | 'busy' | 'away'>('online');
console.log("Session:", session);
console.log("Status:", status);
// Updated function to get user initials
const getUserInitials = () => {
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 "?";
};
// Function to get display name
const getDisplayName = () => {
return session?.user?.name || "User";
};
// Function to get user role
const getUserRole = () => {
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 "";
};
// Function to check if user has a specific role
const hasRole = (requiredRoles: string[]) => {
if (!session?.user?.role) {
console.log('No user roles found');
return false;
}
const userRoles = Array.isArray(session.user.role) ? session.user.role : [session.user.role];
console.log('Raw user roles:', userRoles);
// 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()
);
console.log('Clean user roles:', cleanUserRoles);
// Clean required roles
const cleanRequiredRoles = requiredRoles.map(role => role.toLowerCase());
console.log('Clean required roles:', cleanRequiredRoles);
// Check if user has any of the required roles
const hasAnyRole = cleanRequiredRoles.some(role => cleanUserRoles.includes(role));
console.log('Has any role:', hasAnyRole);
return hasAnyRole;
};
// Status configurations
const statusConfig = {
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 = 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 = [
{
title: "QG",
icon: Target,
href: '/qg',
},
];
// Role-specific menu items
const roleSpecificItems = [
{
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 = [
...baseMenuItems,
...roleSpecificItems.filter(item => hasRole(item.requiredRoles))
];
// Format current date and time
const now = new Date();
const formattedDate = format(now, "d MMMM yyyy", { locale: fr });
const formattedTime = format(now, "HH:mm");
return (
<>
<div className="fixed top-0 left-0 right-0 z-50 bg-black">
<div className="flex items-center justify-between px-4 py-1">
{/* Left side */}
<div className="flex items-center space-x-4">
<button
onClick={() => setIsSidebarOpen(true)}
className="text-white/80 hover:text-white"
>
<Menu className="w-5 h-5" />
</button>
<Link href='/'>
<Image
src='/Neahv2 logo W.png'
alt='Neah Logo'
width={40}
height={13}
className='text-white'
/>
</Link>
<Link href='/agenda' className='text-white/80 hover:text-white'>
<Calendar className='w-5 h-5' />
</Link>
<Link href='/timetracker' className='text-white/80 hover:text-white'>
<Clock className='w-5 h-5' />
<span className="sr-only">TimeTracker</span>
</Link>
<Link href='/notes' className='text-white/80 hover:text-white'>
<PenLine className='w-5 h-5' />
<span className="sr-only">Notes</span>
</Link>
<Link href='/alma' className='text-white/80 hover:text-white'>
<Robot className='w-5 h-5' />
<span className="sr-only">ALMA</span>
</Link>
<Link href='/vision' className='text-white/80 hover:text-white'>
<Video className='w-5 h-5' />
<span className="sr-only">Vision</span>
</Link>
<Link href='/observatory' className='text-white/80 hover:text-white'>
<Telescope className='w-5 h-5' />
<span className="sr-only">Observatory</span>
</Link>
<Link href='/radio' className='text-white/80 hover:text-white'>
<RadioIcon className='w-5 h-5' />
<span className="sr-only">Radio</span>
</Link>
<Link href='/announcement' className='text-white/80 hover:text-white'>
<Megaphone className='w-5 h-5' />
<span className="sr-only">Announcement</span>
</Link>
</div>
{/* Right side */}
<div className="flex items-center space-x-8">
{/* Date and Time with smaller text */}
<div className="text-white/80 text-sm">
<span className="mr-2">{formattedDate}</span>
<span>{formattedTime}</span>
</div>
<Link
href='/notifications'
className='text-white/80 hover:text-white'
>
<Bell className='w-5 h-5' />
</Link>
{status === "authenticated" && session?.user ? (
<DropdownMenu>
<DropdownMenuTrigger className="outline-none">
<div className="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white cursor-pointer hover:bg-blue-700 transition-colors">
{getUserInitials()}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 bg-black/90 border-gray-700">
<DropdownMenuLabel className="text-white/80">
<div className="flex items-center justify-between">
<span>{getDisplayName()}</span>
<DropdownMenu>
<DropdownMenuTrigger className="outline-none">
<div className="flex items-center space-x-1 text-sm">
<Circle className={`h-3 w-3 ${statusConfig[userStatus].color}`} />
<span className="text-gray-400">{statusConfig[userStatus].label}</span>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="bg-black/90 border-gray-700">
<DropdownMenuItem
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={() => handleStatusChange('online')}
>
<Circle className="h-3 w-3 text-green-500 mr-2" />
<span>Online</span>
</DropdownMenuItem>
<DropdownMenuItem
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={() => handleStatusChange('busy')}
>
<Circle className="h-3 w-3 text-orange-500 mr-2" />
<span>Busy</span>
</DropdownMenuItem>
<DropdownMenuItem
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={() => handleStatusChange('away')}
>
<Circle className="h-3 w-3 text-gray-500 mr-2" />
<span>Away</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator className="bg-gray-700" />
{visibleMenuItems.map((item) => (
<DropdownMenuItem
key={item.title}
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={() => window.location.href = item.href}
>
<item.icon className="mr-2 h-4 w-4" />
<span>{item.title}</span>
</DropdownMenuItem>
))}
<DropdownMenuItem
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={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';
}
}}
>
<LogOut className="mr-2 h-4 w-4" />
<span>Déconnexion</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<div className='cursor-pointer text-white/80 hover:text-white'>
<span onClick={() => signIn("keycloak", { callbackUrl: "/" })}>
Login
</span>
</div>
)}
</div>
</div>
</div>
<Sidebar isOpen={isSidebarOpen} onClose={() => setIsSidebarOpen(false)} />
</>
);
}