617 lines
24 KiB
TypeScript
617 lines
24 KiB
TypeScript
'use client';
|
||
|
||
import { cart_api } from '@/features/cart/lib/api';
|
||
import { category_api } from '@/shared/config/api/category/api';
|
||
import { Link, useRouter } from '@/shared/config/i18n/navigation';
|
||
import { useCartId } from '@/shared/hooks/cartId';
|
||
import formatPhone from '@/shared/lib/formatPhone';
|
||
import { getToken } from '@/shared/lib/token';
|
||
import { Badge } from '@/shared/ui/badge';
|
||
import { Button } from '@/shared/ui/button';
|
||
import { Input } from '@/shared/ui/input';
|
||
import { Popover, PopoverContent } from '@/shared/ui/popover';
|
||
import {
|
||
Sheet,
|
||
SheetClose,
|
||
SheetContent,
|
||
SheetHeader,
|
||
SheetTitle,
|
||
SheetTrigger,
|
||
} from '@/shared/ui/sheet';
|
||
import { banner_api } from '@/widgets/welcome/lib/api';
|
||
import { userStore } from '@/widgets/welcome/lib/hook';
|
||
import { PopoverTrigger } from '@radix-ui/react-popover';
|
||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||
import {
|
||
ChevronRight,
|
||
Facebook,
|
||
Heart,
|
||
Instagram,
|
||
LayoutGrid,
|
||
Mail,
|
||
MenuIcon,
|
||
Phone,
|
||
Search,
|
||
Send,
|
||
ShoppingCart,
|
||
Twitter,
|
||
User,
|
||
XIcon,
|
||
} from 'lucide-react';
|
||
import { useTranslations } from 'next-intl';
|
||
import Image from 'next/image';
|
||
import { useSearchParams } from 'next/navigation';
|
||
import { useEffect, useState } from 'react';
|
||
import useCategoryActive from '../lib/openCategory';
|
||
import { ChangeLang } from './ChangeLang';
|
||
import { MobileLanguageSelector } from './MobileLanguageSelector';
|
||
import NavbarMobile from './NavbarMobile';
|
||
import { SearchResult } from './SearchResult';
|
||
|
||
const Navbar = () => {
|
||
const [isSticky, setIsSticky] = useState(false);
|
||
const [query, setQuery] = useState('');
|
||
const searchParams = useSearchParams();
|
||
const token = getToken();
|
||
const t = useTranslations();
|
||
const { cart_id } = useCartId();
|
||
const [cartQuenty, setCartQuenty] = useState<number>(0);
|
||
const { setCartId } = useCartId();
|
||
const { setUser } = userStore();
|
||
|
||
const { mutate: cart } = useMutation({
|
||
mutationFn: () => cart_api.create(),
|
||
onSuccess: (data) => {
|
||
setCartId(data.data.cart_id);
|
||
},
|
||
});
|
||
|
||
const { data: me } = useQuery({
|
||
queryKey: ['get_me'],
|
||
queryFn: () => banner_api.getMe(),
|
||
});
|
||
|
||
const { data: category } = useQuery({
|
||
queryKey: ['category_list'],
|
||
queryFn: () => category_api.getCategory({ page: 1, page_size: 99 }),
|
||
select(data) {
|
||
return data.data;
|
||
},
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (me) {
|
||
setUser(me.data);
|
||
}
|
||
}, [me]);
|
||
|
||
useEffect(() => {
|
||
if (token) {
|
||
cart();
|
||
}
|
||
}, [token]);
|
||
|
||
const queryFromUrl = searchParams.get('q') ?? '';
|
||
|
||
const { data: cartItems } = useQuery({
|
||
queryKey: ['cart_items', cart_id],
|
||
queryFn: () => cart_api.get_cart_items(cart_id!),
|
||
enabled: !!cart_id,
|
||
select(data) {
|
||
return data.data.cart_item;
|
||
},
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (cartItems) {
|
||
const total = cartItems.reduce((sum, item) => sum + item.quantity, 0);
|
||
setCartQuenty(total > 9 ? 9 : total);
|
||
} else if (cart_id === null) {
|
||
setCartQuenty(0);
|
||
}
|
||
}, [cartItems, cart_id]);
|
||
|
||
useEffect(() => {
|
||
setQuery(queryFromUrl);
|
||
}, [queryFromUrl]);
|
||
|
||
const [searchOpen, setSearchOpen] = useState(false);
|
||
const { active, openToolbar, setActive, setOpenToolbar, setCloseToolbar } =
|
||
useCategoryActive();
|
||
|
||
useEffect(() => {
|
||
const handleScroll = () => {
|
||
setIsSticky(window.scrollY > 40);
|
||
};
|
||
|
||
handleScroll();
|
||
|
||
window.addEventListener('scroll', handleScroll);
|
||
return () => window.removeEventListener('scroll', handleScroll);
|
||
}, []);
|
||
|
||
const router = useRouter();
|
||
|
||
return (
|
||
<>
|
||
<div className="w-full bg-[#57A595] h-10 max-lg:hidden">
|
||
<div className="custom-container h-full flex justify-between items-center">
|
||
<ul className="text-sm flex items-center justify-center gap-6">
|
||
{/* Sahifalar */}
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<Link
|
||
href="/about"
|
||
className="flex items-center gap-1.5 font-medium"
|
||
>
|
||
{t('Biz haqimizda')}
|
||
</Link>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<Link
|
||
href="/privacy-policy"
|
||
className="flex items-center gap-1.5 font-medium"
|
||
>
|
||
{t('Maxfiylik siyosati')}
|
||
</Link>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<Link
|
||
href="/faq"
|
||
className="flex items-center gap-1.5 font-medium"
|
||
>
|
||
{t('Savol-javob')}
|
||
</Link>
|
||
</li>
|
||
|
||
{/* Divider */}
|
||
<li className="text-white/30">|</li>
|
||
|
||
{/* Ijtimoiy tarmoqlar */}
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'#'}
|
||
className="flex items-center gap-2"
|
||
aria-label="telegram"
|
||
>
|
||
<Send className="size-4" />
|
||
</a>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'#'}
|
||
className="flex items-center gap-2"
|
||
aria-label="Instagram"
|
||
>
|
||
<Instagram className="size-4" />
|
||
</a>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'#'}
|
||
className="flex items-center gap-2"
|
||
aria-label="Facebook"
|
||
>
|
||
<Facebook className="size-4" />
|
||
</a>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'#'}
|
||
className="flex items-center gap-2"
|
||
aria-label="Twitter"
|
||
>
|
||
<Twitter className="size-4" />
|
||
</a>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'#'}
|
||
className="flex items-center gap-2"
|
||
aria-label="Mail"
|
||
>
|
||
<Mail className="size-4" />
|
||
</a>
|
||
</li>
|
||
<li className="text-white hover:text-white/80 transition-colors cursor-pointer">
|
||
<a
|
||
href={'tel:+998901234567'}
|
||
className="flex items-center gap-2"
|
||
aria-label="Phone"
|
||
>
|
||
<Phone className="size-4" />
|
||
<p>{formatPhone('+998901234567')}</p>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
<ChangeLang />
|
||
</div>
|
||
</div>
|
||
|
||
<section
|
||
className={`py-4 shadow-sm z-50 w-full bg-white transition-all duration-300 mb-5 border-b border-slate-200
|
||
${isSticky ? 'fixed top-0 shadow-md' : 'relative'}
|
||
`}
|
||
>
|
||
<div className="custom-container h-6 flex justify-between items-center gap-3">
|
||
<div
|
||
className="p-2 flex gap-2 rounded-xl cursor-pointer items-center"
|
||
onClick={() => router.push('/')}
|
||
>
|
||
<Image
|
||
src="/logos/logo.png"
|
||
alt="logo"
|
||
width={40}
|
||
unoptimized
|
||
height={20}
|
||
className="w-10 h-fit"
|
||
/>
|
||
<p className="font-semibold">GASTRO</p>
|
||
</div>
|
||
<div className="w-full flex justify-end items-center lg:hidden gap-2">
|
||
<Button
|
||
variant={'ghost'}
|
||
size={'icon'}
|
||
onClick={() => router.push('/search')}
|
||
>
|
||
<Search className="size-5" />
|
||
</Button>
|
||
<MobileLanguageSelector />
|
||
<Sheet>
|
||
<SheetTrigger asChild>
|
||
<Button variant="outline" size="icon">
|
||
<MenuIcon className="size-5" />
|
||
</Button>
|
||
</SheetTrigger>
|
||
<SheetContent className="w-[320px] p-0 flex flex-col">
|
||
<SheetHeader className="px-6 py-4 border-b border-slate-200 bg-gradient-to-r from-[#57A595] to-[#69b5a5]">
|
||
<SheetTitle className="flex items-center gap-3">
|
||
<div className="bg-white p-2 rounded-lg">
|
||
<Image
|
||
width={32}
|
||
unoptimized
|
||
height={32}
|
||
src={'/logos/logo.png'}
|
||
alt="logo"
|
||
className="w-8 h-8"
|
||
/>
|
||
</div>
|
||
<p className="text-white text-xl font-bold">GASTRO</p>
|
||
</SheetTitle>
|
||
</SheetHeader>
|
||
|
||
<div className="flex-1 overflow-y-auto">
|
||
{/* Menu Bo'limlari */}
|
||
<div className="px-4 py-6 space-y-6">
|
||
{/* Asosiy Sahifalar */}
|
||
<div>
|
||
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3 px-2">
|
||
{t('Sahifalar')}
|
||
</h3>
|
||
<nav className="space-y-1">
|
||
<SheetClose asChild>
|
||
<Link
|
||
href="/about"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-slate-100 hover:text-[#57A595] transition-all group"
|
||
>
|
||
<div className="w-1 h-1 rounded-full bg-slate-400 group-hover:bg-[#57A595]" />
|
||
<span className="text-sm font-medium">
|
||
{t('Biz haqimizda')}
|
||
</span>
|
||
<ChevronRight className="size-4 ml-auto opacity-0 group-hover:opacity-100 transition-opacity" />
|
||
</Link>
|
||
</SheetClose>
|
||
<SheetClose asChild>
|
||
<Link
|
||
href="/privacy-policy"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-slate-100 hover:text-[#57A595] transition-all group"
|
||
>
|
||
<div className="w-1 h-1 rounded-full bg-slate-400 group-hover:bg-[#57A595]" />
|
||
<span className="text-sm font-medium">
|
||
{t('Maxfiylik siyosati')}
|
||
</span>
|
||
<ChevronRight className="size-4 ml-auto opacity-0 group-hover:opacity-100 transition-opacity" />
|
||
</Link>
|
||
</SheetClose>
|
||
<SheetClose asChild>
|
||
<Link
|
||
href="/faq"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-slate-100 hover:text-[#57A595] transition-all group"
|
||
>
|
||
<div className="w-1 h-1 rounded-full bg-slate-400 group-hover:bg-[#57A595]" />
|
||
<span className="text-sm font-medium">
|
||
{t('Savol-javob')}
|
||
</span>
|
||
<ChevronRight className="size-4 ml-auto opacity-0 group-hover:opacity-100 transition-opacity" />
|
||
</Link>
|
||
</SheetClose>
|
||
</nav>
|
||
</div>
|
||
|
||
{/* Divider */}
|
||
<div className="h-px bg-slate-200" />
|
||
|
||
{/* Aloqa */}
|
||
<div>
|
||
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3 px-2">
|
||
{t("Biz bilan bog'laning")}
|
||
</h3>
|
||
<nav className="space-y-1">
|
||
<a
|
||
href="#"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-blue-50 hover:text-blue-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-blue-100 text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||
<Send className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">Telegram</span>
|
||
</a>
|
||
<a
|
||
href="#"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-pink-50 hover:text-pink-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-pink-100 text-pink-600 group-hover:bg-pink-600 group-hover:text-white transition-colors">
|
||
<Instagram className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">Instagram</span>
|
||
</a>
|
||
<a
|
||
href="#"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-blue-50 hover:text-blue-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-blue-100 text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||
<Facebook className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">Facebook</span>
|
||
</a>
|
||
<a
|
||
href="#"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-sky-50 hover:text-sky-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-sky-100 text-sky-600 group-hover:bg-sky-600 group-hover:text-white transition-colors">
|
||
<Twitter className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">Twitter</span>
|
||
</a>
|
||
<a
|
||
href="mailto:info@gastro.uz"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-purple-50 hover:text-purple-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-purple-100 text-purple-600 group-hover:bg-purple-600 group-hover:text-white transition-colors">
|
||
<Mail className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">
|
||
info@gastro.uz
|
||
</span>
|
||
</a>
|
||
<a
|
||
href="tel:+998901234567"
|
||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-slate-700 hover:bg-emerald-50 hover:text-emerald-600 transition-all group"
|
||
>
|
||
<div className="p-1.5 rounded-lg bg-emerald-100 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white transition-colors">
|
||
<Phone className="size-3.5" />
|
||
</div>
|
||
<span className="text-sm font-medium">
|
||
{formatPhone('+998901234567')}
|
||
</span>
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</SheetContent>
|
||
</Sheet>
|
||
</div>
|
||
<div className="flex-1 flex gap-3">
|
||
<Button
|
||
variant={'outline'}
|
||
className="h-10 max-lg:hidden cursor-pointer"
|
||
onClick={() => {
|
||
if (openToolbar) {
|
||
setCloseToolbar(false);
|
||
} else if (!openToolbar) {
|
||
setOpenToolbar(true);
|
||
}
|
||
}}
|
||
>
|
||
{openToolbar ? (
|
||
<XIcon className="text-foreground" />
|
||
) : (
|
||
<LayoutGrid className="size-4 text-foreground" />
|
||
)}
|
||
<p className="text-foreground">Kataloglar</p>
|
||
</Button>
|
||
|
||
<div className="relative w-full max-lg:hidden">
|
||
<Input
|
||
placeholder={t('Mahsulot nomi')}
|
||
value={query}
|
||
onFocus={() => setSearchOpen(true)}
|
||
onBlur={() => setTimeout(() => setSearchOpen(false), 200)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' && query.trim()) {
|
||
router.push(`/search?q=${encodeURIComponent(query)}`);
|
||
setSearchOpen(false);
|
||
}
|
||
}}
|
||
onChange={(e) => setQuery(e.target.value)}
|
||
className="border border-slate-200 focus:border-blue-400 focus:ring-blue-400 px-9 h-10"
|
||
/>
|
||
|
||
<Search className="absolute top-1/2 -translate-y-1/2 left-2 size-5 text-muted-foreground" />
|
||
|
||
{searchOpen && (
|
||
<div className="absolute top-full left-0 right-0 mt-2 bg-white border border-slate-200 rounded-xl shadow-lg min-h-[300px] max-h-[600px] overflow-y-auto scrollbar-thin z-50">
|
||
<div className="p-4">
|
||
<SearchResult query={query} />
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<Button
|
||
variant={'ghost'}
|
||
className="h-10 max-lg:hidden cursor-pointer border border-slate-200"
|
||
onClick={() => router.push('/favourite')}
|
||
aria-label="my favouurite product"
|
||
>
|
||
<Heart className="size-4 text-foreground" />
|
||
</Button>
|
||
<Button
|
||
variant={'ghost'}
|
||
id="cart-icon"
|
||
onClick={() => router.push('/cart')}
|
||
className="h-10 relative max-lg:hidden cursor-pointer border border-slate-200"
|
||
>
|
||
<ShoppingCart className="size-4 text-foreground" />
|
||
<Badge className="absolute -top-2 -right-2 line-clamp-1 w-6 flex justify-center items-center">
|
||
{cartQuenty === 9 ? cartQuenty + '+' : cartQuenty}
|
||
</Badge>
|
||
</Button>
|
||
<Button
|
||
variant={'ghost'}
|
||
onClick={() => {
|
||
if (token) {
|
||
router.push('/profile');
|
||
} else {
|
||
router.push('/auth');
|
||
}
|
||
}}
|
||
aria-label="my account"
|
||
className="h-10 max-lg:hidden cursor-pointer border border-slate-200"
|
||
>
|
||
<User className="size-4 text-foreground" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<NavbarMobile />
|
||
|
||
<Popover open={openToolbar} modal={false}>
|
||
<PopoverTrigger className="!h-0 absolute top-12 w-screen">
|
||
<div />
|
||
</PopoverTrigger>
|
||
<PopoverContent
|
||
className="w-[100vw] !p-0 h-[100vh] rounded-b-xl"
|
||
onInteractOutside={() => setOpenToolbar(false)}
|
||
>
|
||
<div className="flex h-[90vh]">
|
||
<div className="border-r border-slate-200 w-[20%] py-3 flex flex-col gap-2 px-3 overflow-y-auto scrollbar-thin bg-slate-50">
|
||
{category &&
|
||
category.map((e, index) => {
|
||
const isActive = active?.name === e.name;
|
||
|
||
return (
|
||
<Button
|
||
key={index}
|
||
variant="secondary"
|
||
onMouseEnter={() => setActive(e)}
|
||
onClick={() => setActive(e)}
|
||
className={`flex justify-between items-center cursor-pointer px-4 h-14 rounded-xl transition-all duration-300 ${
|
||
isActive
|
||
? 'bg-blue-100 border border-blue-400'
|
||
: 'hover:bg-slate-100 border border-transparent'
|
||
}`}
|
||
>
|
||
<div className="flex gap-3 items-center min-w-0">
|
||
<div className={`p-1.5 rounded-lg bg-white shrink-0`}>
|
||
<Image
|
||
src={e.image || '/placeholder.svg'}
|
||
alt={e.name}
|
||
width={500}
|
||
height={500}
|
||
unoptimized
|
||
className="w-5 h-5 object-contain"
|
||
/>
|
||
</div>
|
||
|
||
<p
|
||
className={`text-sm font-medium truncate ${
|
||
isActive ? 'text-blue-700' : 'text-slate-700'
|
||
}`}
|
||
>
|
||
{e.name}
|
||
</p>
|
||
</div>
|
||
|
||
<ChevronRight
|
||
className={`size-5 transition-transform duration-300 ${
|
||
isActive
|
||
? 'rotate-90 text-blue-600'
|
||
: 'text-slate-400'
|
||
}`}
|
||
/>
|
||
</Button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="w-[80%] overflow-y-auto p-8 scrollbar-thin bg-white">
|
||
<h3 className="text-2xl font-bold mb-6 text-blue-600">
|
||
{active?.name}
|
||
</h3>
|
||
|
||
{active && active?.product_types?.length > 0 ? (
|
||
<div className="grid grid-cols-3 gap-4">
|
||
{active.product_types.map((sub, index) => (
|
||
<Button
|
||
key={index}
|
||
onClick={() => {
|
||
setCloseToolbar(false);
|
||
router.push(`/category/${active.id}/${sub.id}`);
|
||
}}
|
||
variant="outline"
|
||
className="justify-start h-12 border border-slate-200 hover:border-blue-400 hover:bg-blue-50 transition-all rounded-xl"
|
||
>
|
||
{sub.name}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="flex flex-col items-center justify-center h-[50vh] text-slate-400">
|
||
<p className="text-lg font-medium">
|
||
{t('Bu kategoriyada hozircha mahsulot yo‘q')}
|
||
</p>
|
||
<p className="text-sm mt-1">{t('Tez orada qo‘shiladi')}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</section>
|
||
|
||
{isSticky && <div className="h-[72px]" />}
|
||
|
||
<style jsx global>{`
|
||
.scrollbar-thin::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
.scrollbar-thin::-webkit-scrollbar-track {
|
||
background: #f1f5f9;
|
||
border-radius: 100px;
|
||
}
|
||
|
||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||
background: #cbd5e1;
|
||
border-radius: 100px;
|
||
}
|
||
|
||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||
background: #94a3b8;
|
||
}
|
||
|
||
.scrollbar-thin {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #cbd5e1 #f1f5f9;
|
||
}
|
||
`}</style>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default Navbar;
|