decoupling views and controllers

This commit is contained in:
Ruidy Nemausat 2020-02-10 14:09:58 +01:00
parent df86b94dd6
commit c20a7330c8
23 changed files with 290 additions and 239 deletions

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ee6e73" />
<meta name="theme-color" content="#ff6d00" />
<meta name="description" content="Online Meal Planner | Chef's" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

View file

@ -25,6 +25,6 @@
],
"start_url": "/",
"display": "standalone",
"theme_color": "#ee6e73",
"theme_color": "#ff6d00",
"background_color": "#ffffff"
}
}

View file

@ -1,29 +1,29 @@
import React, { useState } from "react";
import { Router } from "../utils/router";
import { Router } from "./utils/router";
import { Switch, Route, Redirect } from "react-router-dom";
import { useAuth0 } from "../utils/auth0-spa";
import { Home } from "./Home";
import { Meal } from "./Meal";
import { SearchPage } from "./Search";
import { CategoryListPage } from "./CategoryList";
import { CategoryPage } from "./Category";
import { ContactPage } from "./Contact";
import { NotFoundPage } from "./NotFound";
import { Navbar } from "../components/Navbar";
import { SearchBar } from "../components/SearchBar";
import { Footer } from "../components/Footer";
import { getData } from "../utils/methods";
import history from "../utils/history";
import { Profile } from "./Profile";
import { PrivateRoute } from "../components/PrivateRoute";
import { PreLoader } from "../components/PreLoader";
import { SideNav } from "../components/SideNav";
import "../index.css";
import { useAuth0 } from "./utils/auth0-spa";
import { SearchController } from "./controllers/SearchController";
import { ContactPage } from "./pages/Contact";
import { NotFoundPage } from "./pages/NotFoundPage";
import { Navbar } from "./components/Navbar";
import { SearchBar } from "./components/SearchBar";
import { Footer } from "./components/Footer";
import { getData } from "./utils/methods";
import history from "./utils/history";
import { ProfileController } from "./controllers/ProfileController";
import { PrivateRoute } from "./components/PrivateRoute";
import { PreLoader } from "./components/PreLoader";
import { SideNav } from "./components/SideNav";
import "./index.css";
import { HomeController } from "./controllers/HomeController";
import { MealController } from "./controllers/MealController";
import { CategoryListController } from "./controllers/CategoryListController";
import { CategoryController } from "./controllers/CategoryController";
export const App = () => {
const { loading } = useAuth0();
const [searchString, setSearchString] = useState("");
const [categories, setCategories] = useState({ categories: [] });
const [searchResults, setSearchResults] = useState({ meals: [] });
// Default meal object. TODO: Find a better alternative
const mealDef = {
@ -36,7 +36,7 @@ export const App = () => {
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"),
strMealThumb: require("./images/breakfast.svg"),
// "https://www.themealdb.com/images/media/meals/vvtvtr1511180578.jpg",
strTags: null,
strYoutube: "#",
@ -87,16 +87,12 @@ export const App = () => {
};
const [meal, setMeal] = useState(mealDef);
const getRandomMeal = () => {
getData("random", setMeal);
};
const getMeal = id => {
getData(id, setMeal, "lookup");
};
const getCategories = () => {
getData("categories", setCategories);
const getRandomMeal = () => {
getData("random", setMeal);
};
const getSearchResults = e => {
@ -167,39 +163,31 @@ export const App = () => {
<Switch>
<Route exact path="/">
<Home buttonUrl={buttonUrl} />
<HomeController buttonUrl={buttonUrl} />
</Route>
<PrivateRoute exact path="/profile">
<Profile />
<ProfileController />
</PrivateRoute>
<Route exact path={buttonUrl}>
{meal !== undefined && meal.meals !== null ? (
<Meal meal={meal} getMeal={getRandomMeal} />
) : (
<NotFoundPage handleClick={getRandomMeal} />
)}
<MealController
meal={meal}
getMeal={getMeal}
getRandomMeal={getRandomMeal}
/>
</Route>
<Route exact path="/categories">
<CategoryListPage
items={categories}
getCategories={getCategories}
/>
<CategoryListController />
</Route>
<Route path="/categories/:strCategory/">
<CategoryPage
getData={getData}
getMeal={getMeal}
setMeal={setMeal}
meal={meal}
/>
<CategoryController />
</Route>
<Route exact path="/search">
<SearchPage
<SearchController
searchString={searchString}
searchResults={searchResults}
/>
@ -214,11 +202,11 @@ export const App = () => {
</Route>
<Route path="/:idMeal">
{meal !== undefined && meal.meals !== null ? (
<Meal meal={meal} getMeal={getMeal} />
) : (
<NotFoundPage handleClick={getRandomMeal} />
)}
<MealController
meal={meal}
getMeal={getMeal}
getRandomMeal={getRandomMeal}
/>
</Route>
<Route path="*">

View file

@ -6,15 +6,13 @@ export const CardEntry = props => {
return (
<Link to={`${idMeal}`}>
<li>
<div className="row">
<div className="col s12 m6">
<div className="card hoverable">
<div className="card-image">
<img src={strMealThumb} alt={strMeal} />
</div>
<div className="card-content">
<h4>{strMeal}</h4>
</div>
<div className="col s12 m6">
<div className="card hoverable">
<div className="card-image">
<img src={strMealThumb} alt={strMeal} />
</div>
<div className="card-content">
<h4>{strMeal}</h4>
</div>
</div>
</div>

View file

@ -7,10 +7,9 @@ import { FooterLink } from "./FooterLink";
import { LogInButton } from "./LogInButton";
import { LogOutButton } from "./LogOutButton";
export const Navbar = props => {
export const Navbar = ({ openNavClick, links, buttonUrl, handleClick }) => {
const { isAuthenticated } = useAuth0();
const { openNavClick, links } = props;
return (
<div className="navbar-fixed">
<nav>
@ -26,8 +25,8 @@ export const Navbar = props => {
)}
<li>
<RandomButton
handleClick={props.handleClick}
url={props.buttonUrl}
handleClick={handleClick}
url={buttonUrl}
size="small"
color="orange darken-2"
/>

View file

@ -6,8 +6,13 @@ import { FooterLink } from "./FooterLink";
import { LogInButton } from "./LogInButton";
import { LogOutButton } from "./LogOutButton";
export const SideNav = props => {
const { showNav, closeNavClick, links } = props;
export const SideNav = ({
showNav,
closeNavClick,
links,
buttonUrl,
handleClick
}) => {
const { isAuthenticated, user } = useAuth0();
let transformStyle = {
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
@ -65,8 +70,8 @@ export const SideNav = props => {
<li>
<RandomButton
handleClick={props.handleClick}
url={props.buttonUrl}
handleClick={handleClick}
url={buttonUrl}
size="small"
color="orange darken-2"
/>

View file

@ -0,0 +1,29 @@
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 = props => {
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} />
)}
</>
);
};

View file

@ -0,0 +1,23 @@
import React, { useEffect, useState } from "react";
import { getData } from "../utils/methods";
import { CategoryListPage } from "../pages/CategoryListPage";
import { PreLoader } from "../components/PreLoader";
export const CategoryListController = () => {
const [categories, setCategories] = useState({ categories: [] });
const getCategories = () => {
getData("categories", setCategories);
};
useEffect(() => {
getCategories();
// eslint-disable-next-line
}, []);
return categories.categories.length === 0 ? (
<PreLoader />
) : (
<CategoryListPage categories={categories.categories} />
);
};

View file

@ -0,0 +1,7 @@
import React from "react";
import { HomePage } from "../pages/HomePage";
export const HomeController = ({ buttonUrl }) => {
return <HomePage buttonUrl={buttonUrl} />;
};

View file

@ -0,0 +1,52 @@
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import { MealPage } from "../pages/MealPage";
import { NotFoundPage } from "../pages/NotFoundPage";
export const MealController = ({ meal, getMeal, getRandomMeal }) => {
const { idMeal } = useParams();
useEffect(() => {
idMeal === undefined ? getRandomMeal() : getMeal(idMeal);
// eslint-disable-next-line
}, []);
const mealItem = meal.meals[0];
const {
strMeal,
strMealThumb,
strYoutube,
strCategory,
strArea,
strInstructions
} = mealItem;
const item = {
mealName: strMeal,
imgAddress: strMealThumb,
videoAddress: strYoutube,
mealCategory: strCategory,
mealArea: strArea
};
let ingredientList = [];
var i;
for (i = 1; i <= 20; i++) {
var strIng = `strIngredient${i}`;
var 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

@ -0,0 +1,16 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
import { PreLoader } from "../components/PreLoader";
import { ProfilePage } from "../pages/ProfilePage";
export const ProfileController = () => {
const { loading, user } = useAuth0();
return loading || !user ? ( // is catched by PrivateRoute
<div className="container center-align">
<PreLoader />
</div>
) : (
<ProfilePage user={user} />
);
};

View file

@ -0,0 +1,13 @@
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} />
);
};

View file

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { App } from "./pages/App.jsx";
import { App } from "./App.jsx";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./utils/auth0-spa";
import history from "./utils/history";

View file

@ -1,48 +0,0 @@
import React, { useEffect, useState } from "react";
import { useParams, Link, Redirect } from "react-router-dom";
export const CategoryPage = props => {
const [meals, setMeals] = useState({ meals: [] });
const { getData } = props;
const { strCategory } = useParams();
const getMeals = () => {
getData(strCategory, setMeals, "filter");
};
useEffect(() => {
getMeals();
// eslint-disable-next-line
}, []);
return (
<div className="container">
<h1 className="logo">Chef's {strCategory} Recipes</h1>
{meals.meals === null ? (
<Redirect to="/404" />
) : (
<ul>
{meals.meals.map((meal, i) => (
<li key={i}>
{/* <CardEntry item={meal} /> */}
<Link to={`/${meal.idMeal}`}>
<div className="row">
<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>
</div>
</Link>
</li>
))}
</ul>
)}
</div>
);
};

View file

@ -1,15 +1,7 @@
import React, { useEffect } from "react";
import React from "react";
import CategoryEntry from "../components/CategoryEntry";
export const CategoryListPage = ({ items, getCategories }) => {
const categories = items.categories;
// const { getCategories } = props;
useEffect(() => {
getCategories();
// eslint-disable-next-line
}, []);
export const CategoryListPage = ({ categories }) => {
return (
<div className="container">
<h1 className="logo">Chef's Categories</h1>

View file

@ -0,0 +1,32 @@
import React from "react";
import { Link } from "react-router-dom";
export const CategoryPage = ({ meals, strCategory }) => {
return (
<div className="container">
<h1 className="logo">Chef's {strCategory} Recipes</h1>
<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>
}
</div>
);
};

View file

@ -1,28 +0,0 @@
import React from "react";
import { RandomButton } from "../components/RandomButton";
export const Home = ({ buttonUrl }) => {
return (
<div className="section ">
<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/healthy_options.svg")}
alt="hero_image"
width="100%"
/>
</div>
</div>
</div>
</div>
);
};

22
src/pages/HomePage.jsx Normal file
View file

@ -0,0 +1,22 @@
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>
);
};

View file

@ -1,58 +0,0 @@
import React, { useEffect } from "react";
import { MealPresentation } from "../components/MealPresentation";
import { IngredientList } from "../components/IngredientList";
import { Recipe } from "../components/Recipe";
import { useParams } from "react-router-dom";
export const Meal = props => {
const { getMeal } = props;
const { idMeal } = useParams();
useEffect(() => {
idMeal === null ? getMeal() : getMeal(idMeal);
// eslint-disable-next-line
}, []);
const meal = props.meal.meals[0];
const {
strMeal,
strMealThumb,
strYoutube,
strCategory,
strArea,
strInstructions
} = meal;
const item = {
mealName: strMeal,
imgAddress: strMealThumb,
videoAddress: strYoutube,
mealCategory: strCategory,
mealArea: strArea
};
let ingredientList = [];
var i;
for (i = 1; i <= 20; i++) {
var strIng = `strIngredient${i}`;
var strMes = `strMeasure${i}`;
if (meal[strIng] !== "" && meal[strIng] !== null) {
ingredientList.push([meal[strIng], meal[strMes]]);
}
}
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>
);
};

20
src/pages/MealPage.jsx Normal file
View file

@ -0,0 +1,20 @@
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,7 +1,7 @@
import React from "react";
import { RandomButton } from "../components/RandomButton";
export const NotFoundPage = props => {
export const NotFoundPage = () => {
return (
<div className="container center-align">
<div className="row">

View file

@ -1,15 +1,7 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
import { PreLoader } from "../components/PreLoader";
export const Profile = () => {
const { loading, user } = useAuth0();
return loading || !user ? ( // is catched by PrivateRoute
<div className="container center-align">
<PreLoader />
</div>
) : (
export const ProfilePage = ({ user }) => {
return (
<div className="container">
<div className="row">
<img

View file

@ -1,17 +1,12 @@
import React from "react";
import { SearchResult } from "../components/SearchResult";
export const SearchPage = props => {
let { meals } = props.searchResults;
const { searchString } = props;
if (meals === null) {
meals = [];
}
export const SearchPage = ({ searchString, searchResults }) => {
const { meals } = searchResults;
return (
<div className="container">
<h1 className="logo">Results for: {searchString}</h1>
{meals[0] === undefined ? (
{meals === null ? (
<div className="center-align">
<p>
No results to display, instead there is a picture of my breakfast.
@ -24,11 +19,13 @@ export const SearchPage = props => {
<p></p>
</div>
) : (
<ul>
{meals.map((meal, i) => (
<SearchResult key={i} meal={meal} />
))}
</ul>
<div className="row">
<ul>
{meals.map((meal, i) => (
<SearchResult key={i} meal={meal} />
))}
</ul>
</div>
)}
</div>
);