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 0000000..c8664d9 Binary files /dev/null and b/web/src/assets/images/recipe_placeholder.png differ diff --git a/web/src/assets/images/recipe_placeholder_wide.jpg b/web/src/assets/images/recipe_placeholder_wide.jpg new file mode 100644 index 0000000..07d0520 Binary files /dev/null and b/web/src/assets/images/recipe_placeholder_wide.jpg differ diff --git a/web/src/components/Banner.tsx b/web/src/components/Banner.tsx new file mode 100644 index 0000000..f7f2fe6 --- /dev/null +++ b/web/src/components/Banner.tsx @@ -0,0 +1,12 @@ + +interface BannerProps { + content: string; +}; + +export default function Banner({ content }: BannerProps) { + return ( +

+ {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 ( +
+
+ +

{content}

+
+
+
+ ); +} 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;