Menu

Building the Main User-Interface with Functionality

Building the Main User-Interface with Functionality

This file builds the main functionality of your app. It handles user input, fetches recipes from the API based on the search, and displays them in a grid. It also manages states such as loading, no results, and errors, and allows users to click a recipe to view its details in a modal.

File Path: Recipe-Finder\src\components\RecipeFinder.jsx

import React from "react";
import { useState } from "react";
import RecipeModal from "./RecipeModal";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSistrix } from "@fortawesome/free-brands-svg-icons";
import { faXmark, faBug } from "@fortawesome/free-solid-svg-icons";

function RecipeFinder() {
  const [search, setSearch] = useState("");
  const [recipes, setRecipes] = useState([]);
  const [loading, setLoading] = useState(false);
  const [hasSearched, setHasSearched] = useState(false);
  const [selectedRecipe, setSelectedRecipe] = useState(null);

  const fetchRecipes = async () => {
    if (!search) return;
    setHasSearched(true);

    setLoading(true);
    try {
      const response = await fetch(
        `https://tasty.p.rapidapi.com/recipes/list?from=0&size=10&q=${search}`,
        {
          method: "GET",
          headers: {
            "X-RapidAPI-Key":
              "7a3a553868mshd9dc2d1e96281a6p1ce958jsn48611636ad48",
            "X-RapidAPI-Host": "tasty.p.rapidapi.com",
          },
        },
      );
      const data = await response.json();
      console.log("API Response:", data);
      setRecipes(data.results || []);
    } catch (error) {
      console.error("Error fetching recipes:", error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <>
      <div className="recipe-finder-component w-[90%] md:w-[70%] lg:w-[80%] xl:w-[80%] py-5 px-2 mb-5">
        <div className="search-bar-container w-full md:w-[90%] lg:w-[80%] xl:w-[50%] relative flex items-center">
          <input
            type="search"
            name="search-recipe"
            id="search-recipe"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") {
                fetchRecipes();
              }
            }}
            className="w-full text-base md:text-lg py-2 px-3"
            placeholder="Search the recipe....."
          />
          <FontAwesomeIcon
            icon={faSistrix}
            className="search-icon text-lg md:text-2xl absolute right-10 cursor-pointer"
            onClick={fetchRecipes}
          />
          {search && (
            <FontAwesomeIcon
              icon={faXmark}
              className="clear-icon text-base md:text-lg absolute right-2 cursor-pointer"
              onClick={() => {
                setSearch("");
              }}
            />
          )}
        </div>

        <div className="recipe-container mt-6">
          {loading ? (
            <>
              <div className="flex justify-center items-center">
                <div className="loader"></div>
              </div>
              <p className="text-xl text-gray-600 mt-3">
                Fetching delicious recipes.....
              </p>
            </>
          ) : !hasSearched ? (
            <p className="text-gray-500 text-center text-base sm:text-lg">
              Your recipes will be displayed here
            </p>
          ) : recipes.length === 0 ? (
            // Error Message Box
            <div className="error-msg-container flex flex-col items-center justify-center py-6 px-4 md:px-6 lg:px-10">
              <FontAwesomeIcon
                icon={faBug}
                style={{ color: "rgb(255, 60, 44)" }}
                className="text-2xl mb-2"
              />
              <p className="text-red-600 text-center text-sm md:text-base">
                Recipe not found
              </p>
              <p className="text-red-600 text-center text-sm md:text-base mt-1">
                Please check the name or try searching again.
              </p>
            </div>
          ) : (
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 recipe-grid gap-2">
              {recipes.map((recipe) => (
                <div
                  key={recipe.id}
                  className="dish flex flex-col cursor-pointer"
                  onClick={() => {
                    setSelectedRecipe(recipe);
                  }}
                >
                  <div className="img-container">
                    <img src={recipe.thumbnail_url} alt={recipe.name} />
                  </div>
                  <div className="dish-text p-3">
                    <p className="name text-lg line-clamp-1">{recipe.name}</p>

                    <p className="text-base mt-2 sm:mt-3">
                      <span className="prep -ml-0.75">⏱ Prep Time:</span>{" "}
                      {recipe.total_time_minutes
                        ? recipe.total_time_minutes < 60
                          ? `${recipe.total_time_minutes} mins`
                          : `${(recipe.total_time_minutes / 60).toFixed(1)} hrs`
                        : "N/A"}
                    </p>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Modal Dialog Component */}
      <RecipeModal
        recipe={selectedRecipe}
        onClose={() => setSelectedRecipe(null)}
      />
    </>
  );
}

export default RecipeFinder;