SCGR's picture
runtime optimization
1dc6a03
import { useLocation, useNavigate } from "react-router-dom";
import { Button, PageContainer, Container } from "@ifrc-go/ui";
import {
UploadCloudLineIcon,
AnalysisIcon,
SearchLineIcon,
QuestionLineIcon,
GoMainIcon,
SettingsIcon,
ChevronDownLineIcon,
MenuLineIcon,
} from "@ifrc-go/icons";
import { useState, useEffect, useRef } from "react";
import styles from './HeaderNav.module.css';
declare global {
interface Window {
confirmNavigationIfNeeded?: (to: string) => void;
}
}
const navItems = [
{ to: "/upload", label: "Upload", Icon: UploadCloudLineIcon },
{ to: "/explore", label: "Explore", Icon: SearchLineIcon },
{ to: "/analytics", label: "Analytics", Icon: AnalysisIcon },
];
export default function HeaderNav() {
const location = useLocation();
const navigate = useNavigate();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<nav className="border-b border-gray-200 bg-white shadow-sm sticky top-0 z-50 backdrop-blur-sm bg-white/95">
<PageContainer
className="border-b-2 border-ifrcRed"
contentClassName="grid grid-cols-3 items-center py-6"
>
<div
className="flex items-center gap-4 min-w-0 cursor-pointer group transition-all duration-200 hover:scale-105"
onClick={() => {
// Prevent navigation to home when already on upload page
if (location.pathname === "/upload" || location.pathname === "/") {
return;
}
if (location.pathname === "/upload") {
if (window.confirmNavigationIfNeeded) {
window.confirmNavigationIfNeeded('/');
return;
}
if (!confirm("You have unsaved changes. Are you sure you want to leave?")) {
return;
}
}
navigate('/');
}}
>
<div className="p-2 rounded-lg bg-gradient-to-br from-ifrcRed/10 to-ifrcRed/20 group-hover:from-ifrcRed/20 group-hover:to-ifrcRed/30 transition-all duration-200">
<GoMainIcon className="h-8 w-8 flex-shrink-0 text-ifrcRed" />
</div>
<div className="flex flex-col">
<span className="font-bold text-xl text-gray-900 leading-tight">PromptAid Vision</span>
</div>
</div>
<div className="flex justify-center">
<nav className="flex items-center space-x-4 bg-gray-50/80 rounded-xl p-2 backdrop-blur-sm">
{navItems.map(({ to, label, Icon }) => {
const isActive = location.pathname === to ||
(to === '/upload' && location.pathname === '/') ||
(to === '/explore' && location.pathname.startsWith('/map/'));
const isUploadPage = location.pathname === "/upload" || location.pathname === "/";
const isUploadOrHomeNav = to === "/upload" || to === "/";
return (
<div key={to} className="relative">
<Container withInternalPadding className="p-2">
<Button
name={label.toLowerCase()}
variant={isActive ? "primary" : "tertiary"}
size={1}
className={`transition-all duration-200 ${
isActive
? 'shadow-lg shadow-ifrcRed/20 transform scale-105'
: 'hover:bg-white hover:shadow-md hover:scale-105'
}`}
onClick={() => {
if (isUploadPage && isUploadOrHomeNav) {
return;
}
if (location.pathname === "/upload") {
if (window.confirmNavigationIfNeeded) {
window.confirmNavigationIfNeeded(to);
return;
}
if (!confirm("You have unsaved changes. Are you sure you want to leave?")) {
return;
}
}
navigate(to);
}}
>
<Icon className={`w-4 h-4 transition-transform duration-200 ${
isActive ? 'scale-110' : 'group-hover:scale-110'
}`} />
<span className="inline ml-2 font-semibold">{label}</span>
</Button>
</Container>
{isActive && (
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-8 h-1 bg-ifrcRed rounded-full animate-pulse"></div>
)}
</div>
);
})}
</nav>
</div>
<div className="flex justify-end">
<div className={styles.dropdownContainer} ref={dropdownRef}>
<Container withInternalPadding className="p-2">
<Button
name="more-options"
variant={isDropdownOpen ? "primary" : "tertiary"}
size={1}
className="transition-all duration-200"
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
>
<MenuLineIcon className="w-4 h-4" />
</Button>
</Container>
{/* Enhanced Dropdown Menu */}
{isDropdownOpen && (
<div className={styles.dropdownMenu}>
<div className={styles.dropdownContent}>
<Container withInternalPadding className="p-2">
<Button
name="help-support"
variant="tertiary"
size={1}
className="w-full justify-start"
onClick={() => {
setIsDropdownOpen(false);
if (location.pathname === "/upload") {
if (window.confirmNavigationIfNeeded) {
window.confirmNavigationIfNeeded('/help');
return;
}
if (!confirm("You have unsaved changes. Are you sure you want to leave?")) {
return;
}
}
navigate('/help');
}}
>
<QuestionLineIcon className="w-4 h-4" />
<span className="ml-2 font-semibold">Help & Support</span>
</Button>
</Container>
<Container withInternalPadding className="p-2">
<Button
name="dev"
variant="tertiary"
size={1}
className="w-full justify-start"
onClick={() => {
setIsDropdownOpen(false);
if (location.pathname === "/upload") {
if (window.confirmNavigationIfNeeded) {
window.confirmNavigationIfNeeded('/admin');
return;
}
if (!confirm("You have unsaved changes. Are you sure you want to leave?")) {
return;
}
}
navigate('/admin');
}}
>
<SettingsIcon className="w-4 h-4" />
<span className="ml-2 font-semibold">Dev</span>
</Button>
</Container>
</div>
</div>
)}
</div>
</div>
</PageContainer>
</nav>
);
}