From 9945ebadb41d2e0c1f040b7dbffe09eca2de8951 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Mon, 13 Feb 2023 21:19:29 -0600 Subject: [PATCH] add recipe workflow, viewing for collections --- client/src/components/pages/AddRecipe.tsx | 44 +++++++--- client/src/components/pages/Collection.tsx | 81 +++++++++++++------ .../components/pages/CollectionBrowser.tsx | 46 ++++++++++- client/src/components/ui/Form.tsx | 7 +- client/src/components/ui/Grid.tsx | 0 client/src/sass/components/Form.scss | 32 ++++++++ client/src/sass/helpers/_placeholders.scss | 4 + client/src/util/API.ts | 7 +- server/controllers/CollectionCtl.ts | 8 ++ server/models/collection.ts | 17 ++++ server/routes/collection.ts | 15 +++- server/routes/recipe.ts | 8 +- server/routes/subscription.ts | 11 ++- 13 files changed, 224 insertions(+), 56 deletions(-) create mode 100644 client/src/components/ui/Grid.tsx create mode 100644 client/src/sass/components/Form.scss diff --git a/client/src/components/pages/AddRecipe.tsx b/client/src/components/pages/AddRecipe.tsx index 792e07e..f27526a 100644 --- a/client/src/components/pages/AddRecipe.tsx +++ b/client/src/components/pages/AddRecipe.tsx @@ -1,33 +1,53 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useRef, useEffect, useState, createRef } from "react"; import { useAuthContext } from "../../context/AuthContext"; +import { Button, Card, Divider, Form, Page, Panel } from "../ui" import { IRecipe } from "../../schemas"; -import { Button, Divider, Form, Page, Panel } from "../ui" +import API from "../../util/API"; const AddRecipe = () => { - const authContext = useAuthContext(); + const { user, token } = useAuthContext(); const [input, setInput] = useState({ name: '', preptime: '', description: '', authoruserid: '', ingredients: [] }) - const [form, setForm] = useState(); + const [toast, setToast] = useState(<>) const getFormState = useCallback((data: IRecipe) => { setInput(data); }, [input]) - const handleCreate = () => { + const handleCreate = async () => { + if (!token) return; + for (let field of Object.keys(input)) { - if (!input[field as keyof IRecipe]) return; + if (!input[field as keyof IRecipe]) { + console.log(field); + return; + } } console.log('good to go!') + + const recipe = new API.Recipe(token); + const result = await recipe.post(input); + + const recipeID = result.recipe.id; + const recipeName = result.recipe.name; + + setToast( + +

Created recipe {recipeName} successfully!

+

View your new recipe here!

+
+ ) } useEffect(() => { - authContext.user && setInput((prev: IRecipe) => { + if (!user) return; + user && setInput((prev: IRecipe) => { return { ...prev, - authoruserid: authContext.user!.id! + authoruserid: user.id! } }) - }, [authContext]) + }, [user]) useEffect(() => { console.log(input); @@ -38,7 +58,7 @@ const AddRecipe = () => {

Add a New Recipe

- +
{ richTextInitialValue: "

Enter recipe details here!

" }} /> - { form ||

Loading...

} - + +
{ toast }
) diff --git a/client/src/components/pages/Collection.tsx b/client/src/components/pages/Collection.tsx index ffcc498..a4ba9b4 100644 --- a/client/src/components/pages/Collection.tsx +++ b/client/src/components/pages/Collection.tsx @@ -1,38 +1,69 @@ import { useAuthContext } from "../../context/AuthContext"; import Protect from "../../util/Protect"; import { useParams } from "react-router-dom"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import API from "../../util/API"; +import { ICollection, IRecipe, IUser } from "../../schemas"; +import { v4 } from "uuid"; +import { Panel } from "../ui"; const Collection = () => { - const [isDefault, setIsDefault] = useState(true); - const { user } = useAuthContext(); + const [data, setData] = useState(); + const [owner, setOwner] = useState(); + const [recipes, setRecipes] = useState(); + const [content, setContent] = useState(<>); + const { user, token } = useAuthContext(); const { id } = useParams(); - if (id) { - setIsDefault(false); - } + useEffect(() => { + if (!id) return; + + token && (async() => { + const collections = new API.Collection(token); + const users = new API.User(token); + + const result: ICollection = await collections.getByID(id); + setData(result); + + const allRecipes = await collections.getRecipesFromOne(id); + setRecipes(allRecipes); + + const actualUser = await users.getByID(result.ownerid as string); + setOwner(actualUser); + })(); + }, [token]) + + useEffect(() => { + if (user && data && recipes) { + setContent( + <> +
+

COLLECTION: {data.name}

+ {

Collection by: {owner ? `${owner.firstname} ${owner.lastname}` : `${user.firstname} ${user.lastname}`}

} +

{recipes.length || 0} recipe{recipes.length != 1 && "s" }

+ { data.ismaincollection && ( + owner ?

(This is {owner.firstname}'s main collection)

:

(This is your main collection)

+ )} + + + { + recipes && recipes.map((each: IRecipe) => +
+

{each.name}

+ { each.description &&
} +
+ ) + } +
+
+ + ) + } + }, [data, recipes]) return ( - { isDefault ? - - <> -

Mikayla's collection

-

37 recipes

-

71 ingredients

-

11 types of cuisine

- - - : - - <> - - - - } - - - {/* recipes */} + { content }
) } diff --git a/client/src/components/pages/CollectionBrowser.tsx b/client/src/components/pages/CollectionBrowser.tsx index d9b6644..ac8d70a 100644 --- a/client/src/components/pages/CollectionBrowser.tsx +++ b/client/src/components/pages/CollectionBrowser.tsx @@ -1,9 +1,53 @@ -import { Page } from "../ui"; +import { useEffect, useState } from "react"; +import { v4 } from "uuid"; +import { useAuthContext } from "../../context/AuthContext"; +import { ICollection, IRecipe } from "../../schemas"; +import API from "../../util/API"; +import { Card, Page } from "../ui"; + +interface CollectionDetails { + idx: number + collection: ICollection + recipes: IRecipe[] +} const CollectionBrowser = () => { + const [list, setList] = useState(); + const { token } = useAuthContext(); + + useEffect(() => { + if (!token) return; + (async() => { + const collections = new API.Collection(token); + const recipes = new API.Recipe(token); + + const allRecipes = await collections.getAllAuthored(); + if (allRecipes) { + const result = new Array(); + + let i = 0; + for (let each of allRecipes) { + } + + setList(allRecipes); + } + })(); + }, [token]) + return (

Browsing your {2} collections:

+ + { + list && list.map(each => { + return ( + +

{each.name}

+ Link to details +
+ ) + }) + }
) } diff --git a/client/src/components/ui/Form.tsx b/client/src/components/ui/Form.tsx index 7704977..34637ab 100644 --- a/client/src/components/ui/Form.tsx +++ b/client/src/components/ui/Form.tsx @@ -1,6 +1,7 @@ import { ChangeEvent, FC, useEffect, useState } from "react" import { v4 } from "uuid" import RichText from "./RichText" +import "/src/sass/components/Form.scss"; export interface FormConfig { parent: string @@ -72,14 +73,14 @@ const Form: FC = ({ parent, _config }) => { if (config.dataTypes![i] == 'TINYMCE') { return ( -
+
updateRichText(txt, i)} />
) } else { return ( -
+
= ({ parent, _config }) => { }, [config]); return ( -
+
{ contents }
) diff --git a/client/src/components/ui/Grid.tsx b/client/src/components/ui/Grid.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/sass/components/Form.scss b/client/src/sass/components/Form.scss new file mode 100644 index 0000000..f14eeef --- /dev/null +++ b/client/src/sass/components/Form.scss @@ -0,0 +1,32 @@ +.ui-form-component { + display: flex; + flex-direction: column; + align-self: center; + width: 75%; + + .form-row { + display: inline-flex; + justify-content: flex-start; + margin-bottom: 6px; + + label { + text-align: left; + width: 25%; + } + input { + border-radius: 4px; + width: 65%; + } + } + + .form-row-editor { + display: inline-flex; + flex-direction: column; + text-align: left; + justify-content: flex-start; + + label { + padding-bottom: 8px; + } + } +} \ No newline at end of file diff --git a/client/src/sass/helpers/_placeholders.scss b/client/src/sass/helpers/_placeholders.scss index 75e42b8..f2605c7 100644 --- a/client/src/sass/helpers/_placeholders.scss +++ b/client/src/sass/helpers/_placeholders.scss @@ -24,4 +24,8 @@ flex-flow: row wrap; justify-content: center; } + + &.width-80 { + width: 80vw; + } } \ No newline at end of file diff --git a/client/src/util/API.ts b/client/src/util/API.ts index 1b5da16..f46ce05 100644 --- a/client/src/util/API.ts +++ b/client/src/util/API.ts @@ -167,7 +167,7 @@ module API { export class Recipe extends RestController { constructor(token: string) { - super(Settings.getAPISTRING() + "/app/recipes", token); + super(Settings.getAPISTRING() + "/app/recipe", token); } } @@ -183,6 +183,11 @@ module API { super(Settings.getAPISTRING() + "/app/collection", token); } + async getRecipesFromOne(id?: number | string) { + const response = await this.instance.get(this.endpoint + `/${id}?getRecipes=true`, this.headers); + return Promise.resolve(response.data); + } + async getAllAuthored(id?: number | string) { let response: AxiosResponse; diff --git a/server/controllers/CollectionCtl.ts b/server/controllers/CollectionCtl.ts index 1e87b31..b4c9adf 100644 --- a/server/controllers/CollectionCtl.ts +++ b/server/controllers/CollectionCtl.ts @@ -15,6 +15,14 @@ export default class CollectionCtl { return new ControllerResponse(code, data); } + async getRecipesFromOne(id: number | string) { + const result = await CollectionInstance.getRecipesFromOne(id); + const ok: boolean = result !== null; + const code: StatusCode = ok ? StatusCode.OK : StatusCode.NotFound; + const data: string | ICollection[] = result || "No collection found with this ID"; + return new ControllerResponse(code, data); + } + async getAll() { const result = await CollectionInstance.getAll(); const ok = result !== null; diff --git a/server/models/collection.ts b/server/models/collection.ts index 3974f76..3b23744 100644 --- a/server/models/collection.ts +++ b/server/models/collection.ts @@ -18,6 +18,23 @@ export class Collection { } } + async getRecipesFromOne(id: number | string) { + try { + const statement = ` + SELECT * FROM recipin.recipe + INNER JOIN recipin.cmp_recipecollection + ON recipe.id = cmp_recipecollection.recipeid + WHERE cmp_recipecollection.collectionid = $1; + `; + const values = [id]; + const result = await pool.query(statement, values); + if (result.rows.length) return result.rows; + return null; + } catch (e: any) { + throw new Error(e); + } + } + async getAllAuthored(id: number | string) { console.log(id, typeof id); try { diff --git a/server/routes/collection.ts b/server/routes/collection.ts index 7adf6d1..743a091 100644 --- a/server/routes/collection.ts +++ b/server/routes/collection.ts @@ -10,11 +10,18 @@ const router = Router(); export const collectionRoute = (app: Express) => { app.use('/app/collection', router); - router.get('/:id', async (req, res, next) => { + router.get('/:id', restrictAccess, async (req, res, next) => { const { id } = req.params; + const { getRecipes } = req.query; + try { - const { code, data } = await CollectionInstance.getOne(id); - res.status(code).send(data); + if (getRecipes || getRecipes == "true") { + const { code, data } = await CollectionInstance.getRecipesFromOne(id); + res.status(code).send(data); + } else { + const { code, data } = await CollectionInstance.getOne(id); + res.status(code).send(data); + } } catch(e) { next(e); } @@ -45,7 +52,7 @@ export const collectionRoute = (app: Express) => { } }) - router.post('/', async (req, res, next) => { + router.post('/', restrictAccess, async (req, res, next) => { const data = req.body; try { diff --git a/server/routes/recipe.ts b/server/routes/recipe.ts index add3ea5..13a4d1f 100644 --- a/server/routes/recipe.ts +++ b/server/routes/recipe.ts @@ -22,12 +22,12 @@ export const recipeRoute = (app: Express) => { }) router.get('/', restrictAccess, async (req, res, next) => { - const user = req.session.user as IUser; - const { filterby } = req.query; + const user = req.user as IUser; + const { filter } = req.query; try { let result: CtlResponse; - switch (filterby) { + switch (filter) { case "myrecipes": result = await recipectl.getAllAuthored(user.id as number); break; @@ -55,7 +55,7 @@ export const recipeRoute = (app: Express) => { }) router.post('/', restrictAccess, async (req, res, next) => { - const user = req.session.user as IUser; + const user = req.user as IUser; const data = req.body; try { diff --git a/server/routes/subscription.ts b/server/routes/subscription.ts index d887154..d36b624 100644 --- a/server/routes/subscription.ts +++ b/server/routes/subscription.ts @@ -1,6 +1,7 @@ import { Express, Router } from "express" import { restrictAccess } from "../auth/middlewares"; import { CollectionCtl } from "../controllers"; +import { IUser } from "../schemas"; const CollectionInstance = new CollectionCtl(); const router = Router(); @@ -8,12 +9,11 @@ export const subscriptionRoute = (app: Express) => { app.use('/app/subscription', router); router.get('/', async (req, res, next) => { - // @ts-ignore - const { user } = req.session.user; + const user = req.user as IUser; if (!user) return; try { - const result = await CollectionInstance.getSubscriptions(user.id as string); + const result = await CollectionInstance.getSubscriptions(user.id!); res.status(200).send(result); } catch(e) { next(e); @@ -21,12 +21,11 @@ export const subscriptionRoute = (app: Express) => { }) router.post('/', restrictAccess, async (req, res, next) => { - // @ts-ignore - const { user } = req.session.user; + const user = req.user as IUser; const { collection } = req.query; try { - const result = await CollectionInstance.postSubscription(collection as string, user.id as string); + const result = await CollectionInstance.postSubscription(collection as string, user.id!); res.status(201).send(result); } catch(e) { next(e);