Gankit12's picture
Res2
fc91a5e
Raw
History Blame Contribute Delete
10.7 kB
/**
* Header - Top navigation bar with logo, navigation links,
* language selector, and user profile.
*
* Features a subtle gradient background and responsive
* hamburger menu on mobile.
*/
import { useState, useCallback } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import {
Bars3Icon,
XMarkIcon,
UserCircleIcon,
ArrowRightOnRectangleIcon,
Squares2X2Icon,
} from "@heroicons/react/24/outline";
import { ROUTES, APP_NAME } from "@utils/constants";
import { useAuth } from "@context/AuthContext";
import LanguageSelector from "./LanguageSelector";
function Header({ language, onLanguageChange }) {
const { t } = useTranslation();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const { isAuthenticated, user, logout } = useAuth();
const NAV_ITEMS = [
{ label: t("nav.home"), path: ROUTES.HOME },
{ label: t("nav.diseaseDetection"), path: ROUTES.DISEASE_DETECTION },
{ label: t("nav.weather"), path: ROUTES.WEATHER },
{ label: t("nav.apmcPrice"), path: ROUTES.APMC },
{ label: t("nav.schemes"), path: ROUTES.SCHEMES },
];
const toggleMobile = useCallback(() => {
setMobileMenuOpen((prev) => !prev);
}, []);
const closeMobile = useCallback(() => {
setMobileMenuOpen(false);
}, []);
const handleLogout = useCallback(() => {
logout();
navigate(ROUTES.HOME);
closeMobile();
}, [logout, navigate, closeMobile]);
return (
<header
className="sticky top-0 z-30 border-b border-neutral-200 bg-white/95 backdrop-blur-sm transition-colors duration-300"
id="app-header"
>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<Link
to={ROUTES.HOME}
className="flex items-center gap-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded-lg"
aria-label={`${APP_NAME} home`}
>
<span className="inline-flex items-center justify-center h-8 w-8 rounded-lg bg-gradient-header text-white font-display font-bold text-sm shadow-sm">
KN
</span>
<span className="text-lg font-display font-bold text-primary-700 hidden sm:block">
{APP_NAME}
</span>
</Link>
{/* Desktop Navigation */}
<nav
className="hidden md:flex items-center gap-1"
aria-label="Main navigation"
id="main-nav"
>
{NAV_ITEMS.map((item) => {
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
className={[
"px-3 py-2 rounded-lg text-sm font-medium transition-colors",
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
isActive
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100",
].join(" ")}
aria-current={isActive ? "page" : undefined}
>
{item.label}
</Link>
);
})}
</nav>
{/* Right actions */}
<div className="flex items-center gap-1.5">
{/* Language selector */}
{onLanguageChange && (
<LanguageSelector
currentLanguage={language}
onLanguageChange={onLanguageChange}
/>
)}
{/* User Dashboard / Profile / Login */}
<div className="hidden md:flex items-center gap-1.5">
{isAuthenticated && user ? (
<>
<Link
to={ROUTES.DASHBOARD}
className={[
"inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
location.pathname === ROUTES.DASHBOARD
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100",
].join(" ")}
>
<Squares2X2Icon className="h-5 w-5" aria-hidden="true" />
{t("nav.dashboard")}
</Link>
<Link
to={ROUTES.PROFILE}
className={[
"inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
location.pathname === ROUTES.PROFILE
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100",
].join(" ")}
title={t("nav.profile")}
>
<UserCircleIcon className="h-5 w-5" aria-hidden="true" />
<span className="max-w-[100px] truncate">{user.name?.split(" ")[0]}</span>
</Link>
<button
type="button"
onClick={handleLogout}
className="inline-flex items-center justify-center rounded-lg p-2 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
aria-label={t("nav.logout")}
title={t("nav.logout")}
>
<ArrowRightOnRectangleIcon className="h-5 w-5" aria-hidden="true" />
</button>
</>
) : (
<Link
to={ROUTES.LOGIN}
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium bg-primary-600 text-white hover:bg-primary-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
>
{t("nav.login")}
</Link>
)}
</div>
{/* Mobile menu button */}
<button
type="button"
onClick={toggleMobile}
className="md:hidden inline-flex items-center justify-center rounded-lg p-2 text-neutral-600 hover:bg-neutral-100 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu"
aria-label={t("header.toggleMenu")}
>
{mobileMenuOpen ? (
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
) : (
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
)}
</button>
</div>
</div>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<nav
id="mobile-menu"
className="md:hidden border-t border-neutral-200 bg-white animate-slide-down"
aria-label="Mobile navigation"
>
<div className="px-4 py-3 space-y-1">
{NAV_ITEMS.map((item) => {
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
onClick={closeMobile}
className={[
"block px-3 py-2 rounded-lg text-sm font-medium transition-colors",
isActive
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100",
].join(" ")}
aria-current={isActive ? "page" : undefined}
>
{item.label}
</Link>
);
})}
{/* Mobile Auth Links */}
<div className="border-t border-neutral-200 mt-2 pt-2">
{isAuthenticated && user ? (
<>
<Link
to={ROUTES.DASHBOARD}
onClick={closeMobile}
className={[
"flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
location.pathname === ROUTES.DASHBOARD
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100",
].join(" ")}
>
<Squares2X2Icon className="h-5 w-5" />
{t("nav.dashboard")}
</Link>
<Link
to={ROUTES.PROFILE}
onClick={closeMobile}
className={[
"flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
location.pathname === ROUTES.PROFILE
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100",
].join(" ")}
>
<UserCircleIcon className="h-5 w-5" />
{t("nav.profile")}
</Link>
<button
type="button"
onClick={handleLogout}
className="flex items-center gap-2 w-full px-3 py-2 rounded-lg text-sm font-medium text-neutral-600 hover:bg-neutral-100 transition-colors"
>
<ArrowRightOnRectangleIcon className="h-5 w-5" />
{t("nav.logout")}
</button>
</>
) : (
<Link
to={ROUTES.LOGIN}
onClick={closeMobile}
className="block px-3 py-2 rounded-lg text-sm font-medium bg-primary-600 text-white text-center hover:bg-primary-700 transition-colors"
>
{t("nav.login")}
</Link>
)}
</div>
</div>
</nav>
)}
</header>
);
}
Header.propTypes = {
language: PropTypes.string,
onLanguageChange: PropTypes.func,
};
export default Header;