(FEAT): Favorites list completed, roughly.

No functionality still.
This commit is contained in:
Hayden Hargreaves 2025-11-13 14:22:10 -07:00
parent 2916eeef61
commit 45a0d0e54c
5 changed files with 211 additions and 1 deletions

View File

@ -0,0 +1,16 @@
export default function ServingSizeIconSmall() {
return <>
<svg className="h-5 text-blue-600" fill="currentColor" version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32" xmlSpace="preserve">
<g>
<circle cx="12" cy="16" r="5"></circle>
<path d="M12,6C6.5,6,2,10.5,2,16s4.5,10,10,10s10-4.5,10-10S17.5,6,12,6z M12,23c-3.9,0-7-3.1-7-7s3.1-7,7-7s7,3.1,7,7
S15.9,23,12,23z"></path>
<path d="M30,10.5V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v5.5c0,0.2,0,0.4,0,0.5h-1V5c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-1c0-0.2,0-0.4,0-0.5V5
c0-0.6-0.4-1-1-1s-1,0.4-1,1v5.5c0,1.9,0.5,3.4,1.4,4.3c0.7,0.8,1,1.8,0.9,2.7l-1,7.3c-0.1,0.8,0.1,1.6,0.6,2.2S25.2,28,26,28
s1.5-0.3,2.1-0.9s0.8-1.4,0.6-2.2l-1-7.3c-0.1-1,0.2-2,0.9-2.8C29.5,13.8,30,12.3,30,10.5z"></path>
</g>
</svg>
</>
}

View File

@ -0,0 +1,25 @@
interface StarIconSmallProps {
filled: boolean;
};
export default function StarIconSmall({ filled }: StarIconSmallProps) {
return <>
{filled ? (
<svg className="h-4 text-blue-600" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.632 9.201a.628.628 0 0 1-.22.678l-5.726 4.96 1.727 7.394a.606.606 0 0 1-.935.676l-6.503-3.953-6.503 3.953a.713.713 0 0 1-.374.112.57.57 0 0 1-.34-.109.629.629 0 0 1-.222-.679l1.729-7.393L.539 9.879A.607.607 0 0 1 .897 8.78l7.536-.635 2.965-7.083a.62.62 0 0 1 1.155.001l2.965 7.082 7.536.635a.63.63 0 0 1 .578.42z">
</path>
<path fill="none" d="M0 0h24v24H0z"></path>
</svg>
) : (
<svg className="h-4 text-gray-500" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.054 8.781l-7.536-.635-2.965-7.082a.619.619 0 0 0-1.155 0L8.433 8.145.896 8.78a.607.607 0 0 0-.357 1.1l5.726 4.96-1.729 7.395a.63.63 0 0 0 .223.679.573.573 0 0 0 .339.108.717.717 0 0 0 .374-.111l6.503-3.954 6.503 3.953a.606.606 0 0 0 .935-.677l-1.727-7.392 5.725-4.96a.607.607 0 0 0-.357-1.099zm-6.48 5.698l1.662 7.113-6.261-3.806-6.262 3.807 1.663-7.114-5.513-4.776 7.257-.611 2.855-6.817 2.855 6.817 7.257.611z">
</path>
<path fill="none" d="M0 0h24v24H0z"></path>
</svg>
)}
</>
}

View File

@ -0,0 +1,9 @@
export default function TimeIconSmall() {
return <>
<svg className="h-5 text-blue-600" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 7V12L14.5 13.5M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</>;
}

View File

