From 7bd29e4dbaec8134bdd5581e65bafe4c0c98b27e Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:13:01 -0600 Subject: [PATCH] working on profile --- .../src/components/derived/CollectionList.tsx | 38 ++- client/src/components/pages/Profile.tsx | 217 ++++++++++++------ client/src/hooks/useDateFormat.tsx | 10 + client/src/util/API.ts | 13 +- server/routes/collection.ts | 21 +- 5 files changed, 206 insertions(+), 93 deletions(-) create mode 100644 client/src/hooks/useDateFormat.tsx diff --git a/client/src/components/derived/CollectionList.tsx b/client/src/components/derived/CollectionList.tsx index 73daa9f..2cbd682 100644 --- a/client/src/components/derived/CollectionList.tsx +++ b/client/src/components/derived/CollectionList.tsx @@ -1,27 +1,47 @@ -import { useEffect } from "react"; +import { FC, useEffect, useState } from "react"; +import { v4 } from "uuid"; import { useAuthContext } from "../../context/AuthContext" +import useDateFormat from "../../hooks/useDateFormat"; +import { ICollection } from "../../schemas"; import API from "../../util/API"; import { Card } from "../ui" -function CollectionList() { +type CollectionListType = FC<{ targetID?: number | string }> + +const CollectionList: CollectionListType = ({ targetID = null }) => { + const [collections, setCollections] = useState(); + const [author, setAuthor] = useState(); const { user, token } = useAuthContext(); useEffect(() => { if (user && token) { - (async() => { - const Collections = new API.Collection(token); - const result = await Collections.getAllAuthored(); - console.log(result); - })(); + if (targetID) { + (async() => { + const Collections = new API.Collection(token); + const result = await Collections.getAllAuthored(targetID); + setCollections(result); + })(); + } else { + (async() => { + const Collections = new API.Collection(token); + const result = await Collections.getAllAuthored(); + setCollections(result); + })(); + } } }, [user]) return ( - + { collections && collections.map(each => +
+

{each.name}

+ { targetID &&

Created by {author}

} +

Created {useDateFormat(each.datecreated)}

+
+ )}
) - } export default CollectionList \ No newline at end of file diff --git a/client/src/components/pages/Profile.tsx b/client/src/components/pages/Profile.tsx index 33524a9..0e50f30 100644 --- a/client/src/components/pages/Profile.tsx +++ b/client/src/components/pages/Profile.tsx @@ -1,34 +1,71 @@ -import { useEffect, useState } from "react"; -import { IUser } from "../../schemas"; import { useNavigate } from "react-router-dom"; -import { useAuthContext } from "../../context/AuthContext"; -import { Button, Divider, Page, Panel } from "../ui"; -import Protect from "../../util/Protect"; -import Friends from "../derived/Friends"; -import API from "../../util/API"; +import { useEffect, useState } from "react"; import { AxiosError } from "axios"; +import { useAuthContext } from "../../context/AuthContext"; +import API from "../../util/API"; +import Protect from "../../util/Protect"; +import { ICollection, IUser } from "../../schemas"; +import Friends from "../derived/Friends"; import CollectionList from "../derived/CollectionList"; +import { Button, Divider, Page, Panel } from "../ui"; +import useDateFormat from "../../hooks/useDateFormat"; + +interface ProfileMetadata { + targetID?: string | number + targetUser?: IUser | undefined + formattedDate: string + collections: ICollection[] + friends: IUser[] + isSet: boolean +} export default function Profile() { - const [contents, setContents] = useState(<>); - const [targetUser, setTargetUser] = useState(); + // globals and router utils const { user, token } = useAuthContext(); const navigate = useNavigate(); - const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: "long" }); + // UI state info + const [contents, setContents] = useState(<>); + // master state for this page + const [metadata, setMetadata] = useState({ + targetID: undefined, + targetUser: undefined, + formattedDate: "", + collections: [], + friends: [], + isSet: false + }); + + const dateFormatter = new Intl.DateTimeFormat('en-US', { timeStyle: undefined, dateStyle: "long" }); + + // STEP 1: FETCH METADATA (requires token) useEffect(() => { - if (!token) return; + if (!token || !user) return; const params = new URLSearchParams(window.location.search); const targetID = params.get('id'); + // if a target is specified in the url if (targetID) { + setMetadata((prev: ProfileMetadata) => { + return { ...prev, targetID: targetID } + }); + + // fetch and store user data with associated user id (async() => { try { const User = new API.User(token); const result = await User.getByID(targetID); - if (result) setTargetUser(result); + if (result) { + setMetadata((prev) => { + return { + ...prev, + targetUser: result, + formattedDate: useDateFormat(result.datecreated) + } + }) + } } catch (error) { if (error instanceof AxiosError) { if (error?.response?.status == 404) { @@ -43,76 +80,120 @@ export default function Profile() { ) + + return; } } else { console.error(error); } } })(); + + // do the same for this user's collections and friends + (async() => { + const Collections = new API.Collection(token); + const result = await Collections.getAllAuthored(metadata.targetID); + if (result) { + setMetadata((prev: ProfileMetadata) => { + return { + ...prev, collections: result + } + }) + } + })(); } else { - const formattedDate = user!.datecreated ? dateFormatter.format(new Date(user!.datecreated)) : "(unknown)"; + // otherwise, this is the current user's profile and should load some slightly different info + (async() => { + const Collections = new API.Collection(token); + const result = await Collections.getAllAuthored(); + if (result) { + setMetadata((prev: ProfileMetadata) => { + return { + ...prev, collections: result + } + }) + } + })(); - setContents( - -
-

{user!.firstname}'s Profile

+ (async() => { + const Friends = new API.Friendship(token); + }) -
- -

About me:

-

{user!.firstname} {user!.lastname}

-

Recipin Member since: {formattedDate}

- -

30 recipes

-

2 collections

-
- - - {/* include number of collections */} -

My collections:

- -
- - -

My friends:

- -
-
-
-
- ) + setMetadata((prev) => { + return { + ...prev, + formattedDate: useDateFormat(user.datecreated) + } + }) } - }, [token]) + setMetadata((prev) => { + return { ...prev, isSet: true } + }) + }, [token]); + + // STEP 2: set up page UI based on profile config above useEffect(() => { - if (targetUser) { - const formattedDate = targetUser.datecreated ? dateFormatter.format(new Date(targetUser.datecreated)) : "(unknown)"; - - setContents( - -
-

{targetUser.firstname}'s Profile

- -
- -

About {targetUser.firstname} {targetUser.lastname}:

-

Recipin Member since: {formattedDate}

-
- - -

My collections:

-
- - -

My friends:

- -
+ if (metadata.isSet) { + if (metadata.targetUser) { + setContents( + +
+

{metadata.targetUser.firstname}'s Profile

+ +
+ +

About {metadata.targetUser.firstname} {metadata.targetUser.lastname}:

+

Recipin Member since: {metadata.formattedDate}

+
+ + +

My collections:

+ +
+ + +

My friends:

+ +
+
-
- - ) + + ) + } else { + setContents( + +
+

{user!.firstname}'s Profile

+ +
+ +

About me:

+

{user!.firstname} {user!.lastname}

+

Recipin Member since: {metadata.formattedDate}

+ +

30 recipes

+

2 collections

+
+ + + {/* include number of collections */} +

My collections ({ metadata.collections.length || 0 }):

+ +
+ + +

My friends:

+ +
+
+
+
+ ) + } } - }, [targetUser]); + }, [metadata]) + // STEP 3: mount the UI return contents } \ No newline at end of file diff --git a/client/src/hooks/useDateFormat.tsx b/client/src/hooks/useDateFormat.tsx new file mode 100644 index 0000000..208a095 --- /dev/null +++ b/client/src/hooks/useDateFormat.tsx @@ -0,0 +1,10 @@ +function useDateFormat(input: string | undefined) { + if (typeof input == 'undefined') return "unknown"; + + const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: "long" }); + const output = dateFormatter.format(new Date(input)); + + return output; +} + +export default useDateFormat \ No newline at end of file diff --git a/client/src/util/API.ts b/client/src/util/API.ts index 546390d..69b4ed2 100644 --- a/client/src/util/API.ts +++ b/client/src/util/API.ts @@ -1,4 +1,4 @@ -import { AxiosHeaders, AxiosRequestHeaders } from "axios"; +import { AxiosHeaders, AxiosRequestHeaders, AxiosResponse } from "axios"; import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList } from "../schemas"; import { default as _instance } from "./axiosInstance"; @@ -165,8 +165,15 @@ module API { super(Settings.getAPISTRING() + "/app/collection", token); } - async getAllAuthored() { - const response = await this.customRoute(CRUDMETHOD.GET, "?authored=true"); + async getAllAuthored(id?: number | string) { + let response: AxiosResponse; + + if (id) { + response = await this.customRoute(CRUDMETHOD.GET, `?authored=true&authorID=${id}`); + } else { + response = await this.customRoute(CRUDMETHOD.GET, "?authored=true"); + } + return Promise.resolve(response.data); } } diff --git a/server/routes/collection.ts b/server/routes/collection.ts index 45dfbf1..7adf6d1 100644 --- a/server/routes/collection.ts +++ b/server/routes/collection.ts @@ -2,6 +2,7 @@ import { Express, Router } from "express"; import { checkIsAdmin, restrictAccess } from "../auth/middlewares"; import CollectionCtl from "../controllers/CollectionCtl"; import { IUser } from "../schemas"; +import { StatusCode } from "../util/types"; const CollectionInstance = new CollectionCtl(); const router = Router(); @@ -19,30 +20,24 @@ export const collectionRoute = (app: Express) => { } }) - router.get('&authored=true', restrictAccess, async (req, res, next) => { - const user = req.user as IUser; - console.log(user.id); - try { - const { code, data } = await CollectionInstance.getAllAuthored(user.id as number); - res.status(code).send(data); - } catch (e) { - next(e); - } - }) - // implement is admin on this route router.get('/', restrictAccess, async (req, res, next) => { const user = req.user as IUser; - const { authored } = req.query; + const { authored, authorID } = req.query; try { - if (authored || authored == "true") { + if (authorID) { + const { code, data } = await CollectionInstance.getAllAuthored(parseInt(authorID as string)); + res.status(code).send(data); + } else if (authored || authored == "true") { const { code, data } = await CollectionInstance.getAllAuthored(user.id as number); res.status(code).send(data); } else { if (user.isadmin) { const { code, data } = await CollectionInstance.getAll(); res.status(code).send(data); + } else { + res.status(403).send("Unauthorized"); } } } catch(e) {