Potion/web/src/pages/Profile.tsx
Hayden Hargreaves 3905557511 (FIX): Fixed google image error.
The API is rate limited, so by defaulting to the letter images works
fine for now.
2025-11-18 21:36:22 -07:00

182 lines
7.0 KiB
TypeScript

import { use, 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";
import { AuthContext } from "../context/AuthContext";
import { GetAuthenticatedUser, GetAuthenticatedUserEngagement, GetAuthenticatedUserFavorites, GetAuthenticatedUserRecipes } from "../services/UserService";
import { isApiError, type ApiError } from "../types/api/error";
import { Logout } from "../services/AuthService";
import { useNavigate } from "react-router-dom";
// TODO: Need to cache the image, it returns nothing because it fails with 429 (rate limits)
// Though this might just be a development issue, it seems to work in production.
export default function Profile() {
// Context
const { getJwt } = use(AuthContext);
const navigate = useNavigate();
// Page state
const [error, setError] = useState<string>("");
const [user, setUser] = useState<User | null>(null);
const [dummyProfileSrc, setDummyProfileSrc] = useState<string>("");
const [recipes, setRecipes] = useState<Recipe[]>([]);
const [favorites, setFavorites] = useState<Recipe[]>([]);
const [activity, setActivity] = useState<Engagement[]>([]);
const [jwt, setJwt] = useState<string>("");
// Log the user out and direct to the home page
const logoutHandler = (): void => {
void Logout();
void navigate("/v2/web/home");
}
const seeAllRecipesHandler = (): void => void navigate("/v2/web/404");
const seeAllFavoritesHandler = (): void => void navigate("/v2/web/favorites");
const seeAllEngagementHandler = (): void => void navigate("/v2/web/404");
const fetchProfileData = async (): Promise<void> => {
const result_user: User | ApiError = await GetAuthenticatedUser();
if (isApiError(result_user)) {
setError(result_user.message);
} else {
setUser(result_user);
}
const result_recipes: Recipe[] | ApiError = await GetAuthenticatedUserRecipes();
if (isApiError(result_recipes)) {
setError(result_recipes.message);
} else {
setRecipes(result_recipes);
}
const result_favorites: Recipe[] | ApiError = await GetAuthenticatedUserFavorites();
if (isApiError(result_favorites)) {
setError(result_favorites.message);
} else {
setFavorites(result_favorites);
}
const result_engagement: Engagement[] | ApiError = await GetAuthenticatedUserEngagement();
if (isApiError(result_engagement)) {
setError(result_engagement.message);
} else {
setActivity(result_engagement);
}
}
// Get the JWT from the cookies
useEffect(() => {
setJwt(getJwt());
}, [getJwt]);
// Get the user when the JWTS change
useEffect(() => {
// No jwt, we can't get user data
if (jwt)
void fetchProfileData();
}, [jwt]);
useEffect(() => {
if (error)
console.log("@error", error);
}, [error]);
useEffect(() => {
if (user)
setDummyProfileSrc(`https://ui-avatars.com/api/?name=${user?.Name.split(" ")[0]}+${user?.Name.split(" ")[1]}&size=150`);
}, [user]);
return (
<>
{/* 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 ?? undefined}
onError={e => {
e.currentTarget.onerror = null; // prevent infinite loop
e.currentTarget.src = dummyProfileSrc;
}}
/>
) : (
<img
className="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none"
src={dummyProfileSrc}
/>
)}
<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} />)
)}
<button onClick={seeAllRecipesHandler} className="w-full">
<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>
</button>
</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} />)
)}
<button onClick={seeAllFavoritesHandler} className="w-full">
<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>
</button>
</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} />)}
<button onClick={seeAllEngagementHandler} className="w-full">
<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>
</button>
</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">
<button onClick={logoutHandler} 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
</button>
</section>
</>
);
}