244 lines
9.6 KiB
TypeScript
244 lines
9.6 KiB
TypeScript
/* 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>
|
||
);
|
||
}
|