in progress: handle add ingredients to recipe
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { useAuthContext } from './context/AuthContext';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import API from './util/API';
|
||||
|
||||
// pages, ui, components, styles
|
||||
import Subscriptions from './components/pages/Subscriptions/Subscriptions';
|
||||
@@ -19,11 +17,10 @@ import CollectionBrowser from './components/pages/CollectionBrowser';
|
||||
import { Navbar } from './components/ui';
|
||||
import GroceryList from './components/pages/GroceryList';
|
||||
import GroceryListCollection from './components/pages/GroceryListCollection';
|
||||
import { TokenType } from './util/types';
|
||||
import './sass/App.scss';
|
||||
import handleToken from './util/handleToken';
|
||||
import AddFriends from './components/pages/AddFriends';
|
||||
import Sandbox from './components/Sandbox';
|
||||
import './sass/App.scss';
|
||||
|
||||
function App() {
|
||||
const { setUser, token, setToken } = useAuthContext();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { v4 } from "uuid";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { getAllUsers, getFriendships, getPendingFriendRequests, getUserByID } from "../../util/apiUtils";
|
||||
import API from "../../util/API";
|
||||
import UserCard from "../ui/UserCard";
|
||||
import { IUser, IFriendship } from "../../schemas";
|
||||
@@ -18,9 +17,9 @@ const Friends: FC<{ targetUser?: IUser }> = ({ targetUser }) => {
|
||||
(async function() {
|
||||
try {
|
||||
const Friends = new API.Friendship(token);
|
||||
const result = await Friends.getAll();
|
||||
const result: IFriendship[] | null = await Friends.getAll();
|
||||
|
||||
if (result.length) {
|
||||
if (result?.length) {
|
||||
setFriends(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
// library/framework
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Button, Card, Divider, Page, Panel } from "../ui"
|
||||
import { DropdownData, IIngredient, IRecipe } from "../../schemas";
|
||||
import IngredientSelector from "../derived/IngredientSelector";
|
||||
import { Autocomplete, TextField } from "@mui/material";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
// util/api
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { DropdownData, IIngredient, IRecipe, RecipeIngredient } from "../../schemas";
|
||||
import { IngredientFieldData } from "../../util/types";
|
||||
import Protect from "../../util/Protect";
|
||||
import API from "../../util/API";
|
||||
import { v4 } from "uuid";
|
||||
import RichText from "../ui/RichText";
|
||||
import { Autocomplete, TextField } from "@mui/material";
|
||||
import { IngredientFieldData } from "../../util/types";
|
||||
import Toast from "../ui/Toast";
|
||||
|
||||
// ui/components
|
||||
import { Button, Card, Divider, Panel, RichText, Toast } from "../ui"
|
||||
import IngredientSelector from "../derived/IngredientSelector";
|
||||
|
||||
export default function AddRecipe() {
|
||||
/**********************************
|
||||
@@ -103,7 +106,7 @@ export default function AddRecipe() {
|
||||
});
|
||||
}
|
||||
|
||||
if (courseList) {
|
||||
if (courseList && !courseData.length) {
|
||||
setCourseData((prev) => {
|
||||
let newEntries = filterDuplicateEntries(courseList, prev);
|
||||
return [...prev, ...newEntries];
|
||||
@@ -112,22 +115,23 @@ export default function AddRecipe() {
|
||||
})();
|
||||
}, [token])
|
||||
|
||||
// mount the ingredient selection section once dependencies have loaded
|
||||
// mount the ingredient selection section once conditions met:
|
||||
// 1. ingredients have been fetched
|
||||
// 2. measurements have been fetched
|
||||
// 3. ingredient fields have not already been initialized
|
||||
useEffect(() => {
|
||||
if (ingredients.length && measurements.length) {
|
||||
const conditionsMet = (ingredients.length && measurements.length) && (!ingredientFields.length);
|
||||
|
||||
if (conditionsMet) {
|
||||
setIngredientFields([<IngredientSelector key={v4()} position={optionCount} ingredients={ingredients} units={measurements} getRowState={getRowState} destroy={destroySelector} />]);
|
||||
}
|
||||
}, [ingredients, measurements])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(ingredientFieldData);
|
||||
}, [getRowState]);
|
||||
|
||||
/**********************************
|
||||
* PAGE SPECIFIC FUNCTIONS
|
||||
*********************************/
|
||||
// submit handler
|
||||
const handleCreate = async () => {
|
||||
async function handleCreate() {
|
||||
if (!user || !token) return;
|
||||
|
||||
// initialize API handlers
|
||||
@@ -153,32 +157,53 @@ export default function AddRecipe() {
|
||||
}
|
||||
}
|
||||
|
||||
let ingredientSelections = new Array<any>();
|
||||
let preparedIngredientData = new Array<RecipeIngredient>();
|
||||
let newIngredientCount = 0;
|
||||
|
||||
// handle ingredient row data
|
||||
for (let row of ingredientFieldData) {
|
||||
if (!row) continue;
|
||||
console.log(row);
|
||||
|
||||
if (row.ingredientSelection === undefined) {
|
||||
messages.push("Please ensure you have included ingredient selections for all rows.");
|
||||
}
|
||||
|
||||
ingredientSelections.push(row.ingredientSelection);
|
||||
|
||||
for (let ing of row.ingredients) {
|
||||
// filter out recipes that already exist
|
||||
if (ingredients.filter(x => x.name == ing.name).includes(ing)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// update the ingredient list
|
||||
setIngredients((prev) => [...prev, ing]);
|
||||
if (!row.quantity) {
|
||||
messages.push("Please provide a quantity for ingredient " + row.ingredientSelection);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!row.measurement) {
|
||||
messages.push(row.ingredientSelection + " missing required unit of measurement");
|
||||
continue;
|
||||
}
|
||||
|
||||
const newID = row.ingredients.filter(ingr => ingr.name == row.ingredientSelection)[0].id;
|
||||
|
||||
const newIngredientData: RecipeIngredient = {
|
||||
id: newID ?? row.ingredients.length + 1,
|
||||
name: row.ingredientSelection as string,
|
||||
quantity: row.quantity,
|
||||
unit: row.measurement
|
||||
}
|
||||
|
||||
preparedIngredientData.push(newIngredientData);
|
||||
|
||||
for (let ing of row.ingredients) {
|
||||
// filter out recipes that already exist
|
||||
if (!ingredients.filter(x => x.name == ing.name).includes(ing)) {
|
||||
// post the new ingredient to the database
|
||||
const newEntry = await ingredientAPI.post(ing);
|
||||
messages.push(`Successfully created new ingredient: ${ing.name}!`);
|
||||
console.log(newEntry);
|
||||
newIngredientCount++;
|
||||
}
|
||||
|
||||
// update the ingredient list
|
||||
setIngredients((prev) => [...prev, ing]);
|
||||
}
|
||||
}
|
||||
|
||||
// post recipe entry
|
||||
@@ -190,7 +215,7 @@ export default function AddRecipe() {
|
||||
const recipeName = result.recipe.name;
|
||||
let recipeIngredientCount = 0;
|
||||
|
||||
for (let ing of ingredientSelections) {
|
||||
for (let ing of preparedIngredientData) {
|
||||
const ok = await recipeAPI.addIngredientToRecipe(ing, recipeID);
|
||||
if (ok) recipeIngredientCount++;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Divider, Page, Panel } from "../ui";
|
||||
import { IRecipe, IUser, IIngredient } from "../../schemas";
|
||||
import { IRecipe, IUser, IIngredient, RecipeIngredient } from "../../schemas";
|
||||
import { getRecipeByID } from "../../util/apiUtils";
|
||||
import Protect from "../../util/Protect";
|
||||
import API from "../../util/API";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import ResourceNotFound from "./StatusPages/404";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export default function Recipe() {
|
||||
const { user, token } = useAuthContext();
|
||||
@@ -14,7 +15,7 @@ export default function Recipe() {
|
||||
|
||||
const [recipe, setRecipe] = useState<IRecipe | "no recipe">();
|
||||
const [userData, setUserData] = useState<IUser>();
|
||||
const [ingredientData, setIngredientData] = useState<IIngredient[]>([]);
|
||||
const [ingredientData, setIngredientData] = useState<RecipeIngredient[]>([]);
|
||||
const [view, setView] = useState<JSX.Element>(<h1>Loading...</h1>);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,17 +33,19 @@ export default function Recipe() {
|
||||
}, [token])
|
||||
|
||||
useEffect(() => {
|
||||
if (recipe) {
|
||||
if (recipe && id) {
|
||||
if (recipe === "no recipe") {
|
||||
setView(<ResourceNotFound><h2>We couldn't find a recipe with the ID {id}.</h2></ResourceNotFound>);
|
||||
} else {
|
||||
if (!user || !token) return;
|
||||
|
||||
(async() => {
|
||||
// while ingredient data has not yet been mapped,
|
||||
// get ingredients and map them
|
||||
(!ingredientData.length) && (async() => {
|
||||
const ingredientAPI = new API.Ingredient(token);
|
||||
const result = await ingredientAPI.getAllForRecipe(id!);
|
||||
const result = await ingredientAPI.getAllForRecipe(id);
|
||||
if (result.length) setIngredientData(result);
|
||||
})
|
||||
})();
|
||||
|
||||
const selfAuthored = (recipe.authoruserid == user.id!);
|
||||
if (selfAuthored) {
|
||||
@@ -61,7 +64,7 @@ export default function Recipe() {
|
||||
useEffect(() => {
|
||||
if (!userData) return;
|
||||
|
||||
if (recipe && recipe !== "no recipe") {
|
||||
if (recipe && ingredientData && recipe !== "no recipe") {
|
||||
setView(
|
||||
<Protect redirect={`/recipe/${id}`}>
|
||||
<h1>{recipe.name}</h1>
|
||||
@@ -75,8 +78,11 @@ export default function Recipe() {
|
||||
|
||||
<h2>Ingredients:</h2>
|
||||
{ ingredientData.length
|
||||
? ingredientData.map((each: IIngredient) => <p>{each.name}</p>)
|
||||
: "No ingredients for this recipe"
|
||||
? ingredientData.map((each: RecipeIngredient) => (
|
||||
<div key={v4()}>
|
||||
<p>{each.quantity} {each.quantity == 1 ? each.unit : (each.unit + "s")} {each.name}</p>
|
||||
</div>
|
||||
)) : <p>No ingredients for this recipe</p>
|
||||
}
|
||||
|
||||
<Divider />
|
||||
@@ -86,7 +92,7 @@ export default function Recipe() {
|
||||
</Protect>
|
||||
);
|
||||
}
|
||||
}, [userData, ingredientData]);
|
||||
}, [userData, recipe, ingredientData]);
|
||||
|
||||
return view
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import Protect from "../../util/Protect";
|
||||
import Form from "./Form/Form";
|
||||
|
||||
@@ -9,6 +10,8 @@ interface BrowserProps {
|
||||
}
|
||||
|
||||
const Browser: FC<BrowserProps> = ({ children, header, searchFunction }) => {
|
||||
const { user, token } = useAuthContext();
|
||||
|
||||
const [form, setForm] = useState<any>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -6,10 +6,12 @@ import Form from "./Form/Form";
|
||||
import Navbar from "./Navbar";
|
||||
import Page from "./Page";
|
||||
import Panel from "./Panel";
|
||||
import RichText from "./RichText";
|
||||
import TextField from "./TextField";
|
||||
import Toast from "./Toast";
|
||||
import Tooltip from "./Tooltip";
|
||||
import UserCard from "./UserCard";
|
||||
|
||||
export {
|
||||
Button, Card, Dropdown, Divider, Form, Navbar, Page, Panel, TextField, Tooltip, UserCard
|
||||
Button, Card, Dropdown, Divider, Form, Navbar, Page, Panel, RichText, TextField, Toast, Tooltip, UserCard
|
||||
}
|
||||
@@ -56,29 +56,66 @@ module API {
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const response = await this.instance.get(this.endpoint, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
const response = await this.instance.get(this.endpoint, this.headers)
|
||||
.catch((err: AxiosError) => {
|
||||
console.log(err.message);
|
||||
return null;
|
||||
});
|
||||
return Promise.resolve(response?.data);
|
||||
}
|
||||
|
||||
async getByID(id: string) {
|
||||
const response = await this.instance.get(this.endpoint + "/" + id, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
const response = await this.instance.get(this.endpoint + "/" + id, this.headers)
|
||||
.catch((err: AxiosError) => {
|
||||
console.log(err.message);
|
||||
return null;
|
||||
});
|
||||
return Promise.resolve(response?.data);
|
||||
}
|
||||
|
||||
async post(data: T) {
|
||||
console.log(data);
|
||||
const response = await this.instance.post(this.endpoint, data, this.headers);
|
||||
try {
|
||||
const response = await this.instance.post(this.endpoint, JSON.stringify(data), this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
} catch(e: any) {
|
||||
if (e instanceof AxiosError) {
|
||||
const error = e as AxiosError;
|
||||
if (error.message) {
|
||||
console.log(error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async put(id: string, data: T | Partial<T>) {
|
||||
try {
|
||||
const response = await this.instance.put(this.endpoint + "/" + id, JSON.stringify(data), this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
} catch(e: any) {
|
||||
if (e instanceof AxiosError) {
|
||||
const error = e as AxiosError;
|
||||
if (error.message) {
|
||||
console.log(error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
try {
|
||||
const response = await this.instance.delete(this.endpoint + '/' + id, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
} catch(e: any) {
|
||||
if (e instanceof AxiosError) {
|
||||
const error = e as AxiosError;
|
||||
if (error.message) {
|
||||
console.log(error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,19 +178,6 @@ module API {
|
||||
super(Settings.getAPISTRING() + "/app/friend", token);
|
||||
}
|
||||
|
||||
override async getAll() {
|
||||
try {
|
||||
const response = await this.instance.get(this.endpoint, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
} catch(e) {
|
||||
const error = e as AxiosError;
|
||||
if (error.response?.status == 404) {
|
||||
console.log('no friends found');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getTargetUserFriendships(id: string | number) {
|
||||
try {
|
||||
const response = await this.instance.get(this.endpoint + `?targetUser=${id}`, this.headers);
|
||||
@@ -189,8 +213,12 @@ module API {
|
||||
}
|
||||
|
||||
async addIngredientToRecipe(ingredient: RecipeIngredient, recipeid: string | number) {
|
||||
const response = await this.instance.post(this.endpoint + `?addIngredients=true&recipeID=${recipeid}`, JSON.stringify(ingredient), this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
const response = await this.instance.post(this.endpoint + `?addIngredients=true&recipeID=${recipeid}`, JSON.stringify(ingredient), this.headers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
return Promise.resolve(response?.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ instance.interceptors.response.use((res: AxiosResponse<any,any>) => {
|
||||
|
||||
return res;
|
||||
}, (err) => {
|
||||
return Promise.reject(err);
|
||||
console.log(err);
|
||||
// return err;
|
||||
// return Promise.reject(err);
|
||||
})
|
||||
|
||||
export default instance;
|
||||
2
dev.sh
Normal file
2
dev.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#! /bin/bash
|
||||
concurrently "cd server && npm run dev" "cd client && npm run dev"
|
||||
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "recipe-manager",
|
||||
"version": "1.0.0",
|
||||
"description": "A concept developed by Mikayla Dobson",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "bash dev.sh"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/innocuous-symmetry/recipe-manager.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/innocuous-symmetry/recipe-manager/issues"
|
||||
},
|
||||
"homepage": "https://github.com/innocuous-symmetry/recipe-manager#readme",
|
||||
"devDependencies": {
|
||||
"concurrently": "^7.6.0"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,17 @@ export default class IngredientCtl {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllForRecipe(recipeid: string) {
|
||||
try {
|
||||
const result = await IngredientInstance.getAllForRecipe(recipeid);
|
||||
const ok = result !== null;
|
||||
const code = ok ? StatusCode.OK : StatusCode.NotFound;
|
||||
return new ControllerResponse(code, (result || "No ingredient found with this recipe ID"));
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async getOne(id: string) {
|
||||
try {
|
||||
const result = await IngredientInstance.getOne(id);
|
||||
|
||||
@@ -25,6 +25,17 @@ export class Ingredient {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllForRecipe(recipeid: string) {
|
||||
try {
|
||||
const statement = `SELECT * FROM recipin.cmp_recipeingredient WHERE recipeid = $1`;
|
||||
const result = await pool.query(statement, [recipeid]);
|
||||
if (result.rows.length) return result.rows[0];
|
||||
return null;
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async post(data: IIngredient) {
|
||||
try {
|
||||
const statement = `
|
||||
|
||||
@@ -10,9 +10,16 @@ export const ingredientRoute = (app: Express) => {
|
||||
app.use('/app/ingredient', router);
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
const { recipeID } = req.query;
|
||||
|
||||
try {
|
||||
if (recipeID) {
|
||||
const result = await IngredientInstance.getAllForRecipe(recipeID as string);
|
||||
res.status(result.code).send(result.data);
|
||||
} else {
|
||||
const result: CtlResponse<IIngredient[] | string> = await IngredientInstance.getAll();
|
||||
res.status(result.code).send(result.data);
|
||||
}
|
||||
} catch(e) {
|
||||
next(e);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ export const recipeRoute = (app: Express) => {
|
||||
const data = req.body;
|
||||
const { addIngredients, recipeID } = req.query;
|
||||
|
||||
console.log(data);
|
||||
|
||||
try {
|
||||
if (addIngredients) {
|
||||
const result = await recipectl.addIngredientToRecipe(data, recipeID as string);
|
||||
|
||||
Reference in New Issue
Block a user