Potion/web/src/components/inputs/RecipeSearchBar.tsx
Hayden Hargreaves cbaf34d39c (FIX): Simple fixes that got pointed out last week.
Back to docker fixes.
2026-01-08 21:30:01 -07:00

123 lines
3.9 KiB
TypeScript

import { use, useEffect, useState, type ChangeEvent, type Dispatch, type FormEvent, type SetStateAction } from "react";
import type { SearchFilters } from "../../types/search";
import FilterButton from "../buttons/FilterButton";
import RecipeSearchFilterDropdown from "./RecipeSearchFilterDropdown";
import { SearchRecipes } from "../../services/RecipeService";
import { isApiError } from "../../types/api/error";
import type { Recipe } from "../../types/recipe";
import { useNavigate } from "react-router-dom";
import { FilterContext } from "../../context/FilterContext";
interface RecipeSearchBarProps {
// filters: SearchFilters;
// setFilters: React.Dispatch<React.SetStateAction<SearchFilters>>;
redirect: boolean;
searchOnLoad: boolean;
favorites: boolean;
setRecipes: Dispatch<SetStateAction<Recipe[]>> | null;
// Loading is optional
loading?: boolean;
setLoading?: Dispatch<SetStateAction<boolean>>;
};
export default function RecipeSearchBar({ redirect, searchOnLoad, favorites, setRecipes, loading, setLoading }: RecipeSearchBarProps) {
const navigate = useNavigate();
const { filters, setFilters } = use(FilterContext);
const [displayDropdown, setDisplayDropdown] = useState<boolean>(false);
// SERVER FUNCTIONS
const fetchSearchResults = async () => {
if (redirect) {
await navigate("/v2/web/search");
return;
}
// Should not allow many queries, thought we should allow redirect through loading
if (loading) return;
if (setLoading) setLoading(true);
try {
const result = await SearchRecipes(filters);
if (isApiError(result)) {
console.error(result.message);
return;
}
if (setRecipes)
setRecipes(result);
} finally {
if (setLoading) setLoading(false);
}
}
// HANDLERS
const toggleDropdownHandler = () => setDisplayDropdown(!displayDropdown);
// TODO: Store filters in a global state somewhere!
const searchHandler = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
e.preventDefault();
await fetchSearchResults();
};
const queryInputHandler = (e: ChangeEvent<HTMLInputElement>) => {
const new_filters: SearchFilters = {
...filters,
Search: e.target.value,
};
setFilters(new_filters);
}
// EFFECTS
// TODO: Learn how to use 'useCallback' here to prevent endless loading and fix warning
useEffect(() => {
if (searchOnLoad)
void fetchSearchResults();
}, [searchOnLoad]);
useEffect(() => {
setFilters({
...filters,
Favorites: favorites
});
}, [favorites]);
return (
<form className="w-full px-4 my-8" onSubmit={(e) => void searchHandler(e)}>
<div className="flex w-full gap-x-2">
<div className="relative w-full">
<input type="hidden" name="redirect" value={JSON.stringify(redirect)} />
<input
type="search"
name="search"
placeholder="Search recipes, ingredients..."
value={filters ? filters.Search : ""}
onChange={queryInputHandler}
className="w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button className="absolute left-3 top-1/2 -translate-y-1/2">
<svg
className="h-5 w-5 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</button>
</div>
<FilterButton click={toggleDropdownHandler} />
</div>
<RecipeSearchFilterDropdown filters={filters} setFilters={setFilters} display={displayDropdown} />
</form>
);
}