add recipe workflow, viewing for collections

This commit is contained in:
Mikayla Dobson
2023-02-13 21:19:29 -06:00
parent fc1046bad5
commit 9945ebadb4
13 changed files with 224 additions and 56 deletions

View File

@@ -1,33 +1,53 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useRef, useEffect, useState, createRef } from "react";
import { useAuthContext } from "../../context/AuthContext"; import { useAuthContext } from "../../context/AuthContext";
import { Button, Card, Divider, Form, Page, Panel } from "../ui"
import { IRecipe } from "../../schemas"; import { IRecipe } from "../../schemas";
import { Button, Divider, Form, Page, Panel } from "../ui" import API from "../../util/API";
const AddRecipe = () => { const AddRecipe = () => {
const authContext = useAuthContext(); const { user, token } = useAuthContext();
const [input, setInput] = useState<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '', ingredients: [] }) const [input, setInput] = useState<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '', ingredients: [] })
const [form, setForm] = useState<JSX.Element>(); const [toast, setToast] = useState(<></>)
const getFormState = useCallback((data: IRecipe) => { const getFormState = useCallback((data: IRecipe) => {
setInput(data); setInput(data);
}, [input]) }, [input])
const handleCreate = () => { const handleCreate = async () => {
if (!token) return;
for (let field of Object.keys(input)) { 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!') 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(
<Card>
<p>Created recipe {recipeName} successfully!</p>
<p>View your new recipe <a href={`/recipe/${recipeID}`}>here!</a></p>
</Card>
)
} }
useEffect(() => { useEffect(() => {
authContext.user && setInput((prev: IRecipe) => { if (!user) return;
user && setInput((prev: IRecipe) => {
return { return {
...prev, ...prev,
authoruserid: authContext.user!.id! authoruserid: user.id!
} }
}) })
}, [authContext]) }, [user])
useEffect(() => { useEffect(() => {
console.log(input); console.log(input);
@@ -38,7 +58,7 @@ const AddRecipe = () => {
<h1>Add a New Recipe</h1> <h1>Add a New Recipe</h1>
<Divider /> <Divider />
<Panel> <Panel extraStyles="width-80">
<Form parent={input} _config={{ <Form parent={input} _config={{
parent: "AddRecipe", parent: "AddRecipe",
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"], keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
@@ -49,9 +69,9 @@ const AddRecipe = () => {
richTextInitialValue: "<p>Enter recipe details here!</p>" richTextInitialValue: "<p>Enter recipe details here!</p>"
}} /> }} />
{ form || <h2>Loading...</h2> }
<Button onClick={handleCreate}>Create Recipe!</Button> <Button onClick={handleCreate}>Create Recipe!</Button>
<div id="toast">{ toast }</div>
</Panel> </Panel>
</Page> </Page>
) )

View File

@@ -1,38 +1,69 @@
import { useAuthContext } from "../../context/AuthContext"; import { useAuthContext } from "../../context/AuthContext";
import Protect from "../../util/Protect"; import Protect from "../../util/Protect";
import { useParams } from "react-router-dom"; 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 Collection = () => {
const [isDefault, setIsDefault] = useState(true); const [data, setData] = useState<ICollection>();
const { user } = useAuthContext(); const [owner, setOwner] = useState<IUser>();
const [recipes, setRecipes] = useState<IRecipe[]>();
const [content, setContent] = useState(<></>);
const { user, token } = useAuthContext();
const { id } = useParams(); const { id } = useParams();
if (id) { useEffect(() => {
setIsDefault(false); 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(
<>
<div className="section-header">
<h1>COLLECTION: {data.name}</h1>
{ <p>Collection by: {owner ? `${owner.firstname} ${owner.lastname}` : `${user.firstname} ${user.lastname}`}</p> }
<p>{recipes.length || 0} recipe{recipes.length != 1 && "s" }</p>
{ data.ismaincollection && (
owner ? <p>(This is {owner.firstname}'s main collection)</p> : <p>(This is your main collection)</p>
)}
<Panel>
{
recipes && recipes.map((each: IRecipe) =>
<div className="recipe-card" key={v4()}>
<h2>{each.name}</h2>
{ each.description && <div dangerouslySetInnerHTML={{ __html: each.description }}></div> }
</div>
)
} }
</Panel>
</div>
</>
)
}
}, [data, recipes])
return ( return (
<Protect> <Protect>
{ isDefault ? { content }
<>
<h1>Mikayla's collection</h1>
<p>37 recipes</p>
<p>71 ingredients</p>
<p>11 types of cuisine</p>
</>
:
<>
</>
}
{/* recipes */}
</Protect> </Protect>
) )
} }

View File

@@ -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 CollectionBrowser = () => {
const [list, setList] = useState<ICollection[]>();
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<CollectionDetails[]>();
let i = 0;
for (let each of allRecipes) {
}
setList(allRecipes);
}
})();
}, [token])
return ( return (
<Page> <Page>
<h1>Browsing your {2} collections:</h1> <h1>Browsing your {2} collections:</h1>
{
list && list.map(each => {
return (
<Card key={v4()}>
<h2>{each.name}</h2>
<a href={`/collections/${each.id}`}>Link to details</a>
</Card>
)
})
}
</Page> </Page>
) )
} }

View File

