From 1a13e27a960aacfef40e11de5e27f410f8c147d7 Mon Sep 17 00:00:00 2001 From: himanshu-is-noob Date: Sun, 19 Oct 2025 06:52:07 +0530 Subject: [PATCH] Improved the UI by aligning Images of recipe cards to center and added close 'x' button on random card and made all this responsive --- package.json | 4 +- src/pages/Recipes.jsx | 224 ++++++++++++++++++++++++++---------------- src/styles.css | 7 +- vite.config.js | 3 +- 4 files changed, 148 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 8d15f9e..8f7aa48 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.14", "leaflet": "^1.9.4", "lucide-react": "^0.546.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-leaflet": "^4.2.1", - "react-router-dom": "^6.27.0" + "react-router-dom": "^6.27.0", + "tailwindcss": "^4.1.14" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", diff --git a/src/pages/Recipes.jsx b/src/pages/Recipes.jsx index 9b352c1..e36efa4 100644 --- a/src/pages/Recipes.jsx +++ b/src/pages/Recipes.jsx @@ -1,90 +1,144 @@ -/** - * RECIPES DASHBOARD TODOs - * ----------------------- - * Easy: - * - [ ] Show meal category & area on cards - * - [ ] Add skeleton loader for images - * - [ ] Improve random recipe section layout - * - [ ] Add clear search button - * Medium: - * - [ ] Modal or drawer with full instructions & ingredients list - * - [ ] Filter by category / area (use list endpoints) - * - [ ] Pagination or load more - * - [ ] Favorite meals (localStorage) - * Advanced: - * - [ ] Nutrition estimation integration (open API) – optional - * - [ ] Tag-based browsing (ingredient tags) - * - [ ] Offline caching of last search - * - [ ] Extract service + hook (useMealsSearch) - */ -import { useEffect, useState } from 'react'; -import Loading from '../components/Loading.jsx'; -import ErrorMessage from '../components/ErrorMessage.jsx'; -import Card from '../components/Card.jsx'; -import HeroSection from '../components/HeroSection'; -import Food from '../Images/Food.jpg'; + /** + * RECIPES DASHBOARD TODOs + * ----------------------- + * Easy: + * - [ ] Show meal category & area on cards + * - [ ] Add skeleton loader for images + * - [ ] Improve random recipe section layout + * - [ ] Add clear search button + * Medium: + * - [ ] Modal or drawer with full instructions & ingredients list + * - [ ] Filter by category / area (use list endpoints) + * - [ ] Pagination or load more + * - [ ] Favorite meals (localStorage) + * Advanced: + * - [ ] Nutrition estimation integration (open API) – optional + * - [ ] Tag-based browsing (ingredient tags) + * - [ ] Offline caching of last search + * - [ ] Extract service + hook (useMealsSearch) + */ + import { useEffect, useState } from 'react'; + import Loading from '../components/Loading.jsx'; + import ErrorMessage from '../components/ErrorMessage.jsx'; + import Card from '../components/Card.jsx'; + import HeroSection from '../components/HeroSection'; + import Food from '../Images/Food.jpg'; -export default function Recipes() { - const [ingredient, setIngredient] = useState('chicken'); - const [meals, setMeals] = useState([]); - const [randomMeal, setRandomMeal] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + export default function Recipes() { + const [ingredient, setIngredient] = useState('chicken'); + const [meals, setMeals] = useState([]); + const [randomMeal, setRandomMeal] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - useEffect(() => { search(); }, []); + useEffect(() => { search(); }, []); - async function search() { - try { - setLoading(true); setError(null); - const res = await fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${encodeURIComponent(ingredient)}`); - if (!res.ok) throw new Error('Failed to fetch'); - const json = await res.json(); - setMeals(json.meals || []); - } catch (e) { setError(e); } finally { setLoading(false); } - } + async function search() { + try { + setLoading(true); setError(null); + const res = await fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${encodeURIComponent(ingredient)}`); + if (!res.ok) throw new Error('Failed to fetch'); + const json = await res.json(); + setMeals(json.meals || []); + } catch (e) { setError(e); } finally { setLoading(false); } + } - async function random() { - try { - setLoading(true); setError(null); - const res = await fetch('https://www.themealdb.com/api/json/v1/1/random.php'); - if (!res.ok) throw new Error('Failed to fetch'); - const json = await res.json(); - setRandomMeal(json.meals?.[0] || null); - } catch (e) { setError(e); } finally { setLoading(false); } - } + async function random() { + try { + setLoading(true); setError(null); + const res = await fetch('https://www.themealdb.com/api/json/v1/1/random.php'); + if (!res.ok) throw new Error('Failed to fetch'); + const json = await res.json(); + setRandomMeal(json.meals?.[0] || null); + } catch (e) { setError(e); } finally { setLoading(false); } + } - return ( -
- - Delicious Made Simple - - } - subtitle="Wholesome recipes with heart, made easy for every home cook" - /> -

Recipe Finder

-
{ e.preventDefault(); search(); }} className="inline-form"> - setIngredient(e.target.value)} placeholder="Ingredient" /> - - -
- {loading && } - - {randomMeal && ( - Source}> - - {/* TODO: Show instructions modal */} - - )} -
- {meals.map(m => ( - - - - ))} -
-
- ); -} + return ( +
+ + Delicious Made Simple + + } + subtitle="Wholesome recipes with heart, made easy for every home cook" + /> +

Recipe Finder

+ +
+ +
{ e.preventDefault(); search(); }} className="inline-form"> + setIngredient(e.target.value)} placeholder="Ingredient" className="border rounded-lg px-3 py-2 text-sm sm:text-base" /> + + +
+ +
+ + {loading && } + + {randomMeal && ( +
+ + + {`Random: ${randomMeal.strMeal}`} + +
+ } + + footer={ + + View Source → + + + } + + > + +
+ {randomMeal.strMeal} +
+ +
+ )} + +
+ + {meals.map((m) => ( +
+ +
+
+ {m.strMeal} +
+
+
+
+ ))} +
+ + ); + } diff --git a/src/styles.css b/src/styles.css index fdc0b62..bd446ee 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,5 @@ /* Light/Dark theme variables */ +@import "tailwindcss"; :root { --bg: #ffffff; --bg-alt: #f5f5f7; @@ -113,7 +114,7 @@ a:hover { -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; - text-fill-color: transparent; + /* text-fill-color: transparent; */ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); } @@ -123,7 +124,7 @@ a:hover { -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; - text-fill-color: transparent; + /* text-fill-color: transparent; */ text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.25); } @@ -814,4 +815,4 @@ blockquote { font-size: 1rem; } } -} + diff --git a/vite.config.js b/vite.config.js index 68c1aef..76b020d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,9 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite' export default defineConfig({ - plugins: [react()], + plugins: [react() , tailwindcss(),], server: { proxy: { '/api': {