diff --git a/app/components/desktop/DesktopHeader.tsx b/app/components/desktop/DesktopHeader.tsx index 3045309..d516b5d 100644 --- a/app/components/desktop/DesktopHeader.tsx +++ b/app/components/desktop/DesktopHeader.tsx @@ -1,9 +1,12 @@ import { Link, useLocation, useNavigate } from "@remix-run/react"; -import { useState, type FormEvent } from "react"; -import { Search, Heart, ShoppingBag, User } from "lucide-react"; +import { useState, useRef, useEffect, type FormEvent } from "react"; +import { Search, Heart, ShoppingBag, User, Store } from "lucide-react"; import { useRootData } from "~/hooks/use-root-data"; import { useCartCount } from "~/requestHandler/use-cart-hooks"; +import { useSearchAutocomplete } from "~/requestHandler/use-search-hooks"; +import { useDebounce } from "~/hooks/useDebounce"; import { SunMoonToggle } from "~/components/SunMoonToggle"; +import { AutocompleteSuggestionTypeEnum } from "../../../src/api/types"; import logo from "~/assets/logo/SVG-07.svg"; const NAV = [ @@ -23,15 +26,54 @@ const DesktopHeader = () => { const { user, theme } = useRootData(); const cartCount = useCartCount(); const [q, setQ] = useState(""); + const [open, setOpen] = useState(false); + const searchRef = useRef(null); const isLoggedIn = !!user?.access_token; + const debouncedQ = useDebounce(q.trim(), 400); + const { data: autocompleteData, isLoading: isSuggesting } = + useSearchAutocomplete(debouncedQ); + const suggestions = autocompleteData?.suggestions ?? []; + const showSuggestions = open && q.trim().length > 0; + + // Close the suggestions dropdown when clicking outside the search box. + useEffect(() => { + const onClickOutside = (e: MouseEvent) => { + if (searchRef.current && !searchRef.current.contains(e.target as Node)) { + setOpen(false); + } + }; + document.addEventListener("mousedown", onClickOutside); + return () => document.removeEventListener("mousedown", onClickOutside); + }, []); + const isActive = (to: string) => to === "/" ? location.pathname === "/" : location.pathname.startsWith(to); + const goToSearch = (term: string) => { + const t = term.trim(); + if (!t) return; + setOpen(false); + navigate(`/search/${encodeURIComponent(t)}`); + }; + const onSearch = (e: FormEvent) => { e.preventDefault(); - const term = q.trim(); - if (term) navigate(`/search/${encodeURIComponent(term)}`); + goToSearch(q); + }; + + const onSelectSuggestion = (sug: { + type: AutocompleteSuggestionTypeEnum; + text?: string; + name?: string; + id?: string; + }) => { + if (sug.type === AutocompleteSuggestionTypeEnum.Seller && sug.id) { + setOpen(false); + navigate(`/seller/${sug.id}`); + return; + } + goToSearch(sug.text || sug.name || ""); }; return ( @@ -61,20 +103,79 @@ const DesktopHeader = () => { ))} -
- - setQ(e.target.value)} - name="q" - placeholder="جستجوی محصول، برند یا فروشگاه…" - aria-label="جستجو" - className="w-full bg-transparent outline-none text-[14.5px] text-BLACK placeholder:text-GRAY" - /> - +
+
+ + { + setQ(e.target.value); + setOpen(true); + }} + onFocus={() => setOpen(true)} + onKeyDown={(e) => { + if (e.key === "Escape") setOpen(false); + }} + name="q" + autoComplete="off" + placeholder="جستجوی محصول، برند یا فروشگاه…" + aria-label="جستجو" + className="w-full bg-transparent outline-none text-[14.5px] text-BLACK placeholder:text-GRAY" + /> + + + {showSuggestions ? ( +
+ {isSuggesting && suggestions.length === 0 ? ( +

در حال جستجو...

+ ) : suggestions.length > 0 ? ( + suggestions.map((sug, index) => { + const isSeller = + sug.type === AutocompleteSuggestionTypeEnum.Seller; + const label = isSeller ? sug.name : sug.text; + if (!label) return null; + return ( + + ); + }) + ) : ( + + )} +
+ ) : null} +