Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

197
src/pages/search/page.tsx Normal file
View 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>
</>
);
}

View 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;
}