Files
graff.estate-nextjs-updated/src/components/Layout/Header.tsx
T

244 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable @next/next/no-img-element */
'use client';
import { api } from '@/api';
import { useMediaQueries } from '@/hooks/useMediaQueries';
import { useScroll } from '@/hooks/useScroll';
import { useCheckAuthQuery } from '@/queries/checkAuth';
import { HeaderLink } from '@/ui/HeaderLink';
import {
QueryClient,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
import { AnimatePresence, motion } from 'framer-motion';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useRef, useState } from 'react';
import { useOnClickOutside } from 'usehooks-ts';
import BurgerIcon from '../../../public/icons/burger.svg';
import CloseIcon from '../../../public/icons/close.svg';
import GraffIcon from '../../../public/icons/graff.svg';
import LogoIcon from '../../../public/icons/logo_hor.svg';
import SkolkovoIcon from '../../../public/icons/skolkovo.svg';
import { Products } from './Products';
import { useAuthStore } from '@/stores/useAuthStore';
import { getQueryClient } from '@/lib/queryClient';
export function Header() {
const { setToken } = useAuthStore();
const queryClient = getQueryClient();
const { data: auth } = useCheckAuthQuery();
const { mutate: logout } = useMutation({
mutationFn: () => api.get('auth/logout').json<{ success: boolean }>(),
onSuccess() {
queryClient.invalidateQueries({ queryKey: ['checkAuth'] });
setToken(null);
},
});
const [burgerOpened, setBurgerOpened] = useState(false);
const [productsOpened, setProductsOpened] = useState(false);
const { isLg, isXs, isSm, isMd } = useMediaQueries();
const pathname = usePathname();
const burgerRef = useRef<HTMLDivElement>(null);
const burgerBtnRef = useRef<HTMLDivElement>(null);
const productsRef = useRef<HTMLDivElement>(null);
const productsBtnRef = useRef<HTMLDivElement>(null);
const logoRef = useRef<HTMLAnchorElement>(null);
const navRef = useRef<HTMLDivElement>(null);
useOnClickOutside([productsRef, productsBtnRef], () => {
setProductsOpened(false);
});
useOnClickOutside([burgerRef, burgerBtnRef], () => {
setBurgerOpened(false);
});
const scroll = useScroll(logoRef);
return (
<header className="lg:mt-[1.389vw] relative flex lg:px-[1.389vw]">
<Link
href={'/'}
ref={logoRef}
className="max-lg:hidden cursor-pointer outline-none"
>
<LogoIcon className="w-[13.333vw] aspect-[4/1]" />
</Link>
<div className="relative flex justify-center flex-1 m-auto">
<AnimatePresence>
<motion.nav
ref={navRef}
animate={{
width: burgerOpened && (isXs || isSm) ? 340 : 'auto',
}}
className="fixed self-center max-lg:top-4 top-[1.389vw] lg:p-[0.278vw] p-1 lg:rounded-[1.389vw] rounded-[20px] bg-[#37393B99] [backdrop-filter:blur(40px)] lg:gap-[0.278vw] flex gap-1 z-12 mx-auto left-1/2 -translate-x-1/2"
>
{((isLg && scroll < -logoRef.current?.clientHeight!) || !isLg) && (
<Link
href={'/'}
className="aspect-square lg:p-[1.111vw] p-3 hover:bg-[#232425] group active:bg-white lg:rounded-[1.111vw] rounded-2xl content-center m-auto"
>
<GraffIcon className="text-white group-active:text-black lg:w-[1.111vw] md:max-lg:w-[2.083vw] w-4 lg:h-[1.111vw] md:max-lg:h-[2.083vw] h-4" />
</Link>
)}
<div
ref={productsBtnRef}
className="max-md:hidden flex items-center"
>
<button
className={
'lg:px-[1.667vw] lg:py-[1.111vw] px-6 py-4 font-medium btnm lg:rounded-[1.111vw] rounded-2xl active:bg-white cursor-pointer outline-none' +
(productsOpened
? ' bg-white text-black'
: ' active:text-black hover:bg-[#232425]')
}
onClick={() => setProductsOpened((prev) => !prev)}
>
Продукты
</button>
</div>
<HeaderLink
className="max-md:hidden btnm"
href={'/about'}
text={'О нас'}
/>
<HeaderLink
className="max-md:hidden btnm"
href={'/blog'}
text={'Блог'}
/>
<HeaderLink
className="max-md:hidden btnm"
href={'/projects'}
text={'Проекты'}
/>
<div className="md:justify-end flex justify-center flex-1">
<Link
href={'/form'}
className="btnm bg-gradient font-medium lg:px-[1.667vw] lg:py-[1.181vw] py-[17px] px-6 text-nowrap lg:rounded-[1.111vw] rounded-2xl flex items-center"
>
Оставить заявку
</Link>
</div>
<div className="md:hidden md:justify-enda flex" ref={burgerBtnRef}>
<button
className="!border-none p-[18px] hover:bg-[#232425] rounded-2xl active:opacity-50 outline-none cursor-pointer"
onClick={() => setBurgerOpened((prev) => !prev)}
>
{burgerOpened ? (
<CloseIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4" />
) : (
<BurgerIcon className="lg:w-[1.111vw] lg:h-[1.111vw] w-4 h-4" />
)}
</button>
</div>
{auth && (
<div className="sm:max-md:absolute -right-1/4 sm:max-md:bg-[#37393B99] self-center">
<button
className="lg:rounded-[1.111vw] rounded-2xl btnm font-medium lg:p-[0.833vw] p-3 hover:bg-[#FF4517] transition-colors h-full hover:text-black outline-none cursor-pointer"
onClick={() => logout()}
>
Выйти
</button>
</div>
)}
<AnimatePresence>
{burgerOpened && (isXs || isSm) && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute p-4 pt-2 top-16 rounded-2xl bg-[#232425] md:hidden space-y-6 xs:max-h-[calc(100dvh-100px)] h-fit"
>
<div className="px-2 -mx-4 space-y-1">
<HeaderLink
href={'/about'}
text={'О нас'}
className="accent"
/>
<HeaderLink
href={'/blog'}
text={'Блог'}
className="accent"
/>
<HeaderLink
href={'/projects'}
text={'Проекты'}
className="accent"
/>
</div>
<hr className="border-[#37393B]" />
<div className="space-y-[10px]">
<p className="btnm opacity-60">Продукты</p>
<Products />
</div>
<div>
<p className="btnm opacity-60 mb-1">Контакты:</p>
<Link
href={'tel:88007700067'}
className="accent font-medium outline-none"
>
8 800 770 00 67
</Link>
</div>
</motion.div>
<div className="absolute w-screen h-screen bg-[#0F101199] backdrop-blur-lg -top-4 -left-[calc((100vw-100%)/2)] -z-[1]" />
</>
)}
</AnimatePresence>
</motion.nav>
</AnimatePresence>
<AnimatePresence>
{productsOpened && (isMd || isLg) && (
<>
<motion.div
onClick={() => setProductsOpened(false)}
ref={productsRef}
initial={{ opacity: 0 }}
animate={{
width: isLg ? 'max(68.889vw,360px)' : 'calc(100vw - 32px)',
opacity: 100,
}}
exit={{ opacity: 0 }}
className="fixed max-md:hidden top-[6.944vw] left-1/2 -translate-x-1/2 lg:rounded-[1.111vw] md:max-lg:rounded-2xl bg-[#37393B99] backdrop-blur-2xl p-1 z-[13]"
>
<Products />
</motion.div>
<div className="fixed w-screen h-screen bg-[#0F101199] backdrop-blur-lg top-0 left-0 z-[11]" />
</>
)}
</AnimatePresence>
</div>
{pathname.startsWith('/projects') ? (
<Link
href={'https://dprofile.ru/graff.estate'}
className="max-xl:hidden rounded-[20px] bg-[#37393B99] backdrop-blur-[20px] hover:bg-[#232425] py-5 pl-[63px] pr-[33px] right-5 fixed z-[2] top-5 overflow-clip bg-[url(/img/components/header/dp.png)] bg-no-repeat bg-right-bottom"
>
<img
src={'/img/components/header/show_case.png'}
width={53.77}
height={104.48}
alt="кейс dprofile"
className="absolute bottom-0 left-0 rotate-[2.56deg]"
/>
<p className="btnm font-medium">Смотреть кейс</p>
</Link>
) : (
<div className="bg-[#B5F54E] p-[0.764vw] self-center rounded-[0.556vw] max-lg:hidden">
<SkolkovoIcon className="w-[1.944vw] h-[1.806vw] text-[#0F1011]" />
</div>
)}
</header>
);
}