mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
664 lines
26 KiB
TypeScript
664 lines
26 KiB
TypeScript
import { useState, useEffect, useMemo } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { useNavigate, useLocation } from "react-router-dom";
|
|
import { ExternalPortal } from "@/external-portal/components/External-Portal-Navigation/PortalHeader";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/shared/common/ui/select";
|
|
import { toast } from "sonner";
|
|
import {
|
|
ChevronDown,
|
|
FileText,
|
|
Search,
|
|
Info,
|
|
Menu,
|
|
X,
|
|
} from "lucide-react";
|
|
import { useUser } from "@/shared/context/UserContext";
|
|
import { useTenantConfig, resolveModuleConfig } from "@/layout/components/TenantConfig";
|
|
import {
|
|
UI_LANGUAGE_OPTIONS,
|
|
resolveUiLanguage,
|
|
} from "@/shared/i18n/uiLanguages";
|
|
import {
|
|
hasComplaintVerification,
|
|
isComplaintAuthContext,
|
|
} from "@/complaints/utils/complaintVerificationStorage";
|
|
import { COMPLAINT_RECORDS_PATH } from "@/complaints/utils/complaintRoutes";
|
|
import { useExternalPortalSession } from "@/shared/hooks/useExternalPortalSession";
|
|
|
|
interface NavItem {
|
|
id: string;
|
|
label: string;
|
|
path?: string;
|
|
requiresCompleteRegistration?: true;
|
|
children?: NavItem[];
|
|
}
|
|
|
|
const languageOptions = UI_LANGUAGE_OPTIONS;
|
|
|
|
const Header = () => {
|
|
const [activeTab, setActiveTab] = useState("overview");
|
|
const [scrolled, setScrolled] = useState(false);
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
const [servicesOpen, setServicesOpen] = useState(false);
|
|
const [navigateTo, setNaviageTo] = useState("/");
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const isComplaintContext = isComplaintAuthContext(location.pathname);
|
|
const { isAuthenticated, showExternalPortalChrome } = useExternalPortalSession();
|
|
const { config: tenantConfig } = useTenantConfig();
|
|
const moduleConfig = resolveModuleConfig(tenantConfig);
|
|
|
|
const userDetails = useUser();
|
|
const { t, i18n } = useTranslation();
|
|
const currentLanguage = resolveUiLanguage(i18n.language);
|
|
|
|
const hasCompletedRegistration = userDetails?.hasFinishedRegistration;
|
|
const changeLanguage = (lng: string) => i18n.changeLanguage(lng);
|
|
|
|
useEffect(() => {
|
|
if (!localStorage.getItem("i18nextLng")) {
|
|
i18n.changeLanguage("am");
|
|
localStorage.setItem("i18nextLng", "am");
|
|
}
|
|
}, [i18n]);
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setScrolled(window.scrollY > 10);
|
|
};
|
|
window.addEventListener("scroll", handleScroll);
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
}, []);
|
|
|
|
const handleSignIn = () => {
|
|
navigate("/login");
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (location.pathname.includes("/external-portal/portal-outgoing")) {
|
|
setActiveTab("records");
|
|
return;
|
|
}
|
|
|
|
if (location.pathname.startsWith("/complaints")) {
|
|
setActiveTab("complaint");
|
|
}
|
|
}, [location.pathname]);
|
|
|
|
const navItems: NavItem[] = useMemo(() => {
|
|
if (showExternalPortalChrome) {
|
|
return [];
|
|
}
|
|
|
|
if (isAuthenticated) {
|
|
return [
|
|
{ id: "overview", label: t("landingPage.overview") },
|
|
{ id: "workflow", label: t("landingPage.workflow") },
|
|
];
|
|
}
|
|
|
|
const complaintServices: NavItem[] = moduleConfig.complaint
|
|
? [
|
|
{
|
|
id: "complaint",
|
|
label: t("landingPage.fileComplaint"),
|
|
path: "/complaints",
|
|
},
|
|
{
|
|
id: "follow-complaint",
|
|
label: t("landingPage.followComplaint"),
|
|
path: "/follow-complaint",
|
|
},
|
|
]
|
|
: [];
|
|
|
|
const items: NavItem[] = [
|
|
{ id: "overview", label: t("landingPage.overview") },
|
|
{ id: "features", label: t("landingPage.features") },
|
|
];
|
|
|
|
if (complaintServices.length > 0) {
|
|
items.push({
|
|
id: "services",
|
|
label: t("landingPage.services"),
|
|
children: complaintServices,
|
|
});
|
|
}
|
|
|
|
items.push(
|
|
{ id: "workflow", label: t("landingPage.workflow") },
|
|
{ id: "howto", label: t("landingPage.manual") },
|
|
);
|
|
|
|
return items;
|
|
}, [isAuthenticated, moduleConfig.complaint, showExternalPortalChrome, t]);
|
|
|
|
const handleNavClick = (item: NavItem) => {
|
|
if (
|
|
item.requiresCompleteRegistration &&
|
|
!hasCompletedRegistration &&
|
|
!hasComplaintVerification()
|
|
) {
|
|
toast.error(t("registration.registrationRequired"));
|
|
return;
|
|
}
|
|
|
|
if (item.path) {
|
|
navigate(item.path);
|
|
setMobileMenuOpen(false);
|
|
}
|
|
};
|
|
|
|
// Animation variants
|
|
const mobileMenuVariants = {
|
|
hidden: {
|
|
opacity: 0,
|
|
height: 0,
|
|
transition: {
|
|
duration: 0.3,
|
|
when: "afterChildren",
|
|
},
|
|
},
|
|
visible: {
|
|
opacity: 1,
|
|
height: "auto",
|
|
transition: {
|
|
duration: 0.3,
|
|
when: "beforeChildren",
|
|
staggerChildren: 0.1,
|
|
},
|
|
},
|
|
};
|
|
|
|
const mobileItemVariants = {
|
|
hidden: { x: -20, opacity: 0 },
|
|
visible: {
|
|
x: 0,
|
|
opacity: 1,
|
|
transition: {
|
|
x: { stiffness: 1000, velocity: -100 },
|
|
},
|
|
},
|
|
};
|
|
|
|
const servicesVariants = {
|
|
hidden: {
|
|
opacity: 0,
|
|
y: -10,
|
|
scale: 0.95,
|
|
transition: {
|
|
duration: 0.2,
|
|
},
|
|
},
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
scale: 1,
|
|
transition: {
|
|
duration: 0.2,
|
|
staggerChildren: 0.05,
|
|
},
|
|
},
|
|
};
|
|
|
|
const serviceItemVariants = {
|
|
hidden: { opacity: 0, y: -5 },
|
|
visible: { opacity: 1, y: 0 },
|
|
};
|
|
|
|
const getServiceIcon = (id: string) => {
|
|
switch (id) {
|
|
case "complaint":
|
|
return <FileText className="w-4 h-4" />;
|
|
case "follow-complaint":
|
|
return <Search className="w-4 h-4" />;
|
|
case "about-us":
|
|
return <Info className="w-4 h-4" />;
|
|
default:
|
|
return <FileText className="w-4 h-4" />;
|
|
}
|
|
};
|
|
|
|
// Get current language display name
|
|
const getCurrentLanguageDisplay = () => {
|
|
const currentLang = languageOptions.find(
|
|
(lang) => lang.value === currentLanguage,
|
|
);
|
|
return currentLang ? currentLang.label : "English";
|
|
};
|
|
useMemo(() => {
|
|
if (showExternalPortalChrome) {
|
|
setNaviageTo(COMPLAINT_RECORDS_PATH);
|
|
}
|
|
}, [showExternalPortalChrome]);
|
|
return (
|
|
<>
|
|
{/* Registration Alert - Fixed for mobile */}
|
|
<nav
|
|
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
|
scrolled
|
|
? "bg-white dark:bg-gray-900 shadow-lg"
|
|
: "bg-white dark:bg-gray-900 shadow-sm"
|
|
} `}
|
|
>
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between items-center gap-4 h-16 md:h-20">
|
|
{/* Logo Section */}
|
|
<div className="flex items-center">
|
|
{!showExternalPortalChrome ? (
|
|
<>
|
|
{tenantConfig?.logo ? (
|
|
<div className="relative group inline-flex">
|
|
{/* Glow */}
|
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-primary/20 to-secondary/20 blur-xl opacity-0 group-hover:opacity-100 transition-all duration-500 scale-110" />
|
|
|
|
{/* Footer Logo Wrapper */}
|
|
<motion.div
|
|
whileHover={{ scale: 1.04 }}
|
|
whileTap={{ scale: 0.97 }}
|
|
transition={{ type: "spring", stiffness: 220, damping: 16 }}
|
|
className="relative inline-flex items-center justify-center cursor-pointer group"
|
|
onClick={() =>
|
|
navigate(
|
|
showExternalPortalChrome
|
|
? COMPLAINT_RECORDS_PATH
|
|
: isComplaintContext
|
|
? "/complaints"
|
|
: "/",
|
|
)
|
|
}
|
|
title={t("nav.homePage")}
|
|
>
|
|
<motion.img
|
|
src={tenantConfig.logo}
|
|
alt="Footer Logo"
|
|
className="
|
|
h-15 md:h-30
|
|
w-auto
|
|
object-contain
|
|
group-hover:scale-105
|
|
transition-transform
|
|
duration-300
|
|
"
|
|
onError={(e) => {
|
|
const fallback =
|
|
e.currentTarget.parentElement?.querySelector(
|
|
".footer-logo-fallback",
|
|
);
|
|
|
|
if (fallback) {
|
|
fallback.classList.remove("hidden");
|
|
}
|
|
|
|
e.currentTarget.remove();
|
|
}}
|
|
/>
|
|
|
|
{/* Fallback when logo fails to load */}
|
|
<div
|
|
className="
|
|
footer-logo-fallback
|
|
hidden
|
|
h-20
|
|
w-20
|
|
items-center
|
|
justify-center
|
|
rounded-full
|
|
bg-primary
|
|
text-primary-foreground
|
|
font-bold
|
|
text-2xl
|
|
pointer-events-none
|
|
"
|
|
>
|
|
{tenantConfig?.organizationName
|
|
?.split(/[\s\-–—]+/)
|
|
.filter(Boolean)
|
|
.map((word) => word.charAt(0))
|
|
.join("")
|
|
.toUpperCase() || "SO"}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
) : (
|
|
<motion.div
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
className="h-10 w-10 md:h-12 md:w-12 rounded-full bg-primary flex items-center justify-center text-white font-bold cursor-pointer"
|
|
onClick={() => navigate(navigateTo)}
|
|
title={t("nav.homePage")}
|
|
>
|
|
SO
|
|
</motion.div>
|
|
)}
|
|
<motion.span
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className="ml-2 md:ml-3 text-lg md:text-xl font-bold text-gray-800 dark:text-white cursor-pointer"
|
|
onClick={() => navigate(navigateTo)}
|
|
title={t("nav.homePage")}
|
|
>
|
|
{tenantConfig?.organizationName ||
|
|
tenantConfig?.organizationName == ""
|
|
? tenantConfig?.organizationName
|
|
: t("landingPage.smartOffice")}
|
|
</motion.span>
|
|
</>
|
|
) : null}
|
|
|
|
{/* Desktop Navigation */}
|
|
<div className="hidden md:ml-6 lg:ml-10 md:flex md:space-x-4 lg:space-x-6">
|
|
{navItems.map((item) => (
|
|
<div key={item.id} className="relative">
|
|
{item.children ? (
|
|
<div
|
|
className="relative"
|
|
onMouseEnter={() => setServicesOpen(true)}
|
|
onMouseLeave={() => setServicesOpen(false)}
|
|
>
|
|
<button
|
|
onClick={() => setServicesOpen(!servicesOpen)}
|
|
className={`relative inline-flex items-center px-1 pt-1 text-sm lg:text-base font-medium transition-colors duration-200 cursor-pointer ${
|
|
activeTab === item.id
|
|
? "text-gray-900 dark:text-white"
|
|
: "text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-white hover:underline"
|
|
}`}
|
|
>
|
|
{item.label}
|
|
<ChevronDown
|
|
className={`ml-1 w-4 h-4 transition-transform ${
|
|
servicesOpen ? "rotate-180" : ""
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
<AnimatePresence>
|
|
{servicesOpen && (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
exit="hidden"
|
|
variants={servicesVariants}
|
|
className="absolute top-full left-0 mt-2 w-64 bg-white rounded-lg shadow-xl border border-gray-200 z-50 overflow-hidden"
|
|
>
|
|
<div className="py-2">
|
|
{item.children.map((child) => (
|
|
<motion.button
|
|
key={child.id}
|
|
variants={serviceItemVariants}
|
|
onClick={() => handleNavClick(child)}
|
|
className="w-full flex items-center px-4 py-3 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors duration-200"
|
|
>
|
|
<span className="mr-3 text-primary">
|
|
{getServiceIcon(child.id)}
|
|
</span>
|
|
{child.label}
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
) : (
|
|
<button
|
|
onClick={() => {
|
|
setActiveTab(item.id);
|
|
if (item.path) {
|
|
handleNavClick(item);
|
|
} else {
|
|
const el = document.getElementById(item.id);
|
|
if (el) {
|
|
el.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "start",
|
|
});
|
|
}
|
|
}
|
|
}}
|
|
className={`relative inline-flex items-center px-1 pt-1 text-sm lg:text-base font-medium transition-colors duration-200 cursor-pointer ${
|
|
(item.path
|
|
? location.pathname.startsWith(item.path)
|
|
: activeTab === item.id)
|
|
? "text-gray-900 dark:text-white"
|
|
: "text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-white hover:underline"
|
|
}`}
|
|
>
|
|
{item.label}
|
|
{(item.path
|
|
? location.pathname.startsWith(item.path)
|
|
: activeTab === item.id) && (
|
|
<motion.div
|
|
layoutId="activeTabIndicator"
|
|
className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary"
|
|
transition={{
|
|
type: "spring",
|
|
bounce: 0.2,
|
|
duration: 0.6,
|
|
}}
|
|
/>
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Desktop Sign Up Button */}
|
|
{!isAuthenticated && (
|
|
<div className="hidden md:flex items-center space-x-2 lg:space-x-4">
|
|
<div className="w-28 lg:w-32">
|
|
<Select
|
|
value={currentLanguage}
|
|
onValueChange={(lng) => changeLanguage(lng)}
|
|
>
|
|
<SelectTrigger className="w-full text-sm rounded-md border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 px-2 py-1.5 lg:px-3 lg:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary">
|
|
<SelectValue>{getCurrentLanguageDisplay()}</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent className="dark:bg-gray-800 dark:border-gray-700">
|
|
{languageOptions.map((lang) => (
|
|
<SelectItem
|
|
key={lang.value}
|
|
value={lang.value}
|
|
className="dark:text-gray-200 dark:hover:bg-gray-700"
|
|
>
|
|
{lang.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<motion.button
|
|
onClick={handleSignIn}
|
|
whileHover={{
|
|
scale: 1.03,
|
|
boxShadow: "0 4px 12px rgba(24, 170, 157, 0.2)",
|
|
}}
|
|
whileTap={{ scale: 0.98 }}
|
|
className="inline-flex items-center px-3 py-1.5 lg:px-4 lg:py-2.5 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-all duration-200 whitespace-nowrap"
|
|
>
|
|
{t("landingPage.signIn")}
|
|
</motion.button>
|
|
</div>
|
|
)}
|
|
{showExternalPortalChrome && (
|
|
<div className="hidden md:flex items-center">
|
|
<ExternalPortal />
|
|
</div>
|
|
)}
|
|
|
|
{/* Mobile Menu Button */}
|
|
<div className="flex items-center md:hidden">
|
|
<motion.button
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
className="inline-flex items-center justify-center p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary transition-colors duration-200"
|
|
aria-expanded="false"
|
|
whileTap={{ scale: 0.9 }}
|
|
>
|
|
<span className="sr-only">Open main menu</span>
|
|
{mobileMenuOpen ? (
|
|
<X className="h-6 w-6" />
|
|
) : (
|
|
<Menu className="h-6 w-6" />
|
|
)}
|
|
</motion.button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile Menu */}
|
|
<AnimatePresence>
|
|
{mobileMenuOpen && (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
exit="hidden"
|
|
variants={mobileMenuVariants}
|
|
className="md:hidden overflow-hidden bg-white dark:bg-gray-800 shadow-xl border-t border-gray-200 dark:border-gray-700"
|
|
>
|
|
<motion.div className="pt-2 pb-4 space-y-1 px-4">
|
|
{navItems.map((item) => (
|
|
<div key={item.id}>
|
|
{item.children ? (
|
|
<div>
|
|
<button
|
|
onClick={() => setServicesOpen(!servicesOpen)}
|
|
className={`block w-full text-left pl-3 pr-4 py-3 border-l-4 text-base font-medium transition-all duration-200 ${
|
|
activeTab === item.id
|
|
? "bg-primary-500/15 border-primary text-primary"
|
|
: "border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800"
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
{item.label}
|
|
<ChevronDown
|
|
className={`w-4 h-4 transition-transform ${
|
|
servicesOpen ? "rotate-180" : ""
|
|
}`}
|
|
/>
|
|
</div>
|
|
</button>
|
|
|
|
<AnimatePresence>
|
|
{servicesOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: "auto" }}
|
|
exit={{ opacity: 0, height: 0 }}
|
|
className="pl-6 overflow-hidden"
|
|
>
|
|
{item.children.map((child) => (
|
|
<motion.button
|
|
key={child.id}
|
|
onClick={() => handleNavClick(child)}
|
|
className="block w-full text-left pl-3 pr-4 py-2 text-sm text-gray-600 hover:bg-blue-50 hover:text-blue-700 transition-colors duration-200"
|
|
>
|
|
<div className="flex items-center">
|
|
<span className="mr-2 text-primary">
|
|
{getServiceIcon(child.id)}
|
|
</span>
|
|
{child.label}
|
|
</div>
|
|
</motion.button>
|
|
))}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
) : (
|
|
<motion.button
|
|
variants={mobileItemVariants}
|
|
onClick={() => {
|
|
setActiveTab(item.id);
|
|
setMobileMenuOpen(false);
|
|
if (item.path) {
|
|
handleNavClick(item);
|
|
}
|
|
}}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
className={`block w-full text-left pl-3 pr-4 py-3 border-l-4 text-base font-medium transition-all duration-200 ${
|
|
activeTab === item.id
|
|
? "bg-primary-500/15 border-primary text-primary"
|
|
: "border-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 hover:text-gray-800 dark:hover:text-white"
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</motion.button>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
{!isAuthenticated && (
|
|
<motion.div
|
|
variants={mobileItemVariants}
|
|
className="pt-4 pb-2 border-t border-gray-200 dark:border-gray-700 space-y-3"
|
|
>
|
|
<div className="px-2">
|
|
<Select
|
|
value={currentLanguage}
|
|
onValueChange={(lng) => changeLanguage(lng)}
|
|
>
|
|
<SelectTrigger className="w-full text-sm bg-gray-100 dark:bg-gray-700 border-none rounded-md px-3 py-2.5 shadow-sm focus:outline-none focus:ring-2 focus:ring-primary dark:text-gray-200">
|
|
<SelectValue>
|
|
{getCurrentLanguageDisplay()}
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent className="dark:bg-gray-800 dark:border-gray-700">
|
|
{languageOptions.map((lang) => (
|
|
<SelectItem
|
|
key={lang.value}
|
|
value={lang.value}
|
|
className="dark:text-gray-200 dark:hover:bg-gray-700"
|
|
>
|
|
{lang.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<motion.button
|
|
onClick={() => {
|
|
handleSignIn();
|
|
setMobileMenuOpen(false);
|
|
}}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
className="block w-full px-4 py-3 text-base font-medium text-center text-white bg-primary rounded-lg shadow hover:bg-primary-700 transition-all duration-200"
|
|
>
|
|
{t("landingPage.signIn")}
|
|
</motion.button>
|
|
</motion.div>
|
|
)}
|
|
|
|
{showExternalPortalChrome && (
|
|
<motion.div
|
|
variants={mobileItemVariants}
|
|
className="pt-3 border-t border-gray-200"
|
|
>
|
|
<ExternalPortal
|
|
mobileView={true}
|
|
onItemClick={() => setMobileMenuOpen(false)}
|
|
/>
|
|
</motion.div>
|
|
)}
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</nav>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Header;
|