@@ -1,6 +1,7 @@
import { ChangeEvent, FC, useEffect, useState } from "react" import { ChangeEvent, FC, useEffect, useState } from "react"
import { v4 } from "uuid" import { v4 } from "uuid"
import RichText from "./RichText" import RichText from "./RichText"
import "/src/sass/components/Form.scss";
export interface FormConfig<T> { export interface FormConfig<T> {
parent: string parent: string
@@ -72,14 +73,14 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
if (config.dataTypes![i] == 'TINYMCE') { if (config.dataTypes![i] == 'TINYMCE') {
return ( return (
<div id={`${config.parent}-row-${i}`} key={v4()}> <div className="form-row-editor" id={`${config.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label> <label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
<RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} /> <RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} />
</div> </div>
) )
} else { } else {
return ( return (
<div id={`${config.parent}-row-${i}`} key={v4()}> <div className="form-row" id={`${config.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label> <label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
<input <input
type={config.dataTypes![i]} type={config.dataTypes![i]}
@@ -98,7 +99,7 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
}, [config]); }, [config]);
return ( return (
<div className={`ui-form-component ${_config.extraStyles}`}> <div className={`ui-form-component ${_config.extraStyles ?? ""}`}>
{ contents } { contents }
</div> </div>
) )

View File

View File

@@ -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;
}
}
}

View File

@@ -24,4 +24,8 @@
flex-flow: row wrap; flex-flow: row wrap;
justify-content: center; justify-content: center;
} }
&.width-80 {
width: 80vw;
}
} }

View File

@@ -167,7 +167,7 @@ module API {
export class Recipe extends RestController<IRecipe> { export class Recipe extends RestController<IRecipe> {
constructor(token: string) { 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); 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) { async getAllAuthored(id?: number | string) {
let response: AxiosResponse; let response: AxiosResponse;

View File

@@ -15,6 +15,14 @@ export default class CollectionCtl {
return new ControllerResponse(code, data); 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() { async getAll() {
const result = await CollectionInstance.getAll(); const result = await CollectionInstance.getAll();
const ok = result !== null; const ok = result !== null;

View File

@@ -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) { async getAllAuthored(id: number | string) {
console.log(id, typeof id); console.log(id, typeof id);
try { try {

View File

@@ -10,11 +10,18 @@ const router = Router();
export const collectionRoute = (app: Express) => { export const collectionRoute = (app: Express) => {
app.use('/app/collection', router); 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 { id } = req.params;
const { getRecipes } = req.query;
try { try {
if (getRecipes || getRecipes == "true") {
const { code, data } = await CollectionInstance.getRecipesFromOne(id);
res.status(code).send(data);
} else {
const { code, data } = await CollectionInstance.getOne(id); const { code, data } = await CollectionInstance.getOne(id);
res.status(code).send(data); res.status(code).send(data);
}
} catch(e) { } catch(e) {
next(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; const data = req.body;
try { try {

View File

@@ -22,12 +22,12 @@ export const recipeRoute = (app: Express) => {
}) })
router.get('/', restrictAccess, async (req, res, next) => { router.get('/', restrictAccess, async (req, res, next) => {
const user = req.session.user as IUser; const user = req.user as IUser;
const { filterby } = req.query; const { filter } = req.query;
try { try {
let result: CtlResponse<IRecipe[] | string>; let result: CtlResponse<IRecipe[] | string>;
switch (filterby) { switch (filter) {
case "myrecipes": case "myrecipes":
result = await recipectl.getAllAuthored(user.id as number); result = await recipectl.getAllAuthored(user.id as number);
break; break;
@@ -55,7 +55,7 @@ export const recipeRoute = (app: Express) => {
}) })
router.post('/', restrictAccess, async (req, res, next) => { router.post('/', restrictAccess, async (req, res, next) => {
const user = req.session.user as IUser; const user = req.user as IUser;
const data = req.body; const data = req.body;
try { try {

View File

@@ -1,6 +1,7 @@
import { Express, Router } from "express" import { Express, Router } from "express"
import { restrictAccess } from "../auth/middlewares"; import { restrictAccess } from "../auth/middlewares";
import { CollectionCtl } from "../controllers"; import { CollectionCtl } from "../controllers";
import { IUser } from "../schemas";
const CollectionInstance = new CollectionCtl(); const CollectionInstance = new CollectionCtl();
const router = Router(); const router = Router();
@@ -8,12 +9,11 @@ export const subscriptionRoute = (app: Express) => {
app.use('/app/subscription', router); app.use('/app/subscription', router);
router.get('/', async (req, res, next) => { router.get('/', async (req, res, next) => {
// @ts-ignore const user = req.user as IUser;
const { user } = req.session.user;
if (!user) return; if (!user) return;
try { try {
const result = await CollectionInstance.getSubscriptions(user.id as string); const result = await CollectionInstance.getSubscriptions(user.id!);
res.status(200).send(result); res.status(200).send(result);
} catch(e) { } catch(e) {
next(e); next(e);
@@ -21,12 +21,11 @@ export const subscriptionRoute = (app: Express) => {
}) })
router.post('/', restrictAccess, async (req, res, next) => { router.post('/', restrictAccess, async (req, res, next) => {
// @ts-ignore const user = req.user as IUser;
const { user } = req.session.user;
const { collection } = req.query; const { collection } = req.query;
try { 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); res.status(201).send(result);
} catch(e) { } catch(e) {
next(e); next(e);