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 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<ICollection[]>();
|
||||
const [author, setAuthor] = useState<string>();
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default CollectionList
|
||||
@@ -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<JSX.Element>(<></>);
|
||||
const [targetUser, setTargetUser] = useState<IUser>();
|
||||
// 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<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(() => {
|
||||
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() {
|
||||
</Panel>
|
||||
</Page>
|
||||
)
|
||||
|
||||
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(
|
||||
<Protect redirect="profile">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{user!.firstname}'s Profile</h1>
|
||||
(async() => {
|
||||
const Friends = new API.Friendship(token);
|
||||
})
|
||||
|
||||
<div className="profile-grid">
|
||||
<Panel>
|
||||
<h2>About me:</h2>
|
||||
<p>{user!.firstname} {user!.lastname}</p>
|
||||
<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>
|
||||
)
|
||||
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(
|
||||
<Protect redirect="/">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{targetUser.firstname}'s Profile</h1>
|
||||
|
||||
<div className="profile-grid">
|
||||
<Panel>
|
||||
<h2>About {targetUser.firstname} {targetUser.lastname}:</h2>
|
||||
<p>Recipin Member since: {formattedDate}</p>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My collections:</h2>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My friends:</h2>
|
||||
<Friends />
|
||||
</Panel>
|
||||
if (metadata.isSet) {
|
||||
if (metadata.targetUser) {
|
||||
setContents(
|
||||
<Protect redirect="/">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{metadata.targetUser.firstname}'s Profile</h1>
|
||||
|
||||
<div className="profile-grid">
|
||||
<Panel>
|
||||
<h2>About {metadata.targetUser.firstname} {metadata.targetUser.lastname}:</h2>
|
||||
<p>Recipin Member since: {metadata.formattedDate}</p>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My collections:</h2>
|
||||
<CollectionList targetID={metadata.targetUser.id} />
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My friends:</h2>
|
||||
<Friends />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Protect>
|
||||
)
|
||||
</Protect>
|
||||
)
|
||||
} else {
|
||||
setContents(
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [targetUser]);
|
||||
}, [metadata])
|
||||
|
||||
// STEP 3: mount the UI
|
||||
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 { 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user