(FEAT): Profile page is complete, minus functionality
This commit is contained in:
parent
45a0d0e54c
commit
c9be9876e3
19
web/src/components/results/ActivityListItem.tsx
Normal file
19
web/src/components/results/ActivityListItem.tsx
Normal 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>
|
||||
</>;
|
||||
}
|
||||
56
web/src/components/results/RecipeListItem.tsx
Normal file
56
web/src/components/results/RecipeListItem.tsx
Normal 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>
|
||||
</>
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
11
web/src/types/engagement.ts
Normal file
11
web/src/types/engagement.ts
Normal 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
19
web/src/types/user.ts
Normal 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user