main
This commit is contained in:
229
src/components/layout/header/header.tsx
Normal file
229
src/components/layout/header/header.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
IconButton,
|
||||
Link as ChakraLink,
|
||||
Stack,
|
||||
VStack,
|
||||
Button,
|
||||
MenuItem,
|
||||
ClientOnly,
|
||||
} from "@chakra-ui/react";
|
||||
import { Link, useRouter } from "@/i18n/navigation";
|
||||
import { ColorModeButton } from "@/components/ui/color-mode";
|
||||
import {
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverRoot,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/overlays/popover";
|
||||
import { RxHamburgerMenu } from "react-icons/rx";
|
||||
import { NAV_ITEMS } from "@/config/navigation";
|
||||
import HeaderLink from "./header-link";
|
||||
import MobileHeaderLink from "./mobile-header-link";
|
||||
import LocaleSwitcher from "@/components/ui/locale-switcher";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
MenuContent,
|
||||
MenuRoot,
|
||||
MenuTrigger,
|
||||
} from "@/components/ui/overlays/menu";
|
||||
import { Avatar } from "@/components/ui/data-display/avatar";
|
||||
import { Skeleton } from "@/components/ui/feedback/skeleton";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { authConfig } from "@/config/auth";
|
||||
import { LoginModal } from "@/components/auth/login-modal";
|
||||
import { LuLogIn } from "react-icons/lu";
|
||||
|
||||
export default function Header() {
|
||||
const t = useTranslations();
|
||||
const [isSticky, setIsSticky] = useState(false);
|
||||
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
const isAuthenticated = !!session;
|
||||
const isLoading = status === "loading";
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsSticky(window.scrollY >= 10);
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
await signOut({ redirect: false });
|
||||
if (authConfig.isAuthRequired) {
|
||||
router.replace("/signin");
|
||||
}
|
||||
};
|
||||
|
||||
// Render user menu or login button based on auth state
|
||||
const renderAuthSection = () => {
|
||||
if (isLoading) {
|
||||
return <Skeleton boxSize="10" rounded="full" />;
|
||||
}
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<MenuRoot positioning={{ placement: "bottom-start" }}>
|
||||
<MenuTrigger rounded="full" focusRing="none">
|
||||
<Avatar name={session?.user?.name || "User"} variant="solid" />
|
||||
</MenuTrigger>
|
||||
<MenuContent>
|
||||
<MenuItem onClick={handleLogout} value="sign-out">
|
||||
{t("auth.sign-out")}
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</MenuRoot>
|
||||
);
|
||||
}
|
||||
|
||||
// Not authenticated - show login button
|
||||
return (
|
||||
<Button
|
||||
variant="solid"
|
||||
colorPalette="primary"
|
||||
size="sm"
|
||||
onClick={() => setLoginModalOpen(true)}
|
||||
>
|
||||
<LuLogIn />
|
||||
{t("auth.sign-in")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
// Render mobile auth section
|
||||
const renderMobileAuthSection = () => {
|
||||
if (isLoading) {
|
||||
return <Skeleton height="10" width="full" />;
|
||||
}
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<>
|
||||
<Avatar name={session?.user?.name || "User"} variant="solid" />
|
||||
<Button
|
||||
variant="surface"
|
||||
size="sm"
|
||||
width="full"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
{t("auth.sign-out")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="solid"
|
||||
colorPalette="primary"
|
||||
size="sm"
|
||||
width="full"
|
||||
onClick={() => setLoginModalOpen(true)}
|
||||
>
|
||||
<LuLogIn />
|
||||
{t("auth.sign-in")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
as="nav"
|
||||
bg={isSticky ? "rgba(255, 255, 255, 0.6)" : "white"}
|
||||
_dark={{
|
||||
bg: isSticky ? "rgba(1, 1, 1, 0.6)" : "black",
|
||||
}}
|
||||
shadow={isSticky ? "sm" : "none"}
|
||||
backdropFilter="blur(12px) saturate(180%)"
|
||||
border="1px solid"
|
||||
borderColor={isSticky ? "whiteAlpha.300" : "transparent"}
|
||||
borderBottomRadius={isSticky ? "xl" : "none"}
|
||||
transition="all 0.4s ease-in-out"
|
||||
px={{ base: 4, md: 8 }}
|
||||
py="3"
|
||||
position="sticky"
|
||||
top={0}
|
||||
zIndex={10}
|
||||
w="full"
|
||||
>
|
||||
<Flex justify="space-between" align="center" maxW="8xl" mx="auto">
|
||||
{/* Logo */}
|
||||
<HStack>
|
||||
<ChakraLink
|
||||
as={Link}
|
||||
href="/home"
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
color={{ base: "primary.500", _dark: "primary.300" }}
|
||||
focusRing="none"
|
||||
textDecor="none"
|
||||
transition="all 0.3s ease-in-out"
|
||||
_hover={{
|
||||
color: { base: "primary.900", _dark: "primary.50" },
|
||||
}}
|
||||
>
|
||||
{"FCS "}
|
||||
</ChakraLink>
|
||||
</HStack>
|
||||
|
||||
{/* DESKTOP NAVIGATION */}
|
||||
<HStack spaceX={4} display={{ base: "none", lg: "flex" }}>
|
||||
{NAV_ITEMS.map((item, index) => (
|
||||
<HeaderLink key={index} item={item} />
|
||||
))}
|
||||
</HStack>
|
||||
|
||||
<HStack>
|
||||
<ColorModeButton colorPalette="gray" />
|
||||
<Box display={{ base: "none", lg: "inline-flex" }} gap={2}>
|
||||
<LocaleSwitcher />
|
||||
<ClientOnly fallback={<Skeleton boxSize="10" rounded="full" />}>
|
||||
{renderAuthSection()}
|
||||
</ClientOnly>
|
||||
</Box>
|
||||
|
||||
{/* MOBILE NAVIGATION */}
|
||||
<Stack display={{ base: "inline-flex", lg: "none" }}>
|
||||
<ClientOnly fallback={<Skeleton boxSize="9" />}>
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger as="span">
|
||||
<IconButton aria-label="Open menu" variant="ghost">
|
||||
<RxHamburgerMenu />
|
||||
</IconButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent width={{ base: "xs", sm: "sm", md: "md" }}>
|
||||
<PopoverBody>
|
||||
<VStack mt="2" align="start" spaceY="2" w="full">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<MobileHeaderLink key={item.label} item={item} />
|
||||
))}
|
||||
<LocaleSwitcher />
|
||||
{renderMobileAuthSection()}
|
||||
</VStack>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
</ClientOnly>
|
||||
</Stack>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
{/* Login Modal */}
|
||||
<LoginModal open={loginModalOpen} onOpenChange={setLoginModalOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user