-);
\ No newline at end of file
+);
diff --git a/src/components/RecipeDetails/index.jsx b/src/components/RecipeDetails/index.jsx
index c18cd3e..37f345b 100644
--- a/src/components/RecipeDetails/index.jsx
+++ b/src/components/RecipeDetails/index.jsx
@@ -1,38 +1,36 @@
+/* eslint-disable react/no-unknown-property */
/* eslint-disable react/prop-types */
-import { Clock, Users } from 'lucide-react';
-import { useRecipeNutrition } from '../../hooks/useCalorieUnits';
-import { closeIcon } from '../../svgIcons/svgIcons';
+import { Clock, Users, BookOpen } from "lucide-react";
+import { closeIcon } from "../../svgIcons/svgIcons";
export const RecipeDetails = ({ recipe, onClose }) => {
- const { data: nutrition, isLoading } = useRecipeNutrition(recipe?.id);
-
- return (
-
-
-
-
{recipe?.title}
-
![{recipe?.title}]({recipe?.image})
-
-
-
- {recipe?.readyInMinutes} mins
-
-
-
- {recipe?.servings} servings
-
- {isLoading ? (
-
Loading nutrition info...
- ) : (
- nutrition && (
-
- Amount: {nutrition?.amount}
- Units: {nutrition?.units}
-
- )
- )}
-
-
+ return (
+
+
+
+
{recipe?.title}
+
![{recipe?.title}]({recipe?.image})
+
+
+
+ {recipe?.readyInMinutes || "N/A"} mins
+
+
+
+ {recipe?.servings || (recipe?.readyInMinutes > 30 ? "6" : "2")}{" "}
+ servings
+
+
+
+ Directions
+
+
+ {recipe?.instructions || "N/A"}
+
- );
+
+
+ );
};
diff --git a/src/components/RecipeFinder.css b/src/components/RecipeFinder.css
index db7f358..def036f 100644
--- a/src/components/RecipeFinder.css
+++ b/src/components/RecipeFinder.css
@@ -1,170 +1,160 @@
.container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 2rem 1rem;
- }
-
- .title {
- font-size: 2rem;
- font-weight: bold;
- text-align: center;
- margin-bottom: 2rem;
- }
-
- .search-container {
- position: relative;
- width: 100%;
- max-width: 200px;
- margin: 0 auto 1rem;
- }
-
- .search-input {
- width: 100%;
- padding: 1rem;
- padding-left: 3rem;
- border: 1px solid #ccc;
- border-radius: 8px;
- font-size: 1rem;
- }
-
- .search-icon {
- position: absolute;
- left: 1rem;
- top: 50%;
- transform: translateY(-50%);
- color: #666;
- }
-
- .filters-container {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- margin-bottom: 2rem;
- }
-
- .filter-select,
- .filter-input {
- padding: 1rem;
- border: 1px solid #ccc;
- border-radius: 4px;
- font-size: 1rem;
- }
-
- .recipe-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 1.5rem;
- }
-
- .recipe-card {
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- cursor: pointer;
- transition: transform 0.2s;
- }
-
- .recipe-card:hover {
- transform: scale(1.03);
- }
-
- .recipe-image {
- width: 100%;
- height: 200px;
- object-fit: cover;
- }
-
- .recipe-content {
- padding: 1rem;
- }
-
- .recipe-title {
- font-size: 1.25rem;
- font-weight: 600;
- margin-bottom: 0.5rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .recipe-info {
- display: flex;
- gap: 1rem;
- font-size: 0.875rem;
- color: #666;
- }
-
- .info-item {
- display: flex;
- align-items: center;
- }
-
- .info-icon {
- margin-right: 0.25rem;
- }
-
- .modal-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1rem;
- }
-
- .modal-content {
- background: white;
- border-radius: 8px;
- max-width: 600px;
- width: 100%;
- max-height: 90vh;
- overflow-y: auto;
- padding: 1.5rem;
- position: relative;
- }
-
- .close-button {
- position: absolute;
- right: 1rem;
- top: 1rem;
- background: none;
- border: none;
- font-size: 1.5rem;
- cursor: pointer;
- color: #666;
- }
-
- .close-button:hover {
- color: #333;
- }
-
- .modal-title {
- font-size: 1.5rem;
- font-weight: bold;
- margin-bottom: 1rem;
- }
-
- .modal-image {
- width: 100%;
- height: 300px;
- object-fit: cover;
- border-radius: 8px;
- margin-bottom: 1rem;
- }
-
- .loading {
- text-align: center;
- padding: 2rem;
- }
-
- .error {
- color: #dc2626;
- text-align: center;
- padding: 2rem;
- }
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem 1rem;
+}
- .filters-section{
- align-items: center;
- }
\ No newline at end of file
+.title {
+ font-size: 2rem;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.search-container {
+ position: relative;
+ max-width: 200px;
+ margin: 0 auto 1rem;
+}
+
+.search-input {
+ width: 100%;
+ padding: 1rem 1rem 1rem 3rem;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ font-size: 1rem;
+}
+
+.search-icon {
+ position: absolute;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #666;
+}
+
+.filters-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ justify-content: center;
+ margin-bottom: 2rem;
+}
+
+.filter-select,
+.filter-input {
+ padding: 1rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 1rem;
+}
+
+.recipe-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1.5rem;
+}
+
+.recipe-card {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform 0.2s;
+}
+
+.recipe-card:hover {
+ transform: scale(1.03);
+}
+
+.recipe-image {
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+}
+
+.recipe-content {
+ padding: 1rem;
+}
+
+.recipe-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.recipe-info {
+ display: flex;
+ gap: 1rem;
+ font-size: 0.875rem;
+ color: #666;
+}
+
+.info-item {
+ display: flex;
+ align-items: center;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+}
+
+.modal-content {
+ background: white;
+ border-radius: 8px;
+ max-width: 600px;
+ width: 100%;
+ max-height: 90vh;
+ overflow-y: auto;
+ padding: 1.5rem;
+ position: relative;
+}
+
+.close-button {
+ position: absolute;
+ right: 1rem;
+ top: 1rem;
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: #666;
+}
+
+.close-button:hover {
+ color: #333;
+}
+
+.modal-title {
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+}
+
+.modal-image {
+ width: 100%;
+ height: 300px;
+ object-fit: cover;
+ border-radius: 8px;
+ margin-bottom: 1rem;
+}
+
+.loading,
+.error {
+ text-align: center;
+ padding: 2rem;
+}
+
+.error {
+ color: #dc2626;
+}
diff --git a/src/components/RecipeFinder.jsx b/src/components/RecipeFinder.jsx
index 9c4fe4f..dd86d52 100644
--- a/src/components/RecipeFinder.jsx
+++ b/src/components/RecipeFinder.jsx
@@ -1,55 +1,59 @@
-import { useReducer } from 'react';
-import { RecipeSearchBar } from '../components/SearchBar/index';
-import { RecipeFilters } from '../components/Filters/index';
-import { RecipeGrid } from '../components/RecipeGrid/index';
-import { RecipeDetails } from '../components/RecipeDetails/index';
-import { useRecipes } from '../hooks/useRecipe';
-import { initialState, recipeReducer } from '../store/RecipeStore';
-import '../components/RecipeFinder.css';
+import { useReducer } from "react";
+import { RecipeSearchBar } from "../components/SearchBar/index";
+import { RecipeFilters } from "../components/Filters/index";
+import { RecipeGrid } from "../components/RecipeGrid/index";
+import { RecipeDetails } from "../components/RecipeDetails/index";
+import { useDetailedRecipes } from "../hooks/useRecipe";
+import { initialState, recipeReducer } from "../store/RecipeStore";
+import "../components/RecipeFinder.css";
export const RecipeFinder = () => {
- const [state, dispatch] = useReducer(recipeReducer, initialState);
- const { data: recipes, isLoading, error } = useRecipes(
- state?.searchQuery,
- state?.filters
- );
+ const [state, dispatch] = useReducer(recipeReducer, initialState);
+ const {
+ data: recipes,
+ isLoading,
+ error,
+ } = useDetailedRecipes(state?.searchQuery, state?.filters);
- const handleSearch = (query) => {
- dispatch({ type: 'SET_SEARCH', payload: query });
- };
+ const handleSearch = (query) => {
+ dispatch({ type: "SET_SEARCH", payload: query });
+ };
- const handleFilterChange = (field, value) => {
- dispatch({ type: 'SET_FILTER', field, payload: value });
- };
+ const handleFilterChange = (field, value) => {
+ dispatch({ type: "SET_FILTER", field, payload: value });
+ };
- const handleRecipeClick = (recipe) => {
- console.log("Recipe clicked:", recipe);
- dispatch({ type: 'SET_SELECTED_RECIPE', payload: recipe });
- };
-
- if (error) return
Error loading recipes
+ const handleRecipeClick = (recipe) => {
+ dispatch({ type: "SET_SELECTED_RECIPE", payload: recipe });
+ };
+ if (error) return
Error loading recipes
;
return (
-
Assignment 4
-
Recipe Finder
-
-
-
-
- {isLoading ? (
-
Loading recipes...
- ):(
-
- )}
+
Assignment 4
+
Recipe Finder
+
+
+
+
+ {isLoading ? (
+
Loading recipes...
+ ) : (
+
+ )}
- {state?.selectedRecipe && (
-
dispatch({ type: 'SET_SELECTED_RECIPE', payload: null })}
- />
- )}
+ {state?.selectedRecipe && (
+
+ dispatch({ type: "SET_SELECTED_RECIPE", payload: null })
+ }
+ />
+ )}
);
-};
\ No newline at end of file
+};
diff --git a/src/components/RecipeGrid/index.jsx b/src/components/RecipeGrid/index.jsx
index f66cdb4..97e5ce3 100644
--- a/src/components/RecipeGrid/index.jsx
+++ b/src/components/RecipeGrid/index.jsx
@@ -1,14 +1,14 @@
/* eslint-disable react/prop-types */
-import { RecipeDetailsCard } from '../RecipeCard/index';
+import { RecipeDetailsCard } from "../RecipeCard/index";
export const RecipeGrid = ({ recipes, onRecipeClick }) => (
-
- {recipes?.map((recipe) => (
- onRecipeClick(recipe)}
- />
- ))}
-
-);
\ No newline at end of file
+
+ {recipes?.map((recipe) => (
+ onRecipeClick(recipe)}
+ />
+ ))}
+
+);
diff --git a/src/components/SearchBar/index.jsx b/src/components/SearchBar/index.jsx
index 6739e08..b66ba39 100644
--- a/src/components/SearchBar/index.jsx
+++ b/src/components/SearchBar/index.jsx
@@ -1,14 +1,14 @@
/* eslint-disable react/prop-types */
-import { Search } from 'lucide-react';
+import { Search } from "lucide-react";
export const RecipeSearchBar = ({ onSearch }) => (
onSearch(e?.target?.value)}
+ onChange={(e) => onSearch(e?.target?.value)}
className="search-input"
/>
-);
\ No newline at end of file
+);
diff --git a/src/components/apiUtils/allApi.js b/src/components/apiUtils/allApi.js
new file mode 100644
index 0000000..bc24f76
--- /dev/null
+++ b/src/components/apiUtils/allApi.js
@@ -0,0 +1,9 @@
+export const fetchRecipeDetails = async (id) => {
+ const response = await fetch(
+ `https://api.spoonacular.com/recipes/${id}/information?includeNutrition=false&apiKey=2bed23978b4d417081acee70da2f9f5f`
+ );
+ if (!response?.ok) {
+ throw new Error("Failed to fetch recipe details");
+ }
+ return await response?.json();
+};
diff --git a/src/hooks/useCalorieUnits.js b/src/hooks/useCalorieUnits.js
deleted file mode 100644
index 85a9aea..0000000
--- a/src/hooks/useCalorieUnits.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-
-export const useRecipeNutrition = (recipeId) => {
- return useQuery({
- queryKey: ['recipeNutrition', recipeId],
- queryFn: async () => {
- const response = await fetch(
- `https://api.spoonacular.com/recipes/${recipeId}/nutritionWidget.json?apiKey=a2f1ea26b02d4919b35c7152b5ebac6d`
- );
- if (!response?.ok) {
- throw new Error("Failed to fetch nutrition data");
- }
- return response?.json();
- },
- enabled: !!recipeId,
- });
-};
diff --git a/src/hooks/useRecipe.js b/src/hooks/useRecipe.js
index b01915e..ad729d6 100644
--- a/src/hooks/useRecipe.js
+++ b/src/hooks/useRecipe.js
@@ -1,23 +1,30 @@
-import { useQuery } from '@tanstack/react-query';
+/* eslint-disable no-unsafe-optional-chaining */
+import { useQuery } from "@tanstack/react-query";
+import { fetchRecipeDetails } from "../components/apiUtils/allApi";
-export const useRecipes = (searchQuery, filters) => {
- return useQuery({
- queryKey: ['recipes', searchQuery, filters],
- queryFn: async () => {
- const params = new URLSearchParams({
- apiKey: 'a2f1ea26b02d4919b35c7152b5ebac6d',
- query: searchQuery,
- cuisine: filters?.cuisine,
- ...(filters?.diet && { diet: filters?.diet }),
- ...(filters?.maxTime && { maxReadyTime: filters?.maxTime }),
- });
+export const useDetailedRecipes = (searchQuery, filters) => {
+ return useQuery({
+ queryKey: ["detailedRecipes", searchQuery, filters],
+ queryFn: async () => {
+ const params = new URLSearchParams({
+ apiKey: "2bed23978b4d417081acee70da2f9f5f",
+ query: searchQuery,
+ cuisine: filters?.cuisine,
+ ...(filters?.diet && { diet: filters?.diet }),
+ ...(filters?.maxTime && { maxReadyTime: filters?.maxTime }),
+ });
- const response = await fetch(
- `https://api.spoonacular.com/recipes/complexSearch?${params}&_start=0&_limit=100`
- );
- const data = await response?.json();
- return data?.results;
- },
- enabled: true,
- });
+ const response = await fetch(
+ `https://api.spoonacular.com/recipes/complexSearch?${params}`
+ );
+ const { results } = await response?.json();
+
+ const detailedRecipes = await Promise?.all(
+ results?.map((recipe) => fetchRecipeDetails(recipe?.id))
+ );
+
+ return detailedRecipes;
+ },
+ enabled: true,
+ });
};
\ No newline at end of file