mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-12 13:26:45 +00:00
refactoring (#5)
* use tsx * compile * refactor Router * refactor layout * refactor home container * refactor meal container * refactor categories container * refactor category container * refactor search and profile container * refactor
This commit is contained in:
parent
0b5a0a991c
commit
0e3b0a7cee
74 changed files with 652 additions and 679 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
build/
|
build/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
/.idea
|
||||||
|
|
@ -9,9 +9,9 @@ Free meal planner for cooks short on ideas! (like me …)
|
||||||
## Feature list
|
## Feature list
|
||||||
|
|
||||||
- Random meal suggestion ✓
|
- Random meal suggestion ✓
|
||||||
- Search by name: you're look for a recipe? Ours are easy to make and Yummy! ✓
|
- Search by name: you look for a recipe? Ours are easy to make and Yummy! ✓
|
||||||
- What's in the fridge ? Choose your main ingredient and get a meal suggestion
|
- What's in the fridge ? Choose your main ingredient and get a meal suggestion
|
||||||
- Choose by category: ✓
|
- Choose by a category: ✓
|
||||||
- Beef
|
- Beef
|
||||||
- Breakfast
|
- Breakfast
|
||||||
- Chicken
|
- Chicken
|
||||||
|
|
|
||||||
1
TODO.md
1
TODO.md
|
|
@ -13,3 +13,4 @@
|
||||||
- [x] Create PageLayout component
|
- [x] Create PageLayout component
|
||||||
- [ ] Use Css-in-Js
|
- [ ] Use Css-in-Js
|
||||||
- [ ] Redirect to 404
|
- [ ] Redirect to 404
|
||||||
|
- [x] Typescript
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "src"
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
@ -33,5 +33,10 @@
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.14.37",
|
||||||
|
"@types/react": "^17.0.3",
|
||||||
|
"typescript": "^4.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
131
src/App.jsx
131
src/App.jsx
|
|
@ -1,131 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Router } from "./utils/router";
|
|
||||||
|
|
||||||
import { PreLoader } from "./components/PreLoader";
|
|
||||||
|
|
||||||
import { useAuth0 } from "./utils/auth0-spa";
|
|
||||||
import { getData } from "./utils/methods";
|
|
||||||
import history from "./utils/history";
|
|
||||||
|
|
||||||
import "./index.css";
|
|
||||||
import MainLayout from "./layouts/MainLayout";
|
|
||||||
import MainRouter from "./controllers/MainRouter";
|
|
||||||
|
|
||||||
export const App = () => {
|
|
||||||
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 getMeal = (id) => {
|
|
||||||
getData(id, setMeal, "lookup");
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRandomMeal = () => {
|
|
||||||
getData("random", setMeal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSearchResults = (e) => {
|
|
||||||
searchString === ""
|
|
||||||
? e.preventDefault()
|
|
||||||
: getData(searchString, setSearchResults, "search");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { value } = e.target;
|
|
||||||
setSearchString(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonUrl = "/random";
|
|
||||||
|
|
||||||
return loading ? (
|
|
||||||
<div className="container center-align valign-wrapper">
|
|
||||||
<PreLoader />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Router history={history}>
|
|
||||||
<MainLayout
|
|
||||||
buttonUrl={buttonUrl}
|
|
||||||
meal={meal}
|
|
||||||
getMeal={getMeal}
|
|
||||||
getRandomMeal={getRandomMeal}
|
|
||||||
searchString={searchString}
|
|
||||||
searchResults={searchResults}
|
|
||||||
setSearchResults={setSearchResults}
|
|
||||||
handleChange={handleChange}
|
|
||||||
setSearchString={setSearchString}
|
|
||||||
getSearchResults={getSearchResults}
|
|
||||||
>
|
|
||||||
<MainRouter
|
|
||||||
buttonUrl={buttonUrl}
|
|
||||||
meal={meal}
|
|
||||||
getMeal={getMeal}
|
|
||||||
getRandomMeal={getRandomMeal}
|
|
||||||
searchString={searchString}
|
|
||||||
searchResults={searchResults}
|
|
||||||
/>
|
|
||||||
</MainLayout>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
const { getByText } = render(<App />);
|
|
||||||
const linkElement = getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
55
src/App.tsx
Normal file
55
src/App.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { FC, useState } from "react";
|
||||||
|
import { PreLoader } from "./components/PreLoader";
|
||||||
|
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: [] });
|
||||||
|
const [meal, setMeal] = useState(null);
|
||||||
|
const buttonUrl = "/random";
|
||||||
|
|
||||||
|
const getRandomMeal = () => {
|
||||||
|
getData("random", setMeal);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSearchResults = (e) => {
|
||||||
|
searchString === ""
|
||||||
|
? e.preventDefault()
|
||||||
|
: getData(searchString, setSearchResults, "search");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
setSearchString(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return loading ? (
|
||||||
|
<div className="container center-align valign-wrapper">
|
||||||
|
<PreLoader />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Router>
|
||||||
|
<MainLayout
|
||||||
|
buttonUrl={buttonUrl}
|
||||||
|
getRandomMeal={getRandomMeal}
|
||||||
|
searchString={searchString}
|
||||||
|
setSearchResults={setSearchResults}
|
||||||
|
handleChange={handleChange}
|
||||||
|
setSearchString={setSearchString}
|
||||||
|
getSearchResults={getSearchResults}
|
||||||
|
>
|
||||||
|
<AppRouter
|
||||||
|
getRandomMeal={getRandomMeal}
|
||||||
|
searchString={searchString}
|
||||||
|
searchResults={searchResults}
|
||||||
|
/>
|
||||||
|
</MainLayout>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { MealSummary } from "../types/meal";
|
||||||
|
|
||||||
export const CardEntry = ({ item, className = "col s12 m6" }) => {
|
type Props = {
|
||||||
const { idMeal, strMeal, strMealThumb } = item;
|
meal: MealSummary;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardEntry: FC<Props> = ({ meal, className = "col s12 m6" }) => {
|
||||||
|
const { idMeal, strMeal, strMealThumb } = meal;
|
||||||
return (
|
return (
|
||||||
<Link to={`${idMeal}`}>
|
<Link to={`/${idMeal}`}>
|
||||||
<li>
|
<li>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="card hoverable">
|
<div className="card hoverable">
|
||||||
|
|
@ -97,7 +97,7 @@ const ContactFormTextArea = ({ id, value, dispatch }) => {
|
||||||
<label htmlFor={id}>{id}</label>
|
<label htmlFor={id}>{id}</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="materialize-textarea validate"
|
className="materialize-textarea validate"
|
||||||
rows="12"
|
rows={12}
|
||||||
name={id}
|
name={id}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { CardEntry } from "./CardEntry";
|
|
||||||
|
|
||||||
export const SearchResult = ({ meal }) => {
|
|
||||||
return <CardEntry item={meal} />;
|
|
||||||
};
|
|
||||||
|
|
@ -37,7 +37,7 @@ export const SideNav = ({
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
"rgba(0,0,0,0.5)" /* Black background with opacity */,
|
"rgba(0,0,0,0.5)" /* Black background with opacity */,
|
||||||
zIndex:
|
zIndex:
|
||||||
"2" /* Specify a stack order in case you're using a different order for other elements */,
|
2 /* Specify a stack order in case you're using a different order for other elements */,
|
||||||
// cursor: "pointer" /* Add a pointer on hover */
|
// cursor: "pointer" /* Add a pointer on hover */
|
||||||
}}
|
}}
|
||||||
src={require("../images/special_event.svg")}
|
src={require("../images/special_event.svg")}
|
||||||
1
src/constants.ts
Normal file
1
src/constants.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const buttonURL = "/random";
|
||||||
|
|
@ -1,33 +1,26 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link, useRouteMatch } from "react-router-dom";
|
import { Link, useRouteMatch } from "react-router-dom";
|
||||||
|
|
||||||
const CategoryEntry = ({ category }) => {
|
type Props = {
|
||||||
|
strCategory: string;
|
||||||
|
strCategoryThumb: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CategoriesEntry: FC<Props> = ({ strCategory, strCategoryThumb }) => {
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
const {
|
|
||||||
strCategory,
|
|
||||||
strCategoryThumb,
|
|
||||||
// strCategoryDescription
|
|
||||||
} = category;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <CardEntry item={meal} />
|
|
||||||
<Link to={`${url}/${strCategory}`}>
|
<Link to={`${url}/${strCategory}`}>
|
||||||
<li>
|
<li>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col s12">
|
<div className="col s12">
|
||||||
<div className="card horizontal hoverable">
|
<div className="card horizontal hoverable">
|
||||||
<div className="card-image valign-wrapper">
|
<div className="card-image valign-wrapper">
|
||||||
<img
|
<img src={strCategoryThumb} alt={strCategory} />
|
||||||
// className="responsive-img"
|
|
||||||
src={strCategoryThumb}
|
|
||||||
alt={strCategory}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="card-stacked">
|
<div className="card-stacked">
|
||||||
<div className="card-content black-text">
|
<div className="card-content black-text">
|
||||||
<h2 className="logo">{strCategory}</h2>
|
<h2 className="logo">{strCategory}</h2>
|
||||||
{/* <p>{strCategoryDescription}</p> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -38,4 +31,4 @@ const CategoryEntry = ({ category }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CategoryEntry;
|
export default CategoriesEntry;
|
||||||
21
src/containers/Categories/components/CategoriesPage.tsx
Normal file
21
src/containers/Categories/components/CategoriesPage.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import PageLayout from "../../../layouts/PageLayout";
|
||||||
|
import CategoriesEntry from "./CategoriesEntry";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
categories: { strCategory: string; strCategoryThumb: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CategoriesPage: FC<Props> = ({ categories }) => (
|
||||||
|
<PageLayout title="Chef's Categories">
|
||||||
|
<ul>
|
||||||
|
{categories.map(({ strCategory, strCategoryThumb }, i) => (
|
||||||
|
<CategoriesEntry
|
||||||
|
key={i}
|
||||||
|
strCategory={strCategory}
|
||||||
|
strCategoryThumb={strCategoryThumb}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { getData } from "../utils/methods";
|
import { PreLoader } from "../../components/PreLoader";
|
||||||
import { CategoryListPage } from "../pages/CategoryListPage";
|
import { getData } from "../../services/api";
|
||||||
import { PreLoader } from "../components/PreLoader";
|
import { CategoriesPage } from "./components/CategoriesPage";
|
||||||
|
|
||||||
export const CategoryListController = () => {
|
export const Categories = () => {
|
||||||
const [categories, setCategories] = useState({ categories: [] });
|
const [categories, setCategories] = useState({ categories: [] });
|
||||||
|
|
||||||
const getCategories = () => {
|
const getCategories = () => {
|
||||||
|
|
@ -12,12 +12,11 @@ export const CategoryListController = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCategories();
|
getCategories();
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return categories.categories.length === 0 ? (
|
return categories.categories.length === 0 ? (
|
||||||
<PreLoader />
|
<PreLoader />
|
||||||
) : (
|
) : (
|
||||||
<CategoryListPage categories={categories.categories} />
|
<CategoriesPage categories={categories.categories} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
21
src/containers/Category/components/CategoryPage.tsx
Normal file
21
src/containers/Category/components/CategoryPage.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { CardEntry } from "../../../components/CardEntry";
|
||||||
|
import PageLayout from "../../../layouts/PageLayout";
|
||||||
|
import { MealSummary } from "../../../types/meal";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
meals: { meals: MealSummary[] };
|
||||||
|
strCategory: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CategoryPage: FC<Props> = ({ meals, strCategory }) => (
|
||||||
|
<PageLayout title={`Chef's ${strCategory} Recipes`}>
|
||||||
|
<ul>
|
||||||
|
<div className="row">
|
||||||
|
{meals.meals.map((meal) => (
|
||||||
|
<CardEntry meal={meal} key={meal.idMeal} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
21
src/containers/Category/index.tsx
Normal file
21
src/containers/Category/index.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { Redirect, useParams } from "react-router-dom";
|
||||||
|
import { getData } from "../../services/api";
|
||||||
|
import { CategoryPage } from "./components/CategoryPage";
|
||||||
|
|
||||||
|
export const Category: FC = () => {
|
||||||
|
const { strCategory } = useParams();
|
||||||
|
const [meals, setMeals] = useState({ meals: [] });
|
||||||
|
|
||||||
|
const getMeals = () => getData(strCategory, setMeals, "filter");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMeals();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return !meals.meals ? (
|
||||||
|
<Redirect to="/404" />
|
||||||
|
) : (
|
||||||
|
<CategoryPage meals={meals} strCategory={strCategory} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import React, { useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import PageLayout from "../layouts/PageLayout";
|
import PageLayout from "../../layouts/PageLayout";
|
||||||
import { ContactForm } from "../components/ContactForm";
|
import { ContactForm } from "../../components/ContactForm";
|
||||||
|
|
||||||
export const ContactPage = () => {
|
export const Contact: FC = () => {
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
|
||||||
return isSubmitted ? (
|
return isSubmitted ? (
|
||||||
<div className="container center-align">
|
<div className="container center-align">
|
||||||
<img
|
<img
|
||||||
className="responsive-img"
|
className="responsive-img"
|
||||||
src={require("../images/mail_sent.svg")}
|
src={require("../../images/mail_sent.svg")}
|
||||||
alt="mail_sent"
|
alt="mail_sent"
|
||||||
width="30%"
|
width="30%"
|
||||||
/>
|
/>
|
||||||
23
src/containers/Home/index.tsx
Normal file
23
src/containers/Home/index.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from "react";
|
||||||
|
import { RandomButton } from "../../components/RandomButton";
|
||||||
|
import { buttonURL } from "../../constants";
|
||||||
|
import HeroImage from "../../images/chef.svg";
|
||||||
|
|
||||||
|
export const Home = () => (
|
||||||
|
<section className="container ">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col s12 m6">
|
||||||
|
<h1 className="logo">Chef's Online Cookbook</h1>
|
||||||
|
<RandomButton
|
||||||
|
url={buttonURL}
|
||||||
|
size="large"
|
||||||
|
color="orange darken-2"
|
||||||
|
handleClick={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<picture className="col s12 m6">
|
||||||
|
<img src={HeroImage} alt="hero_image" width="100%" />
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
26
src/containers/Meal/components/MealIngredientList.tsx
Normal file
26
src/containers/Meal/components/MealIngredientList.tsx
Normal 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>
|
||||||
|
);
|
||||||
31
src/containers/Meal/components/MealPage.tsx
Normal file
31
src/containers/Meal/components/MealPage.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import Meal from "../../../types/meal";
|
||||||
|
import { MealIngredientList } from "./MealIngredientList";
|
||||||
|
import { MealPresentation } from "./MealPresentation";
|
||||||
|
import { MealRecipe } from "./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>
|
||||||
|
);
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
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 {
|
const {
|
||||||
mealName,
|
mealName,
|
||||||
imgAddress,
|
imgAddress,
|
||||||
|
|
@ -9,7 +15,6 @@ export const MealPresentation = ({ meal }) => {
|
||||||
mealCategory,
|
mealCategory,
|
||||||
mealArea,
|
mealArea,
|
||||||
isFav,
|
isFav,
|
||||||
handleFavChange,
|
|
||||||
} = meal;
|
} = meal;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
13
src/containers/Meal/components/MealRecipe.tsx
Normal file
13
src/containers/Meal/components/MealRecipe.tsx
Normal 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>
|
||||||
|
);
|
||||||
75
src/containers/Meal/index.tsx
Normal file
75
src/containers/Meal/index.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React, { FC, useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { NotFound } from "../NotFound";
|
||||||
|
import { useFirebase } from "../../services/Firebase";
|
||||||
|
import { useAuth0 } from "../../utils/auth0-spa";
|
||||||
|
import { MealPage } from "./components/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}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NotFound handleClick={getRandomMeal} />
|
||||||
|
);
|
||||||
|
};
|
||||||
9
src/containers/Meal/service.ts
Normal file
9
src/containers/Meal/service.ts
Normal 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);
|
||||||
|
};
|
||||||
32
src/containers/NotFound/index.tsx
Normal file
32
src/containers/NotFound/index.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { RandomButton } from "../../components/RandomButton";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handleClick: (any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NotFound: FC<Props> = ({ handleClick }) => (
|
||||||
|
<div className="container center-align">
|
||||||
|
<div className="row">
|
||||||
|
<h1>Wrong Way!</h1>
|
||||||
|
<div className="col s12 offset-m3 m6">
|
||||||
|
<div className="card hoverable">
|
||||||
|
<div className="card-image">
|
||||||
|
<img
|
||||||
|
className="responsive-img"
|
||||||
|
src="https://images.otstatic.com/prod/26153735/2/large.jpg"
|
||||||
|
alt="404 not found"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card-content">
|
||||||
|
<RandomButton
|
||||||
|
url="/random"
|
||||||
|
handleClick={handleClick}
|
||||||
|
color={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
32
src/containers/Profile/components/ProfilePage.tsx
Normal file
32
src/containers/Profile/components/ProfilePage.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { CardEntry } from "../../../components/CardEntry";
|
||||||
|
import { MealSummary } from "../../../types/meal";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user: any;
|
||||||
|
meals: MealSummary[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfilePage: FC<Props> = ({ user, meals }) => (
|
||||||
|
<div className="container">
|
||||||
|
<div className="row valign-wrapper">
|
||||||
|
<img
|
||||||
|
className="left circle responsive-img"
|
||||||
|
src={user.picture}
|
||||||
|
alt="Avatar"
|
||||||
|
width="15%"
|
||||||
|
/>
|
||||||
|
<h2 className="col s9">{user.name}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<b>Email: </b>
|
||||||
|
{user.email}
|
||||||
|
<h3>Favourites meals</h3>
|
||||||
|
<ul>
|
||||||
|
{meals?.map((meal) => (
|
||||||
|
<CardEntry key={meal.idMeal} meal={meal} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
25
src/containers/Profile/index.tsx
Normal file
25
src/containers/Profile/index.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { PreLoader } from "../../components/PreLoader";
|
||||||
|
import { useFirebase } from "../../services/Firebase";
|
||||||
|
import { useAuth0 } from "../../utils/auth0-spa";
|
||||||
|
import { ProfilePage } from "./components/ProfilePage";
|
||||||
|
|
||||||
|
export const Profile: FC = () => {
|
||||||
|
const { loading, user } = useAuth0();
|
||||||
|
const [favs, setFavs] = useState([]);
|
||||||
|
const db = useFirebase();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
db.getByEmail(user.email).then((res) => {
|
||||||
|
setFavs(res.favs);
|
||||||
|
});
|
||||||
|
}, [db, user.email]);
|
||||||
|
|
||||||
|
return loading || !user ? ( // is caught by PrivateRoute
|
||||||
|
<div className="container center-align">
|
||||||
|
<PreLoader />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ProfilePage user={user} meals={favs} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { SearchResult } from "../components/SearchResult";
|
import BreakfastImage from "../../../images/breakfast.svg";
|
||||||
import PageLayout from "../layouts/PageLayout";
|
import PageLayout from "../../../layouts/PageLayout";
|
||||||
|
import { MealSummary } from "../../../types/meal";
|
||||||
|
import { SearchResult } from "./SearchResult";
|
||||||
|
|
||||||
export const SearchPage = ({ searchString, searchResults }) => {
|
type Props = {
|
||||||
|
searchString: string;
|
||||||
|
searchResults: { meals: MealSummary[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchPage: FC<Props> = ({ searchString, searchResults }) => {
|
||||||
const { meals } = searchResults;
|
const { meals } = searchResults;
|
||||||
return (
|
return (
|
||||||
<PageLayout title={`Results for: ${searchString}`}>
|
<PageLayout title={`Results for: ${searchString}`}>
|
||||||
{meals === null ? (
|
{!meals ? (
|
||||||
<div className="center-align">
|
<div className="center-align">
|
||||||
<p>
|
<p>
|
||||||
No results to display, instead there is a picture of my breakfast.
|
No results to display, instead there is a picture of my breakfast.
|
||||||
</p>
|
</p>
|
||||||
<img
|
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
|
||||||
src={require("../images/breakfast.svg")}
|
|
||||||
alt="Nothing here!"
|
|
||||||
width="70%"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
9
src/containers/Search/components/SearchResult.tsx
Normal file
9
src/containers/Search/components/SearchResult.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { CardEntry } from "../../../components/CardEntry";
|
||||||
|
import { MealSummary } from "../../../types/meal";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
meal: MealSummary;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchResult: FC<Props> = ({ meal }) => <CardEntry meal={meal} />;
|
||||||
12
src/containers/Search/index.tsx
Normal file
12
src/containers/Search/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { MealSummary } from "../../types/meal";
|
||||||
|
import { SearchPage } from "./components/SearchPage";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
searchString: string;
|
||||||
|
searchResults: { meals: MealSummary[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Search: FC<Props> = ({ searchString, searchResults }) => (
|
||||||
|
<SearchPage searchString={searchString} searchResults={searchResults} />
|
||||||
|
);
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useParams, Redirect } from "react-router-dom";
|
|
||||||
import { CategoryPage } from "../pages/CategoryPage";
|
|
||||||
import { getData } from "../utils/methods";
|
|
||||||
|
|
||||||
export const CategoryController = () => {
|
|
||||||
const [meals, setMeals] = useState({ meals: [] });
|
|
||||||
|
|
||||||
const { strCategory } = useParams();
|
|
||||||
|
|
||||||
const getMeals = () => {
|
|
||||||
getData(strCategory, setMeals, "filter");
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getMeals();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return meals.meals === null ? (
|
|
||||||
<Redirect to="/404" />
|
|
||||||
) : (
|
|
||||||
<CategoryPage meals={meals} strCategory={strCategory} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { HomePage } from "../pages/HomePage";
|
|
||||||
|
|
||||||
export const HomeController = ({ buttonUrl }) => {
|
|
||||||
return <HomePage buttonUrl={buttonUrl} />;
|
|
||||||
};
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Switch, Route, Redirect } from "react-router-dom";
|
|
||||||
import { SearchController } from "../controllers/SearchController";
|
|
||||||
import { HomeController } from "../controllers/HomeController";
|
|
||||||
import { MealController } from "../controllers/MealController";
|
|
||||||
import { CategoryController } from "../controllers/CategoryController";
|
|
||||||
import { CategoryListController } from "../controllers/CategoryListController";
|
|
||||||
import { ProfileController } from "../controllers/ProfileController";
|
|
||||||
import { ContactPage } from "../pages/Contact";
|
|
||||||
import { NotFoundPage } from "../pages/NotFoundPage";
|
|
||||||
import { PrivateRoute } from "../components/PrivateRoute";
|
|
||||||
|
|
||||||
const MainRouter = ({
|
|
||||||
buttonUrl,
|
|
||||||
meal,
|
|
||||||
getMeal,
|
|
||||||
getRandomMeal,
|
|
||||||
searchString,
|
|
||||||
searchResults,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/">
|
|
||||||
<HomeController buttonUrl={buttonUrl} />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<PrivateRoute exact path="/profile" component={ProfileController} />
|
|
||||||
|
|
||||||
<Route exact path={buttonUrl}>
|
|
||||||
<MealController
|
|
||||||
meal={meal}
|
|
||||||
getMeal={getMeal}
|
|
||||||
getRandomMeal={getRandomMeal}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route exact path="/categories">
|
|
||||||
<CategoryListController />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/categories/:strCategory/">
|
|
||||||
<CategoryController />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route exact path="/search">
|
|
||||||
<SearchController
|
|
||||||
searchString={searchString}
|
|
||||||
searchResults={searchResults}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/contact">
|
|
||||||
<ContactPage />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/404">
|
|
||||||
<NotFoundPage handleClick={getRandomMeal} />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/:id">
|
|
||||||
<MealController
|
|
||||||
meal={meal}
|
|
||||||
getMeal={getMeal}
|
|
||||||
getRandomMeal={getRandomMeal}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="*">
|
|
||||||
<Redirect to="/404" />
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MainRouter;
|
|
||||||
|
|
@ -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();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
|
||||||
import { PreLoader } from "../components/PreLoader";
|
|
||||||
import { ProfilePage } from "../pages/ProfilePage";
|
|
||||||
import { useFirebase } from "../services/Firebase";
|
|
||||||
|
|
||||||
export const ProfileController = () => {
|
|
||||||
const { loading, user } = useAuth0();
|
|
||||||
const [favs, setFavs] = useState([]);
|
|
||||||
const db = useFirebase();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
db.getByEmail(user.email).then((res) => {
|
|
||||||
setFavs(res.favs);
|
|
||||||
});
|
|
||||||
}, [db, user.email]);
|
|
||||||
|
|
||||||
return loading || !user ? ( // is catched by PrivateRoute
|
|
||||||
<div className="container center-align">
|
|
||||||
<PreLoader />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ProfilePage user={user} data={favs} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { SearchPage } from "../pages/SearchPage";
|
|
||||||
|
|
||||||
export const SearchController = ({ searchString, searchResults }) => {
|
|
||||||
if (searchResults === null) {
|
|
||||||
searchResults = { meals: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchPage searchString={searchString} searchResults={searchResults} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { App } from "./App.jsx";
|
import {App} from "./App";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import { Auth0Provider } from "./utils/auth0-spa";
|
import { Auth0Provider } from "./utils/auth0-spa";
|
||||||
import history from "./utils/history";
|
import history from "./utils/history";
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { Footer } from "../components/Footer";
|
||||||
import { Navbar } from "../components/Navbar";
|
import { Navbar } from "../components/Navbar";
|
||||||
import { SearchBar } from "../components/SearchBar";
|
import { SearchBar } from "../components/SearchBar";
|
||||||
import { Footer } from "../components/Footer";
|
|
||||||
import { SideNav } from "../components/SideNav";
|
import { SideNav } from "../components/SideNav";
|
||||||
|
|
||||||
|
// TODO FC...
|
||||||
const MainLayout = ({
|
const MainLayout = ({
|
||||||
buttonUrl,
|
buttonUrl,
|
||||||
getRandomMeal,
|
getRandomMeal,
|
||||||
|
|
@ -62,6 +63,7 @@ const MainLayout = ({
|
||||||
closeNavClick={closeNavClick}
|
closeNavClick={closeNavClick}
|
||||||
links={links}
|
links={links}
|
||||||
buttonUrl={buttonUrl}
|
buttonUrl={buttonUrl}
|
||||||
|
handleClick={() => {}}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
const PageLayout = ({ title, children }) => {
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<h1 className="logo">{title}</h1>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PageLayout;
|
|
||||||
14
src/layouts/PageLayout.tsx
Normal file
14
src/layouts/PageLayout.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageLayout: FC<Props> = ({ title, children }) => (
|
||||||
|
<div className="container">
|
||||||
|
<h1 className="logo">{title}</h1>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PageLayout;
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import PageLayout from "../layouts/PageLayout";
|
|
||||||
import CategoryEntry from "../components/CategoryEntry";
|
|
||||||
|
|
||||||
export const CategoryListPage = ({ categories }) => {
|
|
||||||
return (
|
|
||||||
<PageLayout title="Chef's Categories">
|
|
||||||
<ul>
|
|
||||||
{categories.map((category, i) => (
|
|
||||||
<CategoryEntry key={i} category={category} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</PageLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import PageLayout from "../layouts/PageLayout";
|
|
||||||
|
|
||||||
export const CategoryPage = ({ meals, strCategory }) => {
|
|
||||||
return (
|
|
||||||
<PageLayout title={`Chef's ${strCategory} Recipes`}>
|
|
||||||
<ul>
|
|
||||||
<div className="row">
|
|
||||||
{meals.meals.map((meal, i) => (
|
|
||||||
<li key={i}>
|
|
||||||
{/* <CardEntry item={meal} /> */}
|
|
||||||
<Link to={`/${meal.idMeal}`}>
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<div className="card hoverable">
|
|
||||||
<div className="card-image">
|
|
||||||
<img src={meal.strMealThumb} alt={meal.strMeal} />
|
|
||||||
</div>
|
|
||||||
<div className="card-content">
|
|
||||||
<h4>{meal.strMeal}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
</PageLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { RandomButton } from "../components/RandomButton";
|
|
||||||
|
|
||||||
export const HomePage = ({ buttonUrl }) => {
|
|
||||||
return (
|
|
||||||
<div className="container ">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<h1 className="logo">Chef's Online Cookbook</h1>
|
|
||||||
<RandomButton url={buttonUrl} size="large" color="orange darken-2" />
|
|
||||||
</div>
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<img
|
|
||||||
src={require("../images/chef.svg")}
|
|
||||||
alt="hero_image"
|
|
||||||
width="100%"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { RandomButton } from "../components/RandomButton";
|
|
||||||
|
|
||||||
export const NotFoundPage = () => {
|
|
||||||
return (
|
|
||||||
<div className="container center-align">
|
|
||||||
<div className="row">
|
|
||||||
<h1>Wrong Way!</h1>
|
|
||||||
<div className="col s12 offset-m3 m6">
|
|
||||||
<div className="card hoverable">
|
|
||||||
<div className="card-image">
|
|
||||||
<img
|
|
||||||
className="responsive-img"
|
|
||||||
src="https://images.otstatic.com/prod/26153735/2/large.jpg"
|
|
||||||
alt="404 not found"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="card-content">
|
|
||||||
<RandomButton url="/random" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { CardEntry } from "../components/CardEntry";
|
|
||||||
|
|
||||||
export const ProfilePage = ({ user, data }) => {
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<div className="row valign-wrapper">
|
|
||||||
<img
|
|
||||||
className="left circle responsive-img"
|
|
||||||
src={user.picture}
|
|
||||||
alt="Avatar"
|
|
||||||
width="15%"
|
|
||||||
/>
|
|
||||||
<h2 className="col s9">{user.name}</h2>
|
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
<b>Email: </b>
|
|
||||||
{user.email}
|
|
||||||
<h3>Favourites meals</h3>
|
|
||||||
<ul>{data && data.map((d, i) => <CardEntry key={i} item={d} />)}</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
1
src/react-app-env.d.ts
vendored
Normal file
1
src/react-app-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="react-scripts" />
|
||||||
57
src/router/AppRouter.tsx
Normal file
57
src/router/AppRouter.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Redirect, Route, Switch } from "react-router-dom";
|
||||||
|
import { buttonURL } from "../constants";
|
||||||
|
import { Categories } from "../containers/Categories";
|
||||||
|
import { Category } from "../containers/Category";
|
||||||
|
import { Home } from "../containers/Home";
|
||||||
|
import { Meal } from "../containers/Meal";
|
||||||
|
import { Profile } from "../containers/Profile";
|
||||||
|
import { Search } from "../containers/Search";
|
||||||
|
import { Contact } from "../containers/Contact";
|
||||||
|
import { NotFound } from "../containers/NotFound";
|
||||||
|
import { PrivateRoute } from "./PrivateRoute";
|
||||||
|
|
||||||
|
//TODO: remove state from router move to containers
|
||||||
|
|
||||||
|
const AppRouter = ({ getRandomMeal, searchString, searchResults }) => (
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/">
|
||||||
|
<Home />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<PrivateRoute exact path="/profile" component={Profile} />
|
||||||
|
|
||||||
|
<Route exact path={buttonURL}>
|
||||||
|
<Meal />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route exact path="/categories">
|
||||||
|
<Categories />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/categories/:strCategory/">
|
||||||
|
<Category />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route exact path="/search">
|
||||||
|
<Search searchString={searchString} searchResults={searchResults} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/contact">
|
||||||
|
<Contact />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/404">
|
||||||
|
<NotFound handleClick={getRandomMeal} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/:id">
|
||||||
|
<Meal />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="*">
|
||||||
|
<Redirect to="/404" />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AppRouter;
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
import React, { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
import { useAuth0 } from "../utils/auth0-spa";
|
||||||
|
|
||||||
|
// TODO use FC and props
|
||||||
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
||||||
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// catches infinite loading when no user is logged in
|
||||||
if (loading || isAuthenticated) {
|
if (loading || isAuthenticated) {
|
||||||
// catches infinite loading when no user is logged in
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fn = async () => {
|
const fn = async () =>
|
||||||
await loginWithRedirect({
|
await loginWithRedirect({
|
||||||
appState: { targetUrl: path }
|
appState: { targetUrl: path },
|
||||||
});
|
});
|
||||||
};
|
|
||||||
fn();
|
fn();
|
||||||
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
||||||
|
|
||||||
const render = props =>
|
const render = (props) => (isAuthenticated ? <Component {...props} /> : null);
|
||||||
isAuthenticated === true ? <Component {...props} /> : null;
|
|
||||||
|
|
||||||
return <Route path={path} render={render} {...rest} />;
|
return <Route path={path} render={render} {...rest} />;
|
||||||
};
|
};
|
||||||
18
src/router/Router.tsx
Normal file
18
src/router/Router.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { Router as RouterOriginal, useLocation } from "react-router-dom";
|
||||||
|
import history from "../utils/history";
|
||||||
|
|
||||||
|
export const Router: FC = ({ children }) => (
|
||||||
|
<RouterOriginal history={history}>
|
||||||
|
<ScrollToTop />
|
||||||
|
{children}
|
||||||
|
</RouterOriginal>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ScrollToTop: FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [location.pathname]);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
2
src/router/index.ts
Normal file
2
src/router/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AppRouter from "./AppRouter";
|
||||||
|
export { AppRouter };
|
||||||
|
|
@ -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/";
|
const ROOT = "https://www.themealdb.com/api/json/v1/1/";
|
||||||
if (option === null) {
|
if (!option) {
|
||||||
return `${ROOT}${keyword}.php`;
|
return `${ROOT}${keyword}.php`;
|
||||||
} else if (option === "filter") {
|
} else if (option === "filter") {
|
||||||
return `${ROOT}${option}.php?c=${keyword}`;
|
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);
|
const URI = createURI(keyword, option);
|
||||||
fetch(URI)
|
fetch(URI)
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.catch(error => console.info(error + "url:" + URI))
|
.catch((error) => console.warn(error + "url:" + URI))
|
||||||
.then(data => set(data));
|
.then((data) => set(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const upFirstChar = lower => {
|
|
||||||
return lower.replace(/^\w/, c => c.toUpperCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addToFavourites = () => {};
|
|
||||||
14
src/types/meal.ts
Normal file
14
src/types/meal.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export default interface Meal {
|
||||||
|
mealName: string;
|
||||||
|
imgAddress: string;
|
||||||
|
videoAddress: string;
|
||||||
|
mealCategory: string;
|
||||||
|
mealArea: string;
|
||||||
|
isFav: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MealSummary {
|
||||||
|
idMeal: string;
|
||||||
|
strMeal: string;
|
||||||
|
strMealThumb: string;
|
||||||
|
}
|
||||||
2
src/utils/methods.ts
Normal file
2
src/utils/methods.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const upFirstChar = (lower: string): string =>
|
||||||
|
lower.replace(/^\w/, (c) => c.toUpperCase());
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import React, { useMemo, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Router as RouterOriginal,
|
|
||||||
useParams,
|
|
||||||
useLocation,
|
|
||||||
useHistory,
|
|
||||||
useRouteMatch
|
|
||||||
} from "react-router-dom";
|
|
||||||
import queryString from "query-string";
|
|
||||||
|
|
||||||
import { createBrowserHistory } from "history";
|
|
||||||
export const history = createBrowserHistory();
|
|
||||||
|
|
||||||
export const Router = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<RouterOriginal history={history}>
|
|
||||||
<ScrollToTop />
|
|
||||||
{children}
|
|
||||||
</RouterOriginal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ScrollToTop = () => {
|
|
||||||
const location = useLocation();
|
|
||||||
useEffect(() => {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}, [location.pathname]);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRouter = () => {
|
|
||||||
const params = useParams();
|
|
||||||
const location = useLocation();
|
|
||||||
const history = useHistory();
|
|
||||||
const match = useRouteMatch();
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return {
|
|
||||||
push: history.push,
|
|
||||||
replace: history.replace,
|
|
||||||
pathname: location.pathname,
|
|
||||||
query: {
|
|
||||||
...queryString.parse(location.search),
|
|
||||||
...params
|
|
||||||
},
|
|
||||||
match,
|
|
||||||
location,
|
|
||||||
history
|
|
||||||
};
|
|
||||||
}, [params, match, location, history]);
|
|
||||||
};
|
|
||||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
yarn.lock
34
yarn.lock
|
|
@ -2099,6 +2099,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.47.tgz#6eca42c3462821309b26edbc2eff0db1e37ab9bc"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.47.tgz#6eca42c3462821309b26edbc2eff0db1e37ab9bc"
|
||||||
integrity sha512-R6851wTjN1YJza8ZIeX6puNBSi/ZULHVh4WVleA7q256l+cP2EtXnKbO455fTs2ytQk3dL9qkU+Wh8l/uROdKg==
|
integrity sha512-R6851wTjN1YJza8ZIeX6puNBSi/ZULHVh4WVleA7q256l+cP2EtXnKbO455fTs2ytQk3dL9qkU+Wh8l/uROdKg==
|
||||||
|
|
||||||
|
"@types/node@^14.14.37":
|
||||||
|
version "14.14.37"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
|
||||||
|
integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||||
|
|
@ -2114,11 +2119,25 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
|
||||||
integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==
|
integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==
|
||||||
|
|
||||||
|
"@types/prop-types@*":
|
||||||
|
version "15.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||||
|
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||||
|
|
||||||
"@types/q@^1.5.1":
|
"@types/q@^1.5.1":
|
||||||
version "1.5.4"
|
version "1.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
||||||
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
||||||
|
|
||||||
|
"@types/react@^17.0.3":
|
||||||
|
version "17.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
|
||||||
|
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@0.0.8":
|
"@types/resolve@0.0.8":
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
||||||
|
|
@ -2126,6 +2145,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/scheduler@*":
|
||||||
|
version "0.16.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
||||||
|
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
|
||||||
|
|
||||||
"@types/source-list-map@*":
|
"@types/source-list-map@*":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
||||||
|
|
@ -4217,6 +4241,11 @@ cssstyle@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssom "~0.3.6"
|
cssom "~0.3.6"
|
||||||
|
|
||||||
|
csstype@^3.0.2:
|
||||||
|
version "3.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
|
||||||
|
integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
|
|
@ -11324,6 +11353,11 @@ typedarray@^0.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
|
typescript@^4.2.3:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
|
||||||
|
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
|
||||||
|
|
||||||
unbox-primitive@^1.0.0:
|
unbox-primitive@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f"
|
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue