182 lines
7.0 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|