@ -0,0 +1,56 @@
import type { Recipe } from "../../types/recipe";
import ServingSizeIconSmall from "../icons/ServingSizeIconSmall";
import StarIconSmall from "../icons/StarIconSmall";
import TimeIconSmall from "../icons/TimeIconSmall";
interface FavoriteResultProps {
recipe: Recipe;
};
export default function FavoriteResult({ recipe }: FavoriteResultProps) {
return <>
<div className="w-full p-2 border-b border-gray-200 hover:bg-gray-100 duration-200 flex items-center flex-col md:flex-row even:bg-[#f8f8f8] cursor-pointer">
<img className="bg-gray-50 size-56 md:size-40 rounded-md border-0" src="/v1/web/static/img/recipe_placeholder.png" />
<div className="text-gray-700 p-4 flex flex-col items-center md:items-start w-full">
<div className="flex flex-col md:flex-row items-center md:items-start justify-between w-full">
<div className="flex flex-col items-center md:items-start">
<h3 className="text-xl font-semibold text-black pb-1">
{recipe.Title} <span className="text-sm font-normal hidden md:inline">{recipe.Category}</span>
</h3>
<div className="text-sm flex gap-x-3 gap-y-1 items-center flex-wrap">
<span className="flex gap-x-1 align-center">
<TimeIconSmall />
{recipe.Duration.Total} min
</span>
<span className="flex gap-x-1 align-center">
{Array.from({ length: recipe.Difficulty }).map((_, i) => (
<StarIconSmall key={`${recipe.Id}-filled-${i}`} filled={true} />
))}
{Array.from({ length: 5 - recipe.Difficulty }).map((_, i) => (
<StarIconSmall key={`${recipe.Id}-unfilled-${i}`} filled={false} />
))}
</span>
<span className="flex gap-x-1 align-center">
<ServingSizeIconSmall />
Serves {recipe.Serves}
</span>
</div>
</div>
<div className="mb-2 mt-4 md:my-0 hidden md:block">
<svg className="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2 9.1371C2 14 6.01943 16.5914 8.96173 18.9109C10 19.7294 11 20.5 12 20.5C13 20.5 14 19.7294 15.0383 18.9109C17.9806 16.5914 22 14 22 9.1371C22 4.27416 16.4998 0.825464 12 5.50063C7.50016 0.825464 2 4.27416 2 9.1371Z"
fill="currentColor"></path>
</svg>
</div>
</div>
<p className="text-sm my-2 text-center md:text-left overflow-hidden text-ellipsis"
style={{ display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical" }}>
{recipe.Description}
</p>
</div>
</div>
</>;
}

View File

@ -1,7 +1,111 @@
import { useEffect, useState } from "react";
import Banner from "../components/Banner";
import RecipeSearchBar from "../components/inputs/RecipeSearchBar";
import type { Recipe } from "../types/recipe";
import FavoriteResult from "../components/results/FavoriteResult";
export default function Favorites() {
const [recipes, setRecipes] = useState<Recipe[]>([]);
// BUG: Remove this
useEffect(() => {
const recipe: Recipe = {
Id: 1,
Title: "Classic Pancakes",
Description: "Fluffy and delicious pancakes perfect for breakfast.",
Instructions: [
"In a bowl, mix all the dry ingredients.",
"In another bowl, whisk the wet ingredients.",
"Combine both mixes until smooth.",
"Heat a non-stick skillet and pour batter.",
"Cook until bubbles form, flip and cook the other side.",
"Serve warm with syrup."
],
Serves: 4,
Difficulty: 2, // scale 1-5 (example)
Duration: {
Total: 20,
Prep: 5,
Cook: 15
},
Category: "breakfast",
Ingredients: [
{ Name: "Flour", Quantity: "2 cups" },
{ Name: "Milk", Quantity: "1.5 cups" },
{ Name: "Egg", Quantity: "1 large" },
{ Name: "Baking Powder", Quantity: "2 teaspoons" },
{ Name: "Salt", Quantity: "0.5 teaspoon" },
{ Name: "Sugar", Quantity: "1 tablespoon" }
],
UserId: 101,
Modified: new Date("2025-10-30T09:00:00"),
Created: new Date("2025-10-01T08:30:00"),
Tags: [
{ Id: 1, Name: "easy", Created: new Date("2025-01-01T12:00:00") },
{ Id: 2, Name: "quick", Created: new Date("2025-01-02T12:00:00") },
{ Id: 3, Name: "breakfast", Created: new Date("2025-01-03T12:00:00") }
],
Favorite: true
};
const recipe2: Recipe = {
Id: 2,
Title: "Classic Pancakes",
Description: "Fluffy and delicious pancakes perfect for breakfast.",
Instructions: [
"In a bowl, mix all the dry ingredients.",
"In another bowl, whisk the wet ingredients.",
"Combine both mixes until smooth.",
"Heat a non-stick skillet and pour batter.",
"Cook until bubbles form, flip and cook the other side.",
"Serve warm with syrup."
],
Serves: 4,
Difficulty: 2, // scale 1-5 (example)
Duration: {
Total: 20,
Prep: 5,
Cook: 15
},
Category: "breakfast",
Ingredients: [
{ Name: "Flour", Quantity: "2 cups" },
{ Name: "Milk", Quantity: "1.5 cups" },
{ Name: "Egg", Quantity: "1 large" },
{ Name: "Baking Powder", Quantity: "2 teaspoons" },
{ Name: "Salt", Quantity: "0.5 teaspoon" },
{ Name: "Sugar", Quantity: "1 tablespoon" }
],
UserId: 101,
Modified: new Date("2025-10-30T09:00:00"),
Created: new Date("2025-10-01T08:30:00"),
Tags: [
{ Id: 1, Name: "easy", Created: new Date("2025-01-01T12:00:00") },
{ Id: 2, Name: "quick", Created: new Date("2025-01-02T12:00:00") },
{ Id: 3, Name: "breakfast", Created: new Date("2025-01-03T12:00:00") }
],
Favorite: true
};
setRecipes([recipe, recipe2]);
}, []);
return (
<>
<p>Favorites</p>
<Banner content="Favorites" />
<RecipeSearchBar filters={null} redirect={false} searchOnLoad={true} favorites={true} />
<hr className="text-gray-300 w-full" />
<div id="result-list" className="flex flex-col w-full p-4 items-center">
{recipes.length < 1 ? (
<p className="text-gray-700 text-sm py-4">No results</p>
) : (
<>
{recipes.map(recipe => <FavoriteResult key={recipe.Id} recipe={recipe} />)}
<p className="text-gray-700 text-sm py-4">End of results</p>
</>
)}
</div>
</>
);
}