(FEAT): Profile page is complete, minus functionality

This commit is contained in:
Hayden Hargreaves 2025-11-13 19:57:10 -07:00
parent 45a0d0e54c
commit c9be9876e3
5 changed files with 303 additions and 1 deletions

View File

@ -0,0 +1,19 @@
import type { Engagement } from "../../types/engagement";
interface ActivityListItemProps {
engagement: Engagement;
}
export default function ActivityListItem({ engagement }: ActivityListItemProps) {
return <>
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 duration-150 flex justify-between items-center">
<p className="text-sm md:text-base text-gray-800">
{engagement.Message}
</p>
<p className="text-xs md:text-sm text-gray-600 w-fit shrink-0">
{engagement.Created.toLocaleDateString()}
</p>
</li>
</>;
}

View File

@ -0,0 +1,56 @@
import type { Recipe, Tag } from "../../types/recipe"
interface RecipeListItemProps {
recipe: Recipe;
};
function displayDifficulty(diff: number): string {
switch (diff) {
case 1:
return "Beginner"
case 2:
return "Easy"
case 3:
return "Intermediate"
case 4:
return "Challenging"
case 5:
return "Extreme"
default:
return ""
}
}
function displayTags(tags: Tag[]): string {
return tags.map(tag => tag.Name).join(", ");
}
export default function RecipeListItem({ recipe }: RecipeListItemProps) {
// TODO: Click event
return <>
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 duration-150">
<p className="text-base md:text-lg hover:text-blue-600 duration-100 cursor-pointer">
{recipe.Title}
</p>
<p className="hidden md:block text-sm text-gray-700 my-1.5">
Difficulty: <span className="font-semibold">{displayDifficulty(recipe.Difficulty)}</span>
{" "} | Duration: <span className="font-semibold">{recipe.Duration.Total} min</span>
{" "} | Category: <span className="font-semibold">{recipe.Category}</span>
</p>
<p className="md:hidden text-xs md:text-sm text-gray-700 my-1">
Difficulty: <span className="font-semibold">{displayDifficulty(recipe.Difficulty)}</span>
</p>
<p className="md:hidden text-xs md:text-sm text-gray-700 my-1">
Duration: <span className="font-semibold">{recipe.Duration.Total} min</span>
</p>
<p className="md:hidden text-xs md:text-sm text-gray-700 my-1">
Category: <span className="font-semibold">{recipe.Category}</span>
</p>
{recipe.Tags && (
<p className="text-xs italic text-gray-500">
Tags: {displayTags(recipe.Tags)}
</p>
)}
</li>
</>
}

View File

@ -1,7 +1,204 @@
import { useEffect, useState } from "react";
import type { User } from "../types/user";
import type { Recipe } from "../types/recipe";
import RecipeListItem from "../components/results/RecipeListItem";
import type { Engagement } from "../types/engagement";
import ActivityListItem from "../components/results/ActivityListItem";
export default function Profile() {
const [user, setUser] = useState<User | null>(null);
const [recipes, setRecipes] = useState<Recipe[]>([]);
const [favorites, setFavorites] = useState<Recipe[]>([]);
const [activity, setActivity] = useState<Engagement[]>([]);
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
};
const eng: Engagement = {
Id: 1,
Type: "made",
Message: "Created some shit",
Entity: 1,
UserId: 1,
Created: new Date(),
};
const user: User = {
Id: 1,
GoogleId: "a",
Name: "Hayden Hargreaves",
Email: "hhargreaves2006@gmail.com",
ImageUrl: "https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c",
GoogleRefreshToken: "a",
Created: new Date(),
};
setUser(user);
setRecipes([recipe, recipe2, recipe, recipe, recipe, recipe, recipe]);
setFavorites([recipe, recipe2]);
setActivity([eng]);
}, []);
return (
<>
<p>Profile</p>
{/* User Details Section */}
<section className="w-full flex flex-col justify-center my-8 py-4 border-b border-gray-300">
<div className="w-full p-4 md:p-8 flex items-center gap-x-8">
{user?.ImageUrl != "" ? (
<img
className="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none"
src={user?.ImageUrl ?? ""}
/>
) : (
<img
className="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none"
src={`https://ui-avatars.com/api/?name=${user?.Name.split(" ")[0]}+${user?.Name.split(" ")[1]}&size=150`}
/>
)}
<div className="flex flex-col gap-y-4">
<div className="">
<h1 className="text-md md:text-2xl font-semibold">{user?.Name}</h1>
<p className="text-xs md:text-sm">{user?.Email}</p>
</div>
<div className="flex gap-x-4">
<p className="text-xs md:text-sm"><span className="font-bold">{recipes.length}</span> recipes</p>
<p className="text-xs md:text-sm"><span className="font-bold">{favorites.length}</span> favorites</p>
</div>
</div>
</div>
</section>
{/* Recipe Section */}
<section className="p-8">
<h2 className="text-2xl font-semibold text-gray-800">My Recipes</h2>
<ul className="w-full my-2">
{recipes.length <= 4 ? (
recipes.map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
) : (
recipes.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
)}
<a href="">
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
See all...
</li>
</a>
</ul>
</section>
{/* Favorites Section */}
<section className="p-8">
<h2 className="text-2xl font-semibold text-gray-800">My Favorites</h2>
<ul className="w-full my-2">
{favorites.length <= 4 ? (
favorites.map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
) : (
favorites.slice(0, 4).map(recipe => <RecipeListItem key={recipe.Id} recipe={recipe} />)
)}
<a href="">
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
See all...
</li>
</a>
</ul>
</section>
{/* Activity Section */}
<section className="p-8">
<h2 className="text-2xl font-semibold text-gray-800">Recent Activity</h2>
<ul className="w-full my-2">
{activity?.map(act => <ActivityListItem key={act.Id} engagement={act} />)}
<a href="">
<li className="w-full border-b border-gray-300 px-2 py-4 even:bg-gray-50 hover:bg-gray-100 hover:text-blue-600 duration-150 text-center">
See all...
</li>
</a>
</ul>
</section>
{/* Logout Section TODO: Click event*/}
<section className="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300 mt-auto">
<a href="" className="text-center border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300">
Logout
</a>
</section>
</>
);
}

View File

@ -0,0 +1,11 @@
export type EngagementType = "made" | "liked" | "viewed" | "shared" | "reviewed" | "rated";
export interface Engagement {
Id: number;
Type: EngagementType;
Message: string;
Entity: number;
UserId: number;
Created: Date;
};

19
web/src/types/user.ts Normal file
View File

@ -0,0 +1,19 @@
export interface GoogleUserInfo {
Id: string;
Email: string;
Verified: boolean;
Name: string;
GivenName: string;
FamilyName: string;
Picture: string;
}
export interface User {
Id: number;
GoogleId: string;
Name: string;
Email: string;
ImageUrl: string;
GoogleRefreshToken: string;
Created: Date;
}