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 { 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<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '', ingredients: [] })
const [form, setForm] = useState<JSX.Element>();
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(
<Card>
<p>Created recipe {recipeName} successfully!</p>
<p>View your new recipe <a href={`/recipe/${recipeID}`}>here!</a></p>
</Card>
)
}
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 = () => {
<h1>Add a New Recipe</h1>
<Divider />
<Panel>
<Panel extraStyles="width-80">
<Form parent={input} _config={{
parent: "AddRecipe",
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
@@ -49,9 +69,9 @@ const AddRecipe = () => {
richTextInitialValue: "<p>Enter recipe details here!</p>"
}} />
{ form || <h2>Loading...</h2> }
<Button onClick={handleCreate}>Create Recipe!</Button>
<div id="toast">{ toast }</div>
</Panel>
</Page>
)

View File

@@ -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<ICollection>();
const [owner, setOwner] = useState<IUser>();
const [recipes, setRecipes] = useState<IRecipe[]>();
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(
<>
<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 (
<Protect>
{ isDefault ?
<>
<h1>Mikayla's collection</h1>
<p>37 recipes</p>
<p>71 ingredients</p>
<p>11 types of cuisine</p>
</>
:
<>
</>
}
{/* recipes */}
{ content }
</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 [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 (
<Page>
<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>
)
}

View File

@@ -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<T> {
parent: string
@@ -72,14 +73,14 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
if (config.dataTypes![i] == 'TINYMCE') {
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>
<RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} />
</div>
)
} else {
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>
<input
type={config.dataTypes![i]}
@@ -98,7 +99,7 @@ const Form: FC<FormProps> = ({ parent, _config }) => {
}, [config]);
return (
<div className={`ui-form-component ${_config.extraStyles}`}>
<div className={`ui-form-component ${_config.extraStyles ?? ""}`}>
{ contents }
</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;
justify-content: center;
}
&.width-80 {
width: 80vw;
}
}

View File

@@ -167,7 +167,7 @@ module API {
export class Recipe extends RestController<IRecipe> {
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;

View File

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

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) {
console.log(id, typeof id);
try {

View File

@@ -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 {
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 {

View File

@@ -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<IRecipe[] | string>;
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 {

View File

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