Files
iddaai-fe/src/components/layout/header/header.tsx
T
2026-04-24 00:27:14 +03:00

332 lines
9.8 KiB
TypeScript

"use client";
import {
Box,
Flex,
HStack,
IconButton,
Link as ChakraLink,
Stack,
VStack,
Button,
MenuItem,
ClientOnly,
Text,
Separator,
} 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, getVisibleNavItems } 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 { isAdminRole } from "@/lib/auth/roles";
import { LuLogIn, LuUser, LuShield, LuZap } from "react-icons/lu";
import GlobalSearch from "@/components/search/global-search";
export default function Header() {
const t = useTranslations();
const [isSticky, setIsSticky] = useState(false);
const [loginModalOpen, setLoginModalOpen] = useState(false);
const [loginModalMode, setLoginModalMode] = useState<"login" | "register">("login");
const router = useRouter();
const { data: session, status } = useSession();
const isAuthenticated = !!session;
const isLoading = status === "loading";
const visibleItems = getVisibleNavItems(NAV_ITEMS, isAuthenticated);
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("/home");
}
};
const openAuthModal = (mode: "login" | "register") => {
setLoginModalMode(mode);
setLoginModalOpen(true);
};
// Desktop auth section
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 value="profile" onClick={() => router.push("/profile")}>
<LuUser />
{t("nav.profile")}
</MenuItem>
{session?.user && isAdminRole(session.user.roles) && (
<MenuItem value="admin" onClick={() => router.push("/admin")}>
<LuShield />
{t("nav.admin")}
</MenuItem>
)}
<MenuItem onClick={handleLogout} value="sign-out">
{t("auth.sign-out")}
</MenuItem>
</MenuContent>
</MenuRoot>
);
}
return (
<HStack gap={2}>
<Button
variant="outline"
colorPalette="gray"
size="sm"
borderRadius="full"
onClick={() => openAuthModal("register")}
>
{t("auth.sign-up")}
</Button>
<Button
variant="solid"
colorPalette="primary"
size="sm"
borderRadius="full"
onClick={() => openAuthModal("login")}
>
<LuLogIn />
{t("auth.sign-in")}
</Button>
</HStack>
);
};
// Mobile auth section
const renderMobileAuthSection = () => {
if (isLoading) return <Skeleton height="10" width="full" />;
if (isAuthenticated) {
return (
<VStack gap={2} w="full">
<Flex align="center" gap={2} w="full">
<Avatar
name={session?.user?.name || "User"}
variant="solid"
size="sm"
/>
<Text fontSize="sm" fontWeight="semibold" truncate>
{session?.user?.name || session?.user?.email}
</Text>
</Flex>
<Button
variant="ghost"
size="sm"
width="full"
justifyContent="flex-start"
onClick={() => router.push("/profile")}
>
<LuUser />
{t("nav.profile")}
</Button>
<Button
variant="surface"
size="sm"
width="full"
onClick={handleLogout}
>
{t("auth.sign-out")}
</Button>
</VStack>
);
}
return (
<VStack gap={2} w="full">
<Button
variant="outline"
colorPalette="gray"
size="sm"
width="full"
borderRadius="full"
onClick={() => openAuthModal("register")}
>
{t("auth.sign-up")}
</Button>
<Button
variant="solid"
colorPalette="primary"
size="sm"
width="full"
borderRadius="full"
onClick={() => openAuthModal("login")}
>
<LuLogIn />
{t("auth.sign-in")}
</Button>
</VStack>
);
};
return (
<>
<Box
as="nav"
bg={isSticky ? "rgba(255, 255, 255, 0.75)" : "white"}
_dark={{
bg: isSticky ? "rgba(1, 1, 1, 0.75)" : "black",
}}
shadow={isSticky ? "md" : "xs"}
backdropFilter="blur(16px) saturate(180%)"
borderBottom="1px solid"
borderColor={{ base: "gray.100", _dark: "gray.800" }}
transition="all 0.3s ease-in-out"
px={{ base: 4, md: 6 }}
py="2.5"
position="sticky"
top={0}
zIndex={10}
w="full"
>
<Flex justify="space-between" align="center" maxW="8xl" mx="auto">
{/* Logo */}
<ChakraLink
as={Link}
href="/home"
focusRing="none"
textDecor="none"
_hover={{ textDecor: "none" }}
display="flex"
alignItems="center"
gap="2"
flexShrink={0}
mr={6}
>
<Flex
boxSize="32px"
bg="primary.500"
borderRadius="lg"
align="center"
justify="center"
shadow="sm"
>
<LuZap color="white" size={18} />
</Flex>
<Box>
<Text
fontSize="md"
fontWeight="800"
lineHeight="1"
color={{ base: "gray.900", _dark: "white" }}
letterSpacing="-0.02em"
>
Suggest
</Text>
<Text
fontSize="xs"
fontWeight="600"
lineHeight="1"
mt="1px"
color={{ base: "primary.600", _dark: "primary.300" }}
letterSpacing="0.08em"
textTransform="uppercase"
>
BET
</Text>
</Box>
</ChakraLink>
{/* DESKTOP NAVIGATION */}
<HStack gap={1} display={{ base: "none", lg: "flex" }} flex={1}>
{visibleItems.map((item) => (
<HeaderLink key={item.href} item={item} />
))}
</HStack>
{/* Right side actions */}
<HStack gap={2} flexShrink={0}>
{/* Global Search (Desktop) */}
<Box display={{ base: "none", lg: "block" }}>
<GlobalSearch />
</Box>
<Separator
orientation="vertical"
height="5"
display={{ base: "none", lg: "block" }}
borderColor={{ base: "gray.200", _dark: "gray.700" }}
/>
<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">
{visibleItems.map((item) => (
<MobileHeaderLink key={item.href} item={item} />
))}
<Box
w="full"
pt={2}
borderTopWidth="1px"
borderColor="border.muted"
>
<LocaleSwitcher />
</Box>
{renderMobileAuthSection()}
</VStack>
</PopoverBody>
</PopoverContent>
</PopoverRoot>
</ClientOnly>
</Stack>
</HStack>
</Flex>
</Box>
{/* Login Modal */}
<LoginModal open={loginModalOpen} onOpenChange={setLoginModalOpen} initialMode={loginModalMode} />
</>
);
}