working on profile
This commit is contained in:
@@ -1,27 +1,47 @@
|
|||||||
import { useEffect } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { v4 } from "uuid";
|
||||||
import { useAuthContext } from "../../context/AuthContext"
|
import { useAuthContext } from "../../context/AuthContext"
|
||||||
|
import useDateFormat from "../../hooks/useDateFormat";
|
||||||
|
import { ICollection } from "../../schemas";
|
||||||
import API from "../../util/API";
|
import API from "../../util/API";
|
||||||
import { Card } from "../ui"
|
import { Card } from "../ui"
|
||||||
|
|
||||||
function CollectionList() {
|
type CollectionListType = FC<{ targetID?: number | string }>
|
||||||
|
|
||||||
|
const CollectionList: CollectionListType = ({ targetID = null }) => {
|
||||||
|
const [collections, setCollections] = useState<ICollection[]>();
|
||||||
|
const [author, setAuthor] = useState<string>();
|
||||||
const { user, token } = useAuthContext();
|
const { user, token } = useAuthContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && token) {
|
if (user && token) {
|
||||||
(async() => {
|
if (targetID) {
|
||||||
const Collections = new API.Collection(token);
|
(async() => {
|
||||||
const result = await Collections.getAllAuthored();
|
const Collections = new API.Collection(token);
|
||||||
console.log(result);
|
const result = await Collections.getAllAuthored(targetID);
|
||||||
})();
|
setCollections(result);
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
(async() => {
|
||||||
|
const Collections = new API.Collection(token);
|
||||||
|
const result = await Collections.getAllAuthored();
|
||||||
|
setCollections(result);
|
||||||
|
})();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
{ collections && collections.map(each =>
|
||||||
|
<div className="collection-item" key={v4()}>
|
||||||
|
<h2>{each.name}</h2>
|
||||||
|
{ targetID && <p>Created by {author}</p>}
|
||||||
|
<p>Created {useDateFormat(each.datecreated)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CollectionList
|
export default CollectionList
|
||||||
@@ -1,34 +1,71 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { IUser } from "../../schemas";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAuthContext } from "../../context/AuthContext";
|
import { useEffect, useState } from "react";
|
||||||
import { Button, Divider, Page, Panel } from "../ui";
|
|
||||||
import Protect from "../../util/Protect";
|
|
||||||
import Friends from "../derived/Friends";
|
|
||||||
import API from "../../util/API";
|
|
||||||
import { AxiosError } from "axios";
|
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 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() {
|
export default function Profile() {
|
||||||
const [contents, setContents] = useState<JSX.Element>(<></>);
|
// globals and router utils
|
||||||
const [targetUser, setTargetUser] = useState<IUser>();
|
|
||||||
const { user, token } = useAuthContext();
|
const { user, token } = useAuthContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: "long" });
|
// UI state info
|
||||||
|
const [contents, setContents] = useState<JSX.Element>(<></>);
|
||||||
|
|
||||||
|
// master state for this page
|
||||||
|
const [metadata, setMetadata] = useState<ProfileMetadata>({
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (!token) return;
|
if (!token || !user) return;
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const targetID = params.get('id');
|
const targetID = params.get('id');
|
||||||
|
|
||||||
|
// if a target is specified in the url
|
||||||
if (targetID) {
|
if (targetID) {
|
||||||
|
setMetadata((prev: ProfileMetadata) => {
|
||||||
|
return { ...prev, targetID: targetID }
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetch and store user data with associated user id
|
||||||
(async() => {
|
(async() => {
|
||||||
try {
|
try {
|
||||||
const User = new API.User(token);
|
const User = new API.User(token);
|
||||||
const result = await User.getByID(targetID);
|
const result = await User.getByID(targetID);
|
||||||
if (result) setTargetUser(result);
|
if (result) {
|
||||||
|
setMetadata((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
targetUser: result,
|
||||||
|
formattedDate: useDateFormat(result.datecreated)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
if (error?.response?.status == 404) {
|
if (error?.response?.status == 404) {
|
||||||
@@ -43,76 +80,120 @@ export default function Profile() {
|
|||||||
</Panel>
|
</Panel>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
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 {
|
} 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(
|
(async() => {
|
||||||
<Protect redirect="profile">
|
const Friends = new API.Friendship(token);
|
||||||
<div className="profile-authenticated">
|
})
|
||||||
<h1>{user!.firstname}'s Profile</h1>
|
|
||||||
|
|
||||||
<div className="profile-grid">
|
setMetadata((prev) => {
|
||||||
<Panel>
|
return {
|
||||||
<h2>About me:</h2>
|
...prev,
|
||||||
<p>{user!.firstname} {user!.lastname}</p>
|
formattedDate: useDateFormat(user.datecreated)
|
||||||
<p>Recipin Member since: {formattedDate}</p>
|
}
|
||||||
<Divider />
|
})
|
||||||
<p>30 recipes</p>
|
|
||||||
<p>2 collections</p>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<Panel>
|
|
||||||
{/* include number of collections */}
|
|
||||||
<h2>My collections:</h2>
|
|
||||||
<CollectionList />
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<Panel>
|
|
||||||
<h2>My friends:</h2>
|
|
||||||
<Friends />
|
|
||||||
</Panel>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Protect>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [token])
|
|
||||||
|
|
||||||
|
setMetadata((prev) => {
|
||||||
|
return { ...prev, isSet: true }
|
||||||
|
})
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
// STEP 2: set up page UI based on profile config above
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (targetUser) {
|
if (metadata.isSet) {
|
||||||
const formattedDate = targetUser.datecreated ? dateFormatter.format(new Date(targetUser.datecreated)) : "(unknown)";
|
if (metadata.targetUser) {
|
||||||
|
setContents(
|
||||||
|
<Protect redirect="/">
|
||||||
|
<div className="profile-authenticated">
|
||||||
|
<h1>{metadata.targetUser.firstname}'s Profile</h1>
|
||||||
|
|
||||||
setContents(
|
<div className="profile-grid">
|
||||||
<Protect redirect="/">
|
<Panel>
|
||||||
<div className="profile-authenticated">
|
<h2>About {metadata.targetUser.firstname} {metadata.targetUser.lastname}:</h2>
|
||||||
<h1>{targetUser.firstname}'s Profile</h1>
|
<p>Recipin Member since: {metadata.formattedDate}</p>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
<div className="profile-grid">
|
<Panel>
|
||||||
<Panel>
|
<h2>My collections:</h2>
|
||||||
<h2>About {targetUser.firstname} {targetUser.lastname}:</h2>
|
<CollectionList targetID={metadata.targetUser.id} />
|
||||||
<p>Recipin Member since: {formattedDate}</p>
|
</Panel>
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<Panel>
|
<Panel>
|
||||||
<h2>My collections:</h2>
|
<h2>My friends:</h2>
|
||||||
</Panel>
|
<Friends />
|
||||||
|
</Panel>
|
||||||
<Panel>
|
</div>
|
||||||
<h2>My friends:</h2>
|
|
||||||
<Friends />
|
|
||||||
</Panel>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Protect>
|
||||||
</Protect>
|
)
|
||||||
)
|
} else {
|
||||||
}
|
setContents(
|
||||||
}, [targetUser]);
|
<Protect redirect="profile">
|
||||||
|
<div className="profile-authenticated">
|
||||||
|
<h1>{user!.firstname}'s Profile</h1>
|
||||||
|
|
||||||
|
<div className="profile-grid">
|
||||||
|
<Panel>
|
||||||
|
<h2>About me:</h2>
|
||||||
|
<p>{user!.firstname} {user!.lastname}</p>
|
||||||
|
<p>Recipin Member since: {metadata.formattedDate}</p>
|
||||||
|
<Divider />
|
||||||
|
<p>30 recipes</p>
|
||||||
|
<p>2 collections</p>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
{/* include number of collections */}
|
||||||
|
<h2>My collections ({ metadata.collections.length || 0 }):</h2>
|
||||||
|
<CollectionList />
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<h2>My friends:</h2>
|
||||||
|
<Friends />
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Protect>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [metadata])
|
||||||
|
|
||||||
|
// STEP 3: mount the UI
|
||||||
return contents
|
return contents
|
||||||
}
|
}
|
||||||
10
client/src/hooks/useDateFormat.tsx
Normal file
10
client/src/hooks/useDateFormat.tsx
Normal file
@@ -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
|
||||||
@@ -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 { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList } from "../schemas";
|
||||||
import { default as _instance } from "./axiosInstance";
|
import { default as _instance } from "./axiosInstance";
|
||||||
|
|
||||||
@@ -165,8 +165,15 @@ module API {
|
|||||||
super(Settings.getAPISTRING() + "/app/collection", token);
|
super(Settings.getAPISTRING() + "/app/collection", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAuthored() {
|
async getAllAuthored(id?: number | string) {
|
||||||
const response = await this.customRoute(CRUDMETHOD.GET, "?authored=true");
|
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);
|
return Promise.resolve(response.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Express, Router } from "express";
|
|||||||
import { checkIsAdmin, restrictAccess } from "../auth/middlewares";
|
import { checkIsAdmin, restrictAccess } from "../auth/middlewares";
|
||||||
import CollectionCtl from "../controllers/CollectionCtl";
|
import CollectionCtl from "../controllers/CollectionCtl";
|
||||||
import { IUser } from "../schemas";
|
import { IUser } from "../schemas";
|
||||||
|
import { StatusCode } from "../util/types";
|
||||||
const CollectionInstance = new CollectionCtl();
|
const CollectionInstance = new CollectionCtl();
|
||||||
|
|
||||||
const router = Router();
|
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
|
// implement is admin on this route
|
||||||
router.get('/', restrictAccess, async (req, res, next) => {
|
router.get('/', restrictAccess, async (req, res, next) => {
|
||||||
const user = req.user as IUser;
|
const user = req.user as IUser;
|
||||||
const { authored } = req.query;
|
const { authored, authorID } = req.query;
|
||||||
|
|
||||||
try {
|
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);
|
const { code, data } = await CollectionInstance.getAllAuthored(user.id as number);
|
||||||
res.status(code).send(data);
|
res.status(code).send(data);
|
||||||
} else {
|
} else {
|
||||||
if (user.isadmin) {
|
if (user.isadmin) {
|
||||||
const { code, data } = await CollectionInstance.getAll();
|
const { code, data } = await CollectionInstance.getAll();
|
||||||
res.status(code).send(data);
|
res.status(code).send(data);
|
||||||
|
} else {
|
||||||
|
res.status(403).send("Unauthorized");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user