From 7c67225f10f61fc2e979d6dee1ea6929463ac13b Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Thu, 30 Oct 2025 13:10:50 -0700 Subject: [PATCH] (FEAT): Continued implementation of the home page. Making lots of progress --- flake.nix | 1 - web/src/assets/images/recipe_placeholder.png | Bin 0 -> 1072 bytes .../assets/images/recipe_placeholder_wide.jpg | Bin 0 -> 7930 bytes web/src/components/Banner.tsx | 12 + web/src/components/buttons/LikeButton.tsx | 10 + web/src/components/cards/ContentCardSmall.tsx | 17 ++ web/src/components/cards/RecipeCardLarge.tsx | 47 ++++ web/src/components/cards/RecipeCardSmall.tsx | 39 +++ web/src/pages/Home.tsx | 232 ++++++++++++++++-- web/src/types/recipe.ts | 37 +++ web/src/types/routes.ts | 5 +- 11 files changed, 382 insertions(+), 18 deletions(-) create mode 100644 web/src/assets/images/recipe_placeholder.png create mode 100644 web/src/assets/images/recipe_placeholder_wide.jpg create mode 100644 web/src/components/Banner.tsx create mode 100644 web/src/components/buttons/LikeButton.tsx create mode 100644 web/src/components/cards/ContentCardSmall.tsx create mode 100644 web/src/components/cards/RecipeCardLarge.tsx create mode 100644 web/src/components/cards/RecipeCardSmall.tsx create mode 100644 web/src/types/recipe.ts diff --git a/flake.nix b/flake.nix index 5e98f56..2465d44 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,6 @@ templ tailwindcss_4 tailwindcss-language-server - tailwindcss-language-server: watchman docker-language-server dockerfile-language-server-nodejs diff --git a/web/src/assets/images/recipe_placeholder.png b/web/src/assets/images/recipe_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..c8664d907a793d90bb47a2859139e80e55b5d63a GIT binary patch literal 1072 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 + {content} + + ); +} diff --git a/web/src/components/buttons/LikeButton.tsx b/web/src/components/buttons/LikeButton.tsx new file mode 100644 index 0000000..d2ffe83 --- /dev/null +++ b/web/src/components/buttons/LikeButton.tsx @@ -0,0 +1,10 @@ +export default function LikeButton() { + return ( + + + + ); +} diff --git a/web/src/components/cards/ContentCardSmall.tsx b/web/src/components/cards/ContentCardSmall.tsx new file mode 100644 index 0000000..882b7ad --- /dev/null +++ b/web/src/components/cards/ContentCardSmall.tsx @@ -0,0 +1,17 @@ + +interface ContentCardSmallProps { + content: string; + target: string; +}; + +export default function ContentCardSmall({ content, target }: ContentCardSmallProps) { + return ( + + ); +} diff --git a/web/src/components/cards/RecipeCardLarge.tsx b/web/src/components/cards/RecipeCardLarge.tsx new file mode 100644 index 0000000..ecba579 --- /dev/null +++ b/web/src/components/cards/RecipeCardLarge.tsx @@ -0,0 +1,47 @@ +import type { Recipe } from "../../types/recipe"; +import RecipePlaceholder from "../../assets/images/recipe_placeholder.png" +import LikeButton from "../buttons/LikeButton"; + +interface RecipeCardLargeProps { + recipe: Recipe | null; +} + +export default function RecipeCardLarge({ recipe }: RecipeCardLargeProps) { + + // HANDLERS + const makeButtonHandler = () => console.log("makeButtonHandler()"); + + + if (recipe == null) { + return

Coming soon!

+ } + + return ( +
+ +
+

+ {recipe.Title} +

+

+ Serves {recipe.Serves} +

+

+ {recipe.Description} +

+
+

+ {recipe.Category} - {recipe.Duration.Total} mins +

+ {recipe.Favorite && } +
+ +
+
+ ); +} diff --git a/web/src/components/cards/RecipeCardSmall.tsx b/web/src/components/cards/RecipeCardSmall.tsx new file mode 100644 index 0000000..b6cffa5 --- /dev/null +++ b/web/src/components/cards/RecipeCardSmall.tsx @@ -0,0 +1,39 @@ +import type { Recipe } from "../../types/recipe"; +import RecipePlaceholder from "../../assets/images/recipe_placeholder.png" +import LikeButton from "../buttons/LikeButton"; + +interface RecipeCardSmallProps { + recipe: Recipe; +} + +export default function RecipeCardSmall({ recipe }: RecipeCardSmallProps) { + + // HANDLERS + const makeButtonHandler = () => console.log("makeButtonHandler()"); + + return ( +
+ +
+

+ {recipe.Title} +

+

+ Serves {recipe.Serves} +

+
+

+ {recipe.Category} - {recipe.Duration.Total} mins +

+ {recipe.Favorite && } +
+ +
+
+ ); +} diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 1eed112..04f34b8 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -1,23 +1,223 @@ +import { Fragment, useEffect, useState } from "react"; import SalmonVideo from "../assets/videos/salmon_video.mp4"; +import Banner from "../components/Banner"; +import ROUTE_CONSTANTS from "../types/routes"; + +import RecipeLarge from "../components/cards/RecipeCardLarge"; +import type { Recipe } from "../types/recipe"; +import RecipeCardSmall from "../components/cards/RecipeCardSmall"; +import ContentCardSmall from "../components/cards/ContentCardSmall"; export default function Home() { + const [loggedIn, isLoggedIn] = useState(false); + const [recipeOfTheWeek, setRecipeOfTheWeek] = useState(null); + + const [madeRecipes, setMadeRecipes] = useState([]); + const [viewedRecipes, setViewedRecipes] = useState([]); + + + // BUG: Remove these + 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 + }; + + isLoggedIn(true); + setRecipeOfTheWeek(recipe); + + const recipes: Recipe[] = [recipe, recipe2]; + setMadeRecipes(recipes); + setViewedRecipes(recipes); + }, []); + return ( -
-
- -

+
+ +

- Discover Your Next Favorite Meal -

-
-

- Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, - we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, - all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or - browse our trending dishes for fresh ideas. -

-

+ Discover Your Next Favorite Meal + + +

+ Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, + we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, + all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or + browse our trending dishes for fresh ideas. +

+ + + {/* Search Section */} +
+ +
+ {/* TODO: Create this */} + {/* @components.SearchBar(filters, true, false, false) */} +
+
+
+ + {/* Highlight Section */} +
+ +

+ Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes + our community loves most. This isn't just about how many people view a recipe; it's also about + how many times it's been made, liked, reviewed, and its average rating, all combined to find + the true fan favorite of the week. It's our way of highlighting the best recipes that truly + resonate with our users! +

+
+ +
+
+ + {/* Lists Section */} +
+ +
+

Recently viewed

+ {loggedIn ? +
+ {viewedRecipes && viewedRecipes.length > 0 ? ( + <> + {viewedRecipes.map((recipe: Recipe) => ( + + ))} + + + ) : ( +

No recently viewed recipes

+ )} +
+ : + + } +

Make again

+ {loggedIn ? +
+ {madeRecipes && madeRecipes.length > 0 ? ( + <> + {madeRecipes.map((recipe: Recipe) => ( + + ))} + + + ) : ( +

No recently made recipes

+ )} + {/* } */} +
+ : + + } +
+
+ + {/* Call-to-Action Section */} + < section + className="w-full flex flex-col items-center justify-center mt-16 py-8 md:py-12 bg-gradient-to-br from-blue-100 to-purple-100 text-center" > +

+ Unleash Your Inner Chef! +

+

+ Have a unique recipe idea? Want to share your culinary masterpiece with the world? + It's time to bring your creations to life! +

+ + Create Your Recipe! + + + ); } diff --git a/web/src/types/recipe.ts b/web/src/types/recipe.ts new file mode 100644 index 0000000..ce5c660 --- /dev/null +++ b/web/src/types/recipe.ts @@ -0,0 +1,37 @@ + +export interface RecipeDuration { + Total: number; + Prep: number; + Cook: number; +} + +// BUG: This might need to be integers? Not sure yet. +export type RecipeMeal = "breakfast" | "lunch" | "dinner" | "dessert" | "snack" | "side" | "other"; + +export interface RecipeIngredient { + Name: string; + Quantity: string; +} + +export interface Tag { + Id: number; + Name: string; + Created: Date; +} + +export interface Recipe { + Id: number; + Title: string; + Description: string; + Instructions: string[]; + Serves: number; + Difficulty: number; + Duration: RecipeDuration; + Category: RecipeMeal; + Ingredients: RecipeIngredient[]; + UserId: number; + Modified: Date; + Created: Date; + Tags: Tag[]; + Favorite: boolean; +} diff --git a/web/src/types/routes.ts b/web/src/types/routes.ts index a6d3a47..955102b 100644 --- a/web/src/types/routes.ts +++ b/web/src/types/routes.ts @@ -6,13 +6,16 @@ const ROUTE_CONSTANTS: { Create: string; Profile: string; ShoppingList: string; + Login: string; + History: string; } = { Home: `${VERSION_FLAG}/web/home`, Favorites: `${VERSION_FLAG}/web/favorites`, Create: `${VERSION_FLAG}/web/create`, Profile: `${VERSION_FLAG}/web/profile`, ShoppingList: `${VERSION_FLAG}/web/list`, - + Login: `${VERSION_FLAG}/web/login`, + History: `${VERSION_FLAG}/web/history`, }; export default ROUTE_CONSTANTS;