Initial commit
This commit is contained in:
197
src/pages/search/page.tsx
Normal file
197
src/pages/search/page.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
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 { 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 { ProductCard } from "../menu/components/ProductCard/ProductCard";
|
||||
import styles from "./search.module.css";
|
||||
|
||||
export default function SearchPage() {
|
||||
const { t } = useTranslation();
|
||||
const { id } = 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 { 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={`/${id}/menu`}>{t("menu.search")}</ProHeader>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: "82vh",
|
||||
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: 16,
|
||||
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 ? (
|
||||
<ProductCard 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>
|
||||
<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={`${id}/cart`} style={{ width: "100%" }}>
|
||||
<Button
|
||||
type="primary"
|
||||
shape="round"
|
||||
style={{ width: "100%", height: 48, marginBottom: 16 }}
|
||||
>
|
||||
{t("menu.cart")}
|
||||
</Button>
|
||||
</Link>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
18
src/pages/search/search.module.css
Normal file
18
src/pages/search/search.module.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.noResults {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
.noResultsText {
|
||||
font-size: 16px !important;
|
||||
color: #6b7280 !important;
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
:global(.darkApp) .noResultsText {
|
||||
color: #9ca3af !important;
|
||||
}
|
||||
Reference in New Issue
Block a user