Initial commit

This commit is contained in:
2026-03-03 19:26:22 +03:00
commit c1f94439ab
167 changed files with 23898 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { Box, Text, HStack, Link as ChakraLink } from "@chakra-ui/react";
import { Link } from "@/i18n/navigation";
import { useTranslations } from "next-intl";
export default function Footer() {
const t = useTranslations();
return (
<Box as="footer" bg="bg.muted" mt="auto">
<HStack
display="flex"
justify={{ base: "center", md: "space-between" }}
alignContent="center"
maxW="8xl"
mx="auto"
wrap="wrap"
px={{ base: 4, md: 8 }}
position="relative"
minH="16"
>
<Text fontSize="sm" color="fg.muted">
© {new Date().getFullYear()}
<ChakraLink
target="_blank"
rel="noopener noreferrer"
href="https://www.fcs.com.tr"
color={{ base: "primary.500", _dark: "primary.300" }}
focusRing="none"
ml="1"
>
{"FCS"}
</ChakraLink>
. {t("all-right-reserved")}
</Text>
<HStack spaceX={4}>
<ChakraLink
as={Link}
href="/privacy"
fontSize="sm"
color="fg.muted"
focusRing="none"
position="relative"
textDecor="none"
transition="color 0.3s ease-in-out"
_hover={{
color: { base: "primary.500", _dark: "primary.300" },
}}
>
{t("privacy-policy")}
</ChakraLink>
<ChakraLink
as={Link}
href="/terms"
fontSize="sm"
color="fg.muted"
focusRing="none"
position="relative"
textDecor="none"
transition="color 0.3s ease-in-out"
_hover={{
color: { base: "primary.500", _dark: "primary.300" },
}}
>
{t("terms-of-service")}
</ChakraLink>
</HStack>
</HStack>
</Box>
);
}

View File

@@ -0,0 +1,104 @@
import { Box, Link as ChakraLink, Text } from '@chakra-ui/react';
import { NavItem } from '@/config/navigation';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from '@/components/ui/overlays/menu';
import { RxChevronDown } from 'react-icons/rx';
import { useActiveNavItem } from '@/hooks/useActiveNavItem';
import { Link } from '@/i18n/navigation';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
function HeaderLink({ item }: { item: NavItem }) {
const t = useTranslations();
const { isActive, isChildActive } = useActiveNavItem(item);
const [open, setOpen] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const handleMouseOpen = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
setOpen(true);
};
const handleMouseClose = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => setOpen(false), 150);
};
return (
<Box key={item.label}>
{item.children ? (
<Box onMouseEnter={handleMouseOpen} onMouseLeave={handleMouseClose}>
<MenuRoot open={open} onOpenChange={(e) => setOpen(e.open)}>
<MenuTrigger asChild>
<Text
display='inline-flex'
alignItems='center'
gap='1'
cursor='pointer'
color={isActive ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
position='relative'
fontWeight='semibold'
>
{t(item.label)}
<RxChevronDown
style={{ transform: open ? 'rotate(-180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}
/>
</Text>
</MenuTrigger>
<MenuContent>
{item.children.map((child, index) => {
const isActiveChild = isChildActive(child.href);
return (
<MenuItem key={index} value={child.href}>
<ChakraLink
key={index}
as={Link}
href={child.href}
focusRing='none'
w='full'
color={isActiveChild ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
position='relative'
textDecor='none'
fontWeight='semibold'
>
{t(child.label)}
</ChakraLink>
</MenuItem>
);
})}
</MenuContent>
</MenuRoot>
</Box>
) : (
<ChakraLink
as={Link}
href={item.href}
focusRing='none'
color={isActive ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
position='relative'
textDecor='none'
fontWeight='semibold'
_after={{
content: "''",
position: 'absolute',
left: 0,
bottom: '-2px',
width: '0%',
height: '1.5px',
bg: { base: 'primary.500', _dark: 'primary.300' },
transition: 'width 0.3s ease-in-out',
}}
_hover={{
_after: {
width: '100%',
},
}}
>
{t(item.label)}
</ChakraLink>
)}
</Box>
);
}
export default HeaderLink;

View 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} />
</>
);
}

View File

@@ -0,0 +1,84 @@
import { Text, Box, Link as ChakraLink, useDisclosure, VStack } from '@chakra-ui/react';
import { RxChevronDown } from 'react-icons/rx';
import { NavItem } from '@/config/navigation';
import { useActiveNavItem } from '@/hooks/useActiveNavItem';
import { Link } from '@/i18n/navigation';
import { useTranslations } from 'next-intl';
function MobileHeaderLink({ item }: { item: NavItem }) {
const t = useTranslations();
const { isActive, isChildActive } = useActiveNavItem(item);
const { open, onToggle } = useDisclosure();
return (
<Box key={item.label} w='full'>
{item.children ? (
<VStack align='start' w='full' spaceY={0}>
<Text
onClick={onToggle}
display='inline-flex'
alignItems='center'
gap='1'
cursor='pointer'
color={isActive ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
textUnderlineOffset='4px'
textUnderlinePosition='from-font'
textDecoration={isActive ? 'underline' : 'none'}
fontWeight='semibold'
fontSize={{ base: 'md', md: 'lg' }}
_hover={{
color: { base: 'primary.500', _dark: 'primary.300' },
transition: 'all 0.2s ease-in-out',
}}
>
{t(item.label)}
<RxChevronDown
style={{ transform: open ? 'rotate(-180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}
/>
</Text>
{open && item.children && (
<VStack align='start' pl='4' pt='1' pb='2' w='full' spaceY={1}>
{item.children.map((child, index) => {
const isActiveChild = isChildActive(child.href);
return (
<ChakraLink
key={index}
as={Link}
href={child.href}
focusRing='none'
color={isActiveChild ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
textUnderlineOffset='4px'
textUnderlinePosition='from-font'
textDecoration={isActiveChild ? 'underline' : 'none'}
fontWeight='semibold'
fontSize={{ base: 'md', md: 'lg' }}
>
{t(child.label)}
</ChakraLink>
);
})}
</VStack>
)}
</VStack>
) : (
<ChakraLink
as={Link}
href={item.href}
w='full'
focusRing='none'
color={isActive ? { base: 'primary.500', _dark: 'primary.300' } : 'fg.muted'}
textUnderlineOffset='4px'
textUnderlinePosition='from-font'
textDecoration={isActive ? 'underline' : 'none'}
fontWeight='semibold'
fontSize={{ base: 'md', md: 'lg' }}
>
{t(item.label)}
</ChakraLink>
)}
</Box>
);
}
export default MobileHeaderLink;