Merging in the React Refactor #56
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() {
|
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 (
|
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