refactor meal container

This commit is contained in:
Ruidy 2021-03-28 15:30:59 +02:00
parent 582a0f44db
commit 681c281d51
17 changed files with 194 additions and 247 deletions

View file

@ -1,87 +1,19 @@
import React, { FC, useState } from "react";
import { Router } from "./router/Router";
import { FC, useState } from "react";
import { PreLoader } from "./components/PreLoader";
import { useAuth0 } from "./utils/auth0-spa";
import { getData } from "./utils/methods";
import "./index.css";
import MainLayout from "./layouts/MainLayout";
import { AppRouter } from "./router";
import { Router } from "./router/Router";
import { getData } from "./services/api";
import { useAuth0 } from "./utils/auth0-spa";
export const App: FC = () => {
const { loading } = useAuth0();
const [searchString, setSearchString] = useState("");
const [searchResults, setSearchResults] = useState({ meals: [] });
// Default meal object. TODO: Find a better alternative …
const mealDef = {
meals: [
{
idMeal: "52837",
strMeal: "Chef's meal",
strDrinkAlternate: null,
strCategory: "yummy",
strArea: "Mine",
strInstructions:
"Cook the pasta following pack instructions.\r\n\r\nHeat the oil in a non-stick frying pan and cook the onion, garlic and chilli for 3-4 mins to soften. Stir in the tomato pur\u00e9e and cook for 1 min, then add the pilchards with their sauce. Cook, breaking up the fish with a wooden spoon, then add the olives and continue to cook for a few more mins.\r\n\r\nDrain the pasta and add to the pan with 2-3 tbsp of the cooking water. Toss everything together well, then divide between plates and serve, scattered with Parmesan.",
strMealThumb: require("./images/breakfast.svg"),
// "https://www.themealdb.com/images/media/meals/vvtvtr1511180578.jpg",
strTags: null,
strYoutube: "#",
strIngredient1: "Spaghetti",
strIngredient2: "Olive Oil",
strIngredient3: "Onion",
strIngredient4: "Garlic",
strIngredient5: "Red Chilli",
strIngredient6: "Tomato Puree",
strIngredient7: "Pilchards",
strIngredient8: "Black Olives",
strIngredient9: "Parmesan",
strIngredient10: "",
strIngredient11: "",
strIngredient12: "",
strIngredient13: "",
strIngredient14: "",
strIngredient15: "",
strIngredient16: "",
strIngredient17: "",
strIngredient18: "",
strIngredient19: "",
strIngredient20: "",
strMeasure1: "300g",
strMeasure2: "1 tbls",
strMeasure3: "1 finely chopped ",
strMeasure4: "2 cloves minced",
strMeasure5: "1",
strMeasure6: "1 tbls",
strMeasure7: "425g",
strMeasure8: "70g",
strMeasure9: "Shaved",
strMeasure10: "",
strMeasure11: "",
strMeasure12: "",
strMeasure13: "",
strMeasure14: "",
strMeasure15: "",
strMeasure16: "",
strMeasure17: "",
strMeasure18: "",
strMeasure19: "",
strMeasure20: "",
strSource: "https://www.bbcgoodfood.com/recipes/pilchard-puttanesca",
dateModified: null,
},
],
};
const [meal, setMeal] = useState(mealDef);
const [meal, setMeal] = useState(null);
const buttonUrl = "/random";
const getMeal = (id) => {
getData(id, setMeal, "lookup");
};
const getRandomMeal = () => {
getData("random", setMeal);
};
@ -105,11 +37,8 @@ export const App: FC = () => {
<Router>
<MainLayout
buttonUrl={buttonUrl}
// meal={meal}
// getMeal={getMeal}
getRandomMeal={getRandomMeal}
searchString={searchString}
// searchResults={searchResults}
setSearchResults={setSearchResults}
handleChange={handleChange}
setSearchString={setSearchString}
@ -117,8 +46,6 @@ export const App: FC = () => {
>
<AppRouter
buttonUrl={buttonUrl}
meal={meal}
getMeal={getMeal}
getRandomMeal={getRandomMeal}
searchString={searchString}
searchResults={searchResults}

View file

@ -1,24 +0,0 @@
import React from "react";
export const IngredientList = ({ ingredients }) => {
return (
<div className="ingredientList">
<table className="striped highlight responsive-table">
<thead>
<tr>
<th>Ingredient</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{ingredients.map((ing, i) => (
<tr key={i}>
<td>{ing[0]}</td>
<td>{ing[1]}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

View file

@ -1,11 +0,0 @@
import React from "react";
export const Recipe = ({ recipe }) => {
return (
<div className="recipe">
<div className="divider"></div>
<h3>Instructions</h3>
<p className="flow-text">{recipe}</p>
</div>
);
};

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { useParams, Redirect } from "react-router-dom";
import { useEffect, useState } from "react";
import { Redirect, useParams } from "react-router-dom";
import { CategoryPage } from "../pages/CategoryPage";
import { getData } from "../utils/methods";
import { getData } from "../services/api";
export const CategoryController = () => {
const [meals, setMeals] = useState({ meals: [] });

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { getData } from "../utils/methods";
import { CategoryListPage } from "../pages/CategoryListPage";
import { PreLoader } from "../components/PreLoader";
import { CategoryListPage } from "../pages/CategoryListPage";
import { getData } from "../services/api";
export const CategoryListController = () => {
const [categories, setCategories] = useState({ categories: [] });

View file

@ -0,0 +1,31 @@
import { FC } from "react";
import Meal from "../../types/Meal";
import { MealIngredientList } from "./components/MealIngredientList";
import { MealPresentation } from "./components/MealPresentation";
import { MealRecipe } from "./components/MealRecipe";
type Props = {
ingredients: string[];
recipe: string;
meal: Meal;
handleFavChange: () => void;
};
export const MealPage: FC<Props> = ({
meal,
ingredients,
recipe,
handleFavChange,
}) => (
<section className="container">
<div className="row">
<div className="col s12 l6">
<MealPresentation meal={meal} handleFavChange={handleFavChange} />
</div>
<div className="col s12 l6">
<MealIngredientList ingredients={ingredients} />
</div>
</div>
<MealRecipe recipe={recipe} />
</section>
);

View file

@ -0,0 +1,26 @@
import { FC } from "react";
type Props = {
ingredients: string[];
};
export const MealIngredientList: FC<Props> = ({ ingredients }) => (
<div className="ingredientList">
<table className="striped highlight responsive-table">
<thead>
<tr>
<th>Ingredient</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{ingredients.map((ing, i) => (
<tr key={i}>
<td>{ing[0]}</td>
<td>{ing[1]}</td>
</tr>
))}
</tbody>
</table>
</div>
);

View file

@ -1,7 +1,13 @@
import React from "react";
import { FC } from "react";
import { Link } from "react-router-dom";
import Meal from "../../../types/Meal";
export const MealPresentation = ({ meal }) => {
type Props = {
meal: Meal;
handleFavChange: () => void;
};
export const MealPresentation: FC<Props> = ({ meal, handleFavChange }) => {
const {
mealName,
imgAddress,
@ -9,7 +15,6 @@ export const MealPresentation = ({ meal }) => {
mealCategory,
mealArea,
isFav,
handleFavChange,
} = meal;
return (

View file

@ -0,0 +1,13 @@
import { FC } from "react";
type Props = {
recipe: string;
};
export const MealRecipe: FC<Props> = ({ recipe }) => (
<div className="recipe">
<div className="divider" />
<h3>Instructions</h3>
<p className="flow-text">{recipe}</p>
</div>
);

View file

@ -0,0 +1,75 @@
import React, { FC, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { NotFoundPage } from "../../pages/NotFoundPage";
import { useFirebase } from "../../services/Firebase";
import { useAuth0 } from "../../utils/auth0-spa";
import { MealPage } from "./MealPage";
import { getMeal, getRandomMeal } from "./service";
export const Meal: FC = () => {
// hooks
const { user, isAuthenticated } = useAuth0();
const { id } = useParams();
const fb = useFirebase();
// local state
const [meal, setMeal] = useState(null);
const [isFav, setIsFav] = useState<boolean>();
// variables
const mealItem = meal?.meals?.[0];
// effects
/** Fetch meal from db */
useEffect(() => {
!id ? getRandomMeal(setMeal) : getMeal(id, setMeal);
}, [id]);
/** Updates fav status in db */
useEffect(() => {
if (isAuthenticated) {
fb.isFav(user.email, mealItem?.idMeal).then((res) => setIsFav(res));
}
}, [user, fb, mealItem?.idMeal, isAuthenticated]);
// other logic
const handleFavChange = () => {
if (isAuthenticated) {
setIsFav(!isFav);
// Send !isFav because state is not yet updated
fb.addToFavs(
user?.email,
mealItem?.idMeal,
mealItem?.strMeal,
mealItem?.strMealThumb,
!isFav
);
} else {
window.alert("You must be authenticated to add to favourites.");
}
};
const item = {
mealName: mealItem?.strMeal,
imgAddress: mealItem?.strMealThumb,
videoAddress: mealItem?.strYoutube,
mealCategory: mealItem?.strCategory,
mealArea: mealItem?.strArea,
isFav,
};
let ingredients = [];
for (let i = 1; i <= 20; i++) {
let strIng = `strIngredient${i}`;
let strMes = `strMeasure${i}`;
if (!!mealItem?.[strIng] && !!mealItem?.[strIng]) {
ingredients.push([mealItem?.[strIng], mealItem?.[strMes]]);
}
}
return !!meal?.meals ? (
<MealPage
meal={item}
ingredients={ingredients}
recipe={mealItem?.strInstructions}
handleFavChange={handleFavChange}
/>
) : (
<NotFoundPage handleClick={getRandomMeal} />
);
};

View file

@ -0,0 +1,9 @@
import { getData } from "../../services/api";
export const getMeal = (id, setMeal) => {
getData(id, setMeal, "lookup");
};
export const getRandomMeal = (setMeal) => {
getData("random", setMeal);
};

View file

@ -1,78 +0,0 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useAuth0 } from "../utils/auth0-spa";
import { MealPage } from "../pages/MealPage";
import { NotFoundPage } from "../pages/NotFoundPage";
import { useFirebase } from "../services/Firebase";
export const MealController = ({ meal, getMeal, getRandomMeal }) => {
const { user, isAuthenticated } = useAuth0();
const { id } = useParams();
const fb = useFirebase();
useEffect(() => {
id === undefined ? getRandomMeal() : getMeal(id);
// eslint-disable-next-line
}, []);
const mealItem = meal.meals[0];
const {
idMeal,
strMeal,
strMealThumb,
strYoutube,
strCategory,
strArea,
strInstructions,
} = mealItem;
const [isFav, setIsFav] = useState<boolean>();
/**
* Updates fav status in db
*/
const handleFavChange = (e) => {
e.preventDefault();
setIsFav(!isFav);
// Send !isFav because state is not yet updated
fb.addToFavs(user.email, idMeal, strMeal, strMealThumb, !isFav);
};
useEffect(() => {
// Not update fav status of the placeholder recipe. TODO: it's ugly...
if (idMeal !== "52837" && isAuthenticated) {
fb.isFav(user.email, idMeal).then((res) => setIsFav(res));
}
}, [user, fb, idMeal, isAuthenticated]);
const item = {
mealName: strMeal,
imgAddress: strMealThumb,
videoAddress: strYoutube,
mealCategory: strCategory,
mealArea: strArea,
isFav,
handleFavChange,
};
let ingredientList = [];
var i;
for (i = 1; i <= 20; i++) {
let strIng = `strIngredient${i}`;
let strMes = `strMeasure${i}`;
if (mealItem[strIng] !== "" && mealItem[strIng] !== null) {
ingredientList.push([mealItem[strIng], mealItem[strMes]]);
}
}
return meal !== undefined && meal.meals !== null ? (
<MealPage
item={item}
ingredientList={ingredientList}
strInstructions={strInstructions}
/>
) : (
<NotFoundPage handleClick={getRandomMeal} />
);
};

View file

@ -1,20 +0,0 @@
import React from "react";
import { MealPresentation } from "../components/MealPresentation";
import { IngredientList } from "../components/IngredientList";
import { Recipe } from "../components/Recipe";
export const MealPage = ({ item, ingredientList, strInstructions }) => {
return (
<div className="container">
<div className="row">
<div className="col s12 l6">
<MealPresentation meal={item} />
</div>
<div className="col s12 l6">
<IngredientList ingredients={ingredientList} />
</div>
</div>
<Recipe recipe={strInstructions} />
</div>
);
};

View file

@ -1,10 +1,10 @@
import { Switch, Route, Redirect } from "react-router-dom";
import { SearchController } from "../containers/SearchController";
import { Home } from "../containers/Home";
import { MealController } from "../containers/MealController";
import { Redirect, Route, Switch } from "react-router-dom";
import { CategoryController } from "../containers/CategoryController";
import { CategoryListController } from "../containers/CategoryListController";
import { Home } from "../containers/Home";
import { Meal } from "../containers/Meal";
import { ProfileController } from "../containers/ProfileController";
import { SearchController } from "../containers/SearchController";
import { ContactPage } from "../pages/Contact";
import { NotFoundPage } from "../pages/NotFoundPage";
import { PrivateRoute } from "./PrivateRoute";
@ -13,8 +13,6 @@ import { PrivateRoute } from "./PrivateRoute";
const AppRouter = ({
buttonUrl,
meal,
getMeal,
getRandomMeal,
searchString,
searchResults,
@ -27,11 +25,7 @@ const AppRouter = ({
<PrivateRoute exact path="/profile" component={ProfileController} />
<Route exact path={buttonUrl}>
<MealController
meal={meal}
getMeal={getMeal}
getRandomMeal={getRandomMeal}
/>
<Meal />
</Route>
<Route exact path="/categories">
@ -58,11 +52,7 @@ const AppRouter = ({
</Route>
<Route path="/:id">
<MealController
meal={meal}
getMeal={getMeal}
getRandomMeal={getRandomMeal}
/>
<Meal />
</Route>
<Route path="*">

View file

@ -1,6 +1,6 @@
export const createURI = (keyword, option) => {
export const createURI = (keyword: string, option: string) => {
const ROOT = "https://www.themealdb.com/api/json/v1/1/";
if (option === null) {
if (!option) {
return `${ROOT}${keyword}.php`;
} else if (option === "filter") {
return `${ROOT}${option}.php?c=${keyword}`;
@ -11,16 +11,10 @@ export const createURI = (keyword, option) => {
}
};
export const getData = (keyword, set, option = null) => {
export const getData = (keyword: string, set, option: string = null) => {
const URI = createURI(keyword, option);
fetch(URI)
.then(response => response.json())
.catch(error => console.info(error + "url:" + URI))
.then(data => set(data));
.then((response) => response.json())
.catch((error) => console.warn(error + "url:" + URI))
.then((data) => set(data));
};
export const upFirstChar = lower => {
return lower.replace(/^\w/, c => c.toUpperCase());
};
export const addToFavourites = () => {};

8
src/types/Meal.ts Normal file
View file

@ -0,0 +1,8 @@
export default interface Meal {
mealName: string;
imgAddress: string;
videoAddress: string;
mealCategory: string;
mealArea: string;
isFav: boolean;
}

2
src/utils/methods.ts Normal file
View file

@ -0,0 +1,2 @@
export const upFirstChar = (lower: string): string =>
lower.replace(/^\w/, (c) => c.toUpperCase());