All checks were successful
		
		
	
	PR Build Check / build (pull_request) Successful in 2m20s
				
			
		
			
				
	
	
		
			186 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 'use client';
 | |
| import { useEffect, useState } from 'react';
 | |
| import { useDispatch, useSelector } from 'react-redux';
 | |
| import Link from 'next/link';
 | |
| import { IRootState } from '@/store';
 | |
| import { toggleTheme, toggleSidebar } from '@/store/themeConfigSlice';
 | |
| import Image from 'next/image';
 | |
| import Dropdown from '@/components/dropdown';
 | |
| import IconMenu from '@/components/icon/icon-menu';
 | |
| import IconSun from '@/components/icon/icon-sun';
 | |
| import IconMoon from '@/components/icon/icon-moon';
 | |
| import IconUser from '@/components/icon/icon-user';
 | |
| import IconLockDots from '@/components/icon/icon-lock-dots';
 | |
| import IconLogout from '@/components/icon/icon-logout';
 | |
| import { usePathname, useRouter } from 'next/navigation';
 | |
| 
 | |
| type UserData = { id: string; email: string; is_active: boolean };
 | |
| 
 | |
| export default function Header() {
 | |
|   const pathname = usePathname();
 | |
|   const dispatch = useDispatch();
 | |
|   const router = useRouter();
 | |
|   const themeConfig = useSelector((state: IRootState) => state.themeConfig);
 | |
|   const isRtl = themeConfig.rtlClass === 'rtl';
 | |
| 
 | |
|   const [user, setUser] = useState<UserData | null>(null);
 | |
|   const [loadingUser, setLoadingUser] = useState(true);
 | |
| 
 | |
|   const API = process.env.NEXT_PUBLIC_FASTAPI_URL || 'http://127.0.0.1:8000';
 | |
| 
 | |
|   // highlight active menu
 | |
|   useEffect(() => {
 | |
|     const selector = document.querySelector(
 | |
|       `ul.horizontal-menu a[href="${window.location.pathname}"]`
 | |
|     );
 | |
|     if (selector) {
 | |
|       document
 | |
|         .querySelectorAll('ul.horizontal-menu .nav-link.active, ul.horizontal-menu a.active')
 | |
|         .forEach((el) => el.classList.remove('active'));
 | |
|       selector.classList.add('active');
 | |
|       const ul: any = selector.closest('ul.sub-menu');
 | |
|       if (ul) {
 | |
|         const ele: any = ul.closest('li.menu')?.querySelector('.nav-link');
 | |
|         setTimeout(() => ele?.classList.add('active'));
 | |
|       }
 | |
|     }
 | |
|   }, [pathname]);
 | |
| 
 | |
|   async function loadUser(signal?: AbortSignal) {
 | |
|   try {
 | |
|     const res = await fetch(`${API}/auth/me`, {
 | |
|       credentials: 'include',
 | |
|       cache: 'no-store',
 | |
|       signal,
 | |
|     });
 | |
|     if (!res.ok) throw new Error();
 | |
|     const data = await res.json().catch(() => null);
 | |
|     setUser(data?.id ? (data as UserData) : null);
 | |
|   } catch {
 | |
|     setUser(null);
 | |
|   } finally {
 | |
|     setLoadingUser(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
|   useEffect(() => {
 | |
|     setLoadingUser(true);
 | |
|     const controller = new AbortController();
 | |
|     loadUser(controller.signal);
 | |
|     return () => controller.abort();
 | |
|   }, [pathname, API]);
 | |
| 
 | |
|   const handleLogout = async () => {
 | |
|     try {
 | |
|     await fetch(`${API}/auth/logout`, {
 | |
|       method: 'POST',
 | |
|       credentials: 'include',
 | |
|       cache: 'no-store',
 | |
|     });
 | |
|   } catch (_) {
 | |
|     // ignore
 | |
|   } finally {
 | |
|     setUser(null);
 | |
|     window.location.href = '/login';
 | |
|   }
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <header className={`z-40 ${themeConfig.semidark && themeConfig.menu === 'horizontal' ? 'dark' : ''}`}>
 | |
|       <div className="shadow-sm">
 | |
|         <div className="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-rtgray-900">
 | |
|           {/* Logo + mobile toggler */}
 | |
|           <div className="horizontal-logo flex items-center justify-between ltr:mr-2 rtl:ml-2 lg:hidden">
 | |
|             <div className="relative h-10 w-32 sm:h-11 sm:w-36 md:h-12 md:w-27 shrink-0 max-h-12">
 | |
|               <Image
 | |
|                 src="/assets/images/newfulllogo.png"
 | |
|                 alt="logo"
 | |
|                 fill
 | |
|                 className="object-cover"
 | |
|                 priority
 | |
|                 sizes="(max-width: 640px) 8rem, (max-width: 768px) 9rem, (max-width: 1024px) 10rem, 10rem"
 | |
|               />
 | |
|             </div>
 | |
| 
 | |
|             <button
 | |
|               type="button"
 | |
|               onClick={() => dispatch(toggleSidebar())}
 | |
|               className="collapse-icon flex p-2 rounded-full hover:bg-rtgray-200 dark:text-white dark:hover:bg-rtgray-700"
 | |
|             >
 | |
|               <IconMenu className="h-6 w-6" />
 | |
|             </button>
 | |
|           </div>
 | |
| 
 | |
|           {/* Right-side actions */}
 | |
|           <div className="flex items-center justify-end space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] lg:space-x-2">
 | |
|             {/* Theme toggle */}
 | |
|             {themeConfig.theme === 'light' ? (
 | |
|               <button
 | |
|                 onClick={() => dispatch(toggleTheme('dark'))}
 | |
|                 className="flex items-center p-2 rounded-full bg-white-light/40 hover:bg-white-light/90 dark:bg-rtgray-800 dark:hover:bg-rtgray-700"
 | |
|               >
 | |
|                 <IconSun />
 | |
|               </button>
 | |
|             ) : (
 | |
|               <button
 | |
|                 onClick={() => dispatch(toggleTheme('light'))}
 | |
|                 className="flex items-center p-2 rounded-full bg-white-light/40 hover:bg-white-light/90 dark:bg-rtgray-800 dark:hover:bg-rtgray-700"
 | |
|               >
 | |
|                 <IconMoon />
 | |
|               </button>
 | |
|             )}
 | |
| 
 | |
|             {/* User dropdown */}
 | |
|             <div className="dropdown flex shrink-0">
 | |
|               {loadingUser ? (
 | |
|                 <div className="h-9 w-9 rounded-full animate-pulse bg-gray-300 dark:bg-rtgray-800" />
 | |
|               ) : user ? (
 | |
|                 <Dropdown
 | |
|                   placement={isRtl ? 'bottom-start' : 'bottom-end'}
 | |
|                   btnClassName="relative group block"
 | |
|                   panelClassName="rounded-lg shadow-lg border border-white/10 bg-rtgray-100 dark:bg-rtgray-800 p-2"
 | |
|                   button={
 | |
|                     <div className="h-9 w-9 rounded-full bg-rtgray-200 dark:bg-rtgray-800 flex items-center justify-center group-hover:bg-rtgray-300 dark:group-hover:bg-rtgray-700">
 | |
|                       <IconUser className="h-5 w-5 text-gray-600 dark:text-gray-300" />
 | |
|                     </div>
 | |
|                   }
 | |
|                 >
 | |
|                   <ul className="w-[230px] font-semibold text-dark">
 | |
|                     <li className="px-4 py-4 flex items-center">
 | |
|                       <div className="truncate ltr:pl-1.5 rtl:pr-4">
 | |
|                         <h4 className="text-sm text-left">{user.email}</h4>
 | |
|                       </div>
 | |
|                     </li>
 | |
|                     <li>
 | |
|                       <Link href="/users/profile" className="dark:hover:text-white">
 | |
|                         <IconUser className="h-4.5 w-4.5 mr-2" /> Profile
 | |
|                       </Link>
 | |
|                     </li>
 | |
|                     <li>
 | |
|                       <Link href="/auth/boxed-lockscreen" className="dark:hover:text-white">
 | |
|                         <IconLockDots className="h-4.5 w-4.5 mr-2" /> Lock Screen
 | |
|                       </Link>
 | |
|                     </li>
 | |
|                     <li className="border-t border-white-light dark:border-white-light/10">
 | |
|                       <button onClick={handleLogout} className="flex w-full items-center py-3 text-danger">
 | |
|                         <IconLogout className="h-4.5 w-4.5 mr-2 rotate-90" /> Sign Out
 | |
|                       </button>
 | |
|                     </li>
 | |
|                   </ul>
 | |
|                 </Dropdown>
 | |
|               ) : (
 | |
|                 <Link
 | |
|                   href="/login"
 | |
|                   className="rounded-md bg-yellow-400 px-3 py-1.5 text-black font-semibold hover:brightness-95"
 | |
|                 >
 | |
|                   Sign In
 | |
|                 </Link>
 | |
|               )}
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
|     </header>
 | |
|   );
 | |
| }
 |