refactoring. Add MainLayout and MainRouter

This commit is contained in:
Ruidy Nemausat 2020-04-15 09:34:41 +02:00
parent 47c9d5f4d9
commit 2a6f843d36
23 changed files with 309 additions and 249 deletions

View file

@ -1,11 +1,14 @@
# TO DO
- [ ] send message after contact form validation (confirm to sender and msg+info to admin)
- [ ] Local storage of prefeernces
- [ ] Firebase
- [ ] Local storage of preferences
- [ ] Firebase firestore and functions
- [ ] Breadcrumb
- [ ] Cookie bar
- [ ] code cleanup (props and refactoring)
- [ ] Back to top button
- [ ] Close Sidebar at outside click
- [ ] Take a look at some components [here](http://react-materialize.github.io/react-materialize/?path=/story/css-grid--default)
- [ ] Decoupled application and data layers. Abstract such that the front end does not know where the data comes from.
- [ ] Create PageLayout component
- [ ] Use Css-in-Js

View file

@ -1,24 +1,15 @@
import React, { useState } from "react";
import { Router } from "./utils/router";
import { Switch, Route, Redirect } from "react-router-dom";
import { PreLoader } from "./components/PreLoader";
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";
import MainLayout from "./layouts/MainLayout";
import MainRouter from "./controllers/MainRouter";
export const App = () => {
const { loading } = useAuth0();
@ -85,6 +76,7 @@ export const App = () => {
},
],
};
const [meal, setMeal] = useState(mealDef);
const getMeal = (id) => {
@ -108,114 +100,33 @@ export const App = () => {
const buttonUrl = "/random";
const [showNav, setShowNav] = useState(false);
const openNavClick = (ev) => {
ev.preventDefault();
setShowNav(true);
document.addEventListener("keydown", handleEscKey);
// document.addEventListener("click", handleOutsideClick);
};
const closeNavClick = (ev) => {
ev.preventDefault();
setShowNav(false);
document.removeEventListener("keydown", handleEscKey);
};
const handleEscKey = (ev) => {
if (ev.key === "Escape") {
setShowNav(false);
}
};
// const handleOutsideClick = ev => {
// console.log(ev);
// };
const links = ["categories", "contact"];
return loading ? (
<div className="container center-align valign-wrapper">
<PreLoader />
</div>
) : (
<>
<Router history={history}>
<header>
<Navbar
handleClick={getRandomMeal}
buttonUrl={buttonUrl}
openNavClick={openNavClick}
links={links}
/>
<SearchBar
searchString={searchString}
setSearchString={setSearchString}
handleChange={handleChange}
onSubmit={getSearchResults}
setSearchResults={setSearchResults}
/>
<SideNav
showNav={showNav}
closeNavClick={closeNavClick}
links={links}
buttonUrl={buttonUrl}
/>
</header>
<Switch>
<Route exact path="/">
<HomeController buttonUrl={buttonUrl} />
</Route>
<PrivateRoute exact path="/profile">
<ProfileController />
</PrivateRoute>
<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>
<Footer />
</Router>
</>
<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>
);
};

View file

@ -1,8 +1,8 @@
import React from "react";
import { Link } from "react-router-dom";
export const CardEntry = props => {
const { idMeal, strMeal, strMealThumb } = props.item;
export const CardEntry = ({ item }) => {
const { idMeal, strMeal, strMealThumb } = item;
return (
<Link to={`${idMeal}`}>
<li>

View file

@ -2,14 +2,14 @@ import React from "react";
import { Link, useRouteMatch } from "react-router-dom";
const CategoryEntry = ({ category }) => {
const { url } = useRouteMatch();
const {
strCategory,
strCategoryThumb
strCategoryThumb,
// strCategoryDescription
} = category;
const { url } = useRouteMatch();
return (
// <CardEntry item={meal} />
<Link to={`${url}/${strCategory}`}>

View file

@ -3,9 +3,7 @@ import { CopyrightText } from "./CopyrightText";
import { GitHubLink } from "./GitHubLink";
import { FooterLink } from "./FooterLink";
export const Footer = () => {
const links = ["categories", "random", "contact"];
export const Footer = ({ links }) => {
return (
<footer className="page-footer">
<div className="row">

View file

@ -1,7 +1,6 @@
import React from "react";
export const IngredientList = props => {
const { ingredients } = props;
export const IngredientList = ({ ingredients }) => {
return (
<div className="ingredientList">
<table className="striped highlight responsive-table">

View file

@ -3,17 +3,15 @@ import { Link } from "react-router-dom";
export const Logo = () => {
return (
<>
<Link to="/" className="brand-logo">
<img
// className="responsive-img"
src="/logo192.png"
alt="chef's logo"
height="30px"
style={{ position: "relative", top: "5px" }}
/>
<span className="logo orange-text text-accent-4">Chef's</span>
</Link>
</>
<Link to="/" className="brand-logo">
<img
// className="responsive-img"
src="/logo192.png"
alt="chef's logo"
height="30px"
style={{ position: "relative", top: "5px" }}
/>
<span className="logo orange-text text-accent-4">Chef's</span>
</Link>
);
};

View file

@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router-dom";
export const MealPresentation = (props) => {
export const MealPresentation = ({ meal }) => {
const {
mealName,
imgAddress,
@ -10,7 +10,7 @@ export const MealPresentation = (props) => {
mealArea,
isFav,
setIsFav,
} = props.meal;
} = meal;
return (
<div className="row">

View file

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

View file

@ -6,12 +6,13 @@ export const SearchBar = ({
setSearchString,
setSearchResults,
handleChange,
onSubmit
onSubmit,
}) => {
const clearSearchBar = () => {
setSearchString("");
setSearchResults({ meals: [] });
};
return (
<div className="section">
<div className="container">

View file

@ -1,7 +1,6 @@
import React from "react";
import { CardEntry } from "./CardEntry";
export const SearchResult = props => {
const { meal } = props;
export const SearchResult = ({ meal }) => {
return <CardEntry item={meal} />;
};

View file

@ -11,94 +11,92 @@ export const SideNav = ({
closeNavClick,
links,
buttonUrl,
handleClick
handleClick,
}) => {
const { isAuthenticated, user } = useAuth0();
let transformStyle = {
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
transition: "0.5s"
transition: "0.5s",
};
return (
<>
<ul id="slide-out" className="sidenav" style={transformStyle}>
<li>
<div className="user-view" style={{ height: "30vh" }}>
<div className="background">
<img
// className="responsive-img"
style={{
position: "fixed" /* Sit on top of the page content */,
width: "100%" /* Full width (cover the whole page) */,
height: "30vh" /* Full width (cover the whole page) */,
top: "0",
left: "0",
right: "0",
bottom: "0",
backgroundColor:
"rgba(0,0,0,0.5)" /* Black background with opacity */,
zIndex:
"2" /* Specify a stack order in case you're using a different order for other elements */
// cursor: "pointer" /* Add a pointer on hover */
}}
src={require("../images/special_event.svg")}
alt="sidenav_background"
/>
</div>
<i className="material-icons right" onClick={closeNavClick}>
close
</i>
{isAuthenticated ? (
<Link to="/profile">
<img className="circle" src={user.picture} alt="user_avatar" />
<span className="white-text name">{user.name}</span>
<span className="white-text email">{user.email}</span>
</Link>
) : (
// <Link to="/profile">
<img
className="circle"
src={require("../images/chef.svg")}
alt="user_avatar"
/>
// </Link>
)}
<ul id="slide-out" className="sidenav" style={transformStyle}>
<li>
<div className="user-view" style={{ height: "30vh" }}>
<div className="background">
<img
// className="responsive-img"
style={{
position: "fixed" /* Sit on top of the page content */,
width: "100%" /* Full width (cover the whole page) */,
height: "30vh" /* Full width (cover the whole page) */,
top: "0",
left: "0",
right: "0",
bottom: "0",
backgroundColor:
"rgba(0,0,0,0.5)" /* Black background with opacity */,
zIndex:
"2" /* Specify a stack order in case you're using a different order for other elements */,
// cursor: "pointer" /* Add a pointer on hover */
}}
src={require("../images/special_event.svg")}
alt="sidenav_background"
/>
</div>
</li>
<li>
<RandomButton
handleClick={handleClick}
url={buttonUrl}
size="small"
color="orange darken-2"
/>
</li>
<li>
<Link to="#">
{!isAuthenticated ? (
<LogInButton color="orange lighten-1" />
) : (
<LogOutButton />
)}
</Link>
</li>
<i className="material-icons right" onClick={closeNavClick}>
close
</i>
<li>
<div className="divider"></div>
</li>
<li>
<Link to="#" className="subheader">
Navigation
</Link>
</li>
{links.map((link, i) => (
<FooterLink key={i} link={link} />
))}
{isAuthenticated && <FooterLink link="profile" />}
</ul>
</>
{isAuthenticated ? (
<Link to="/profile">
<img className="circle" src={user.picture} alt="user_avatar" />
<span className="white-text name">{user.name}</span>
<span className="white-text email">{user.email}</span>
</Link>
) : (
// <Link to="/profile">
<img
className="circle"
src={require("../images/chef.svg")}
alt="user_avatar"
/>
// </Link>
)}
</div>
</li>
<li>
<RandomButton
handleClick={handleClick}
url={buttonUrl}
size="small"
color="orange darken-2"
/>
</li>
<li>
<Link to="#">
{!isAuthenticated ? (
<LogInButton color="orange lighten-1" />
) : (
<LogOutButton />
)}
</Link>
</li>
<li>
<div className="divider"></div>
</li>
<li>
<Link to="#" className="subheader">
Navigation
</Link>
</li>
{links.map((link, i) => (
<FooterLink key={i} link={link} />
))}
{isAuthenticated && <FooterLink link="profile" />}
</ul>
);
};

View file

@ -3,7 +3,7 @@ import { useParams, Redirect } from "react-router-dom";
import { CategoryPage } from "../pages/CategoryPage";
import { getData } from "../utils/methods";
export const CategoryController = props => {
export const CategoryController = () => {
const [meals, setMeals] = useState({ meals: [] });
const { strCategory } = useParams();
@ -17,13 +17,9 @@ export const CategoryController = props => {
// eslint-disable-next-line
}, []);
return (
<>
{meals.meals === null ? (
<Redirect to="/404" />
) : (
<CategoryPage meals={meals} strCategory={strCategory} />
)}
</>
return meals.meals === null ? (
<Redirect to="/404" />
) : (
<CategoryPage meals={meals} strCategory={strCategory} />
);
};

View file

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

View file

@ -0,0 +1,80 @@
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">
<ProfileController />
</PrivateRoute>
<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;

View file

@ -4,7 +4,6 @@ import { useAuth0 } from "../utils/auth0-spa";
import { MealPage } from "../pages/MealPage";
import { NotFoundPage } from "../pages/NotFoundPage";
import { useFirebase } from "../services/Firebase";
// import Firebase from "../data/Firebase";
export const MealController = ({ meal, getMeal, getRandomMeal }) => {
const { user, isAuthenticated } = useAuth0();

View file

@ -1,5 +1,4 @@
import React from "react";
import { SearchPage } from "../pages/SearchPage";
export const SearchController = ({ searchString, searchResults }) => {

View file

@ -0,0 +1,75 @@
import React, { useState } from "react";
import { Navbar } from "../components/Navbar";
import { SearchBar } from "../components/SearchBar";
import { Footer } from "../components/Footer";
import { SideNav } from "../components/SideNav";
const MainLayout = ({
buttonUrl,
getRandomMeal,
handleChange,
searchString,
setSearchString,
getSearchResults,
setSearchResults,
children,
}) => {
const [showNav, setShowNav] = useState(false);
const links = ["categories", "contact"];
const footerLinks = [...links, "random"];
const openNavClick = (ev) => {
ev.preventDefault();
setShowNav(true);
document.addEventListener("keydown", handleEscKey);
// document.addEventListener("click", handleOutsideClick);
};
const closeNavClick = (ev) => {
ev.preventDefault();
setShowNav(false);
document.removeEventListener("keydown", handleEscKey);
};
const handleEscKey = (ev) => {
if (ev.key === "Escape") {
setShowNav(false);
}
};
// const handleOutsideClick = ev => {
// console.log(ev);
// };
return (
<>
<header>
<Navbar
handleClick={getRandomMeal}
buttonUrl={buttonUrl}
openNavClick={openNavClick}
links={links}
/>
<SearchBar
searchString={searchString}
setSearchString={setSearchString}
handleChange={handleChange}
onSubmit={getSearchResults}
setSearchResults={setSearchResults}
/>
<SideNav
showNav={showNav}
closeNavClick={closeNavClick}
links={links}
buttonUrl={buttonUrl}
/>
</header>
{children}
<Footer links={[...links, "random"]} />
</>
);
};
export default MainLayout;

View file

@ -26,7 +26,6 @@ export const CategoryPage = ({ meals, strCategory }) => {
))}
</div>
</ul>
}
</div>
);
};

View file

@ -1,5 +1,7 @@
import { createContext, useContext } from "react";
// create a Firebase context to make state available anywhere in the App.
const FirebaseContext = createContext(null);
export const useFirebase = () => useContext(FirebaseContext);
export default FirebaseContext;

View file

@ -13,6 +13,8 @@ const CONFIG = {
measurementId: config.measurementId,
};
// Firebase initializes the Application and provides method to interact with
// Firebase services as auth and firestore.
export default class Firebase {
constructor() {
app.initializeApp(CONFIG);

View file

@ -1,3 +1,5 @@
// This file centralize all Firebase related exports
import Firebase from "./firebase";
import FirebaseContext, { useFirebase } from "./context";

View file

@ -1,8 +1,8 @@
// This must be set on the server using express
// This must be set on the server using or firebase functions
import { createTransport } from "nodemailer";
import { mailPassword } from "./secret";
import { mailAdress, mailPassword } from "./secret";
const myMail = "ruidy.nemausat@gmail.com";
const myMail = mailAdress;
const myPass = mailPassword;
const handleMail = (mailTo, subject, text) => {
@ -10,18 +10,18 @@ const handleMail = (mailTo, subject, text) => {
service: "gmail",
auth: {
user: myMail,
pass: myPass
}
pass: myPass,
},
});
let mailOptions = {
from: myMail,
to: mailTo,
subject: subject,
text: text
text: text,
};
transporter.sendMail(mailOptions, function(error, info) {
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {