204 lines
6.2 KiB
TypeScript
204 lines
6.2 KiB
TypeScript
import { ProBlack2 } from "ThemeConstants";
|
|
import { Button, Divider, Input, Row } from "antd";
|
|
import SearchIcon from "components/Icons/SearchIcon";
|
|
import LoadingSpinner from "components/LoadingSpinner";
|
|
import ProHeader from "components/ProHeader/ProHeader";
|
|
import ProText from "components/ProText";
|
|
import useBreakPoint from "hooks/useBreakPoint";
|
|
import { CartButton } from "pages/menu/components/CartButton/CartButton";
|
|
import { SearchMenu } from "pages/menu/components/SearchMenu/SearchMenu";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Link, useParams, useSearchParams } from "react-router-dom";
|
|
import { useGetMenuQuery } from "redux/api/others";
|
|
import { useAppSelector } from "redux/hooks";
|
|
import { Product } from "utils/types/appTypes";
|
|
import styles from "./search.module.css";
|
|
|
|
export default function SearchPage() {
|
|
const { t } = useTranslation();
|
|
const { subdomain } = useParams();
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const { themeName } = useAppSelector((state) => state.theme);
|
|
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "");
|
|
const [searchResults, setSearchResults] = useState<Product[]>([]);
|
|
const [isSearching, setIsSearching] = useState(false);
|
|
const restaurantID = localStorage.getItem("restaurantID");
|
|
const { isDesktop } = useBreakPoint();
|
|
|
|
const { data: menuData } = useGetMenuQuery(restaurantID as string, {
|
|
skip: !restaurantID,
|
|
});
|
|
|
|
const handleSearch = useCallback(
|
|
(query: string) => {
|
|
if (!query.trim()) {
|
|
setSearchResults([]);
|
|
return;
|
|
}
|
|
|
|
setIsSearching(true);
|
|
|
|
// Simulate search delay for better UX
|
|
setTimeout(() => {
|
|
const results = menuData?.products.filter((product: Product) => {
|
|
const searchLower = query.toLowerCase();
|
|
const nameMatch = product.name?.toLowerCase().includes(searchLower);
|
|
const descriptionMatch = product.description
|
|
?.toLowerCase()
|
|
.includes(searchLower);
|
|
|
|
return nameMatch || descriptionMatch;
|
|
});
|
|
|
|
setSearchResults(results);
|
|
setIsSearching(false);
|
|
}, 300);
|
|
},
|
|
[menuData],
|
|
);
|
|
|
|
// Handle input change and update search params
|
|
const handleInputChange = useCallback(
|
|
(value: string) => {
|
|
setSearchQuery(value);
|
|
|
|
// Update search params
|
|
if (value.trim()) {
|
|
setSearchParams({ q: value });
|
|
} else {
|
|
setSearchParams({});
|
|
}
|
|
},
|
|
[setSearchParams],
|
|
);
|
|
|
|
// Debounced search effect
|
|
useEffect(() => {
|
|
const timeoutId = setTimeout(() => {
|
|
handleSearch(searchQuery);
|
|
}, 300); // 0.3 second debounce
|
|
|
|
return () => clearTimeout(timeoutId);
|
|
}, [searchQuery, handleSearch]);
|
|
|
|
// Initialize search query from URL params on mount
|
|
useEffect(() => {
|
|
const initialQuery = searchParams.get("q") || "";
|
|
if (initialQuery !== searchQuery) {
|
|
setSearchQuery(initialQuery);
|
|
}
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
return (
|
|
<>
|
|
<ProHeader customRoute={`/${subdomain}/menu`}>{t("menu.search")}</ProHeader>
|
|
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: 16,
|
|
scrollBehavior: "smooth",
|
|
overflow: "auto",
|
|
scrollbarWidth: "none",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
padding: 16,
|
|
backgroundColor: themeName === "light" ? "white" : ProBlack2,
|
|
}}
|
|
>
|
|
<Input
|
|
placeholder="Search"
|
|
prefix={
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
}}
|
|
>
|
|
<SearchIcon className={styles.searchIcon} />
|
|
<Divider type="vertical" style={{ top: 0, height: "1.3em" }} />
|
|
</div>
|
|
}
|
|
style={{
|
|
width: "100%",
|
|
borderRadius: 888,
|
|
height: 45,
|
|
border: "none",
|
|
}}
|
|
value={searchQuery}
|
|
onChange={(e) => {
|
|
handleInputChange(e.target.value);
|
|
}}
|
|
size="large"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
padding: "16px 0",
|
|
height: "100%",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: 16,
|
|
overflow: "auto",
|
|
scrollbarWidth: "none",
|
|
}}
|
|
>
|
|
{searchQuery && isSearching ? (
|
|
<div className={styles.loadingContainer}>
|
|
<LoadingSpinner size="large" spinning={true} showText={true} />
|
|
</div>
|
|
) : searchQuery && searchResults?.length > 0 ? (
|
|
<SearchMenu products={searchResults} />
|
|
) : searchQuery && searchResults?.length === 0 && !isSearching ? (
|
|
<div className={styles.noResults}>
|
|
<ProText className={styles.noResultsText}>
|
|
{t("menu.noResultsFound")}
|
|
</ProText>
|
|
</div>
|
|
) : !searchQuery ? (
|
|
<div className={styles.noResults}>
|
|
<ProText className={styles.noResultsText}>
|
|
{t("menu.searchPlaceholder") ||
|
|
"Start typing to search for products..."}
|
|
</ProText>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
{!isDesktop ? (
|
|
<Row
|
|
style={{
|
|
width: "100%",
|
|
padding: "16px 16px 0",
|
|
position: "fixed",
|
|
bottom: 0,
|
|
left: 0,
|
|
backgroundColor: themeName === "light" ? "white" : ProBlack2,
|
|
boxShadow: "0px -1px 3px rgba(0, 0, 0, 0.1)",
|
|
height: "10vh",
|
|
zIndex: 999,
|
|
}}
|
|
>
|
|
<Link to={`/${subdomain}/cart`} style={{ width: "100%" }}>
|
|
<Button
|
|
type="primary"
|
|
shape="round"
|
|
style={{ width: "100%", height: 48, marginBottom: 16 }}
|
|
>
|
|
{t("menu.cart")}
|
|
</Button>
|
|
</Link>
|
|
</Row>
|
|
) : (
|
|
<CartButton />
|
|
)}
|
|
</>
|
|
);
|
|
}
|