mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-06 02:26:49 +00:00
navbar skeleton
This commit is contained in:
parent
99e61fdd74
commit
0c1e426669
10 changed files with 263 additions and 181 deletions
|
|
@ -97,7 +97,8 @@ Free meal planner for cooks short on ideas! (like me …)
|
|||
|
||||
1. add sidenav on mobile
|
||||
1. send message after contact form validation (confirm to sender and msg+info to admin)
|
||||
1. Breadcrumb
|
||||
1. Design & Breadcrumb
|
||||
1. Cookie bar
|
||||
1. code cleanup (props and refactoring)
|
||||
1. Back to top button
|
||||
1. Take a look at some components [here](http://react-materialize.github.io/react-materialize/?path=/story/css-grid--default)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<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="description" content="Chef's | Meal Planner" />
|
||||
<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" />
|
||||
|
||||
|
|
@ -28,15 +28,9 @@
|
|||
href="https://fonts.googleapis.com/css?family=Marck+Script&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Chef's | Meal Planner</title>
|
||||
<title>Online Meal Planner | Chef's</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var elems = document.querySelectorAll(".sidenav");
|
||||
var instances = M.Sidenav.init(elems, options);
|
||||
});
|
||||
</script> -->
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,39 +1,43 @@
|
|||
import React from "react";
|
||||
import { useInput } from "../utils/inputHook";
|
||||
|
||||
export const ContactForm = ({ handleSubmit }) => {
|
||||
export const ContactForm = ({ onSubmit }) => {
|
||||
// const fields = ["firstname", "lastname", "email", "phone", "message"];
|
||||
|
||||
// const [firstName, setFirstName] = useState("");
|
||||
// const [firstName, setFirstName] = useState("");
|
||||
|
||||
const onSubmit = ev => {
|
||||
const handleSubmit = ev => {
|
||||
ev.preventDefault();
|
||||
handleSubmit(true);
|
||||
onSubmit(true);
|
||||
};
|
||||
|
||||
// const handleChange = ev => {
|
||||
// const { value } = ev.target;
|
||||
// setFirstName(value);
|
||||
// };
|
||||
// const handleChange = ev => {
|
||||
// const { value } = ev.target;
|
||||
// setFirstName(value);
|
||||
// };
|
||||
|
||||
// const { value, bind } = useInput("");
|
||||
const { bind } = useInput("");
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col s12">
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput id="First Name" />
|
||||
</div>
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput id="Last Name" />
|
||||
</div>
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput
|
||||
id="Email"
|
||||
type="email"
|
||||
placeholder="jd@mail.com"
|
||||
id="First Name"
|
||||
// value={firstName}
|
||||
// onChange={handleChange}
|
||||
{...bind}
|
||||
/>
|
||||
</div>
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput id="Phone" placeholder="0123456789" />
|
||||
<ContactFormInput id="Last Name" {...bind} />
|
||||
</div>
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput id="Email" type="email" />
|
||||
</div>
|
||||
<div className="col s12 m6">
|
||||
<ContactFormInput id="Phone" />
|
||||
</div>
|
||||
<div className="col s12">
|
||||
<ContactFormTextArea id="Message" />
|
||||
|
|
@ -45,18 +49,18 @@ export const ContactForm = ({ handleSubmit }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ContactFormInput = ({ id, type = "text" }) => {
|
||||
const ContactFormInput = ({ id, type = "text", value, onChange }) => {
|
||||
return (
|
||||
<div className="input-field">
|
||||
{/* <i class="material-icons prefix">account_circle</i> */}
|
||||
<label for={id}>{id}</label>
|
||||
<label htmlFor={id}>{id}</label>
|
||||
<input
|
||||
className="validate"
|
||||
type={type}
|
||||
id={id}
|
||||
name={id}
|
||||
// value={value}
|
||||
// onChange={onChange}
|
||||
name={value}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -65,7 +69,7 @@ const ContactFormInput = ({ id, type = "text" }) => {
|
|||
const ContactFormTextArea = ({ id }) => {
|
||||
return (
|
||||
<div className="input-field">
|
||||
<label for={id}>{id}</label>
|
||||
<label htmlFor={id}>{id}</label>
|
||||
<textarea
|
||||
className="materialize-textarea validate"
|
||||
rows="12"
|
||||
|
|
@ -85,7 +89,7 @@ const ContactFormSubmit = ({ text }) => {
|
|||
type="submit"
|
||||
name="submit"
|
||||
>
|
||||
<i class="material-icons right">send</i> {text}
|
||||
<i className="material-icons right">send</i> {text}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import { LogOutButton } from "./LogOutButton";
|
|||
export const Navbar = props => {
|
||||
const { isAuthenticated } = useAuth0();
|
||||
const links = ["categories", "contact"];
|
||||
const { openNavClick } = props;
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="navbar-fixed">
|
||||
<nav>
|
||||
<div className="nav-wrapper">
|
||||
<div className="container ">
|
||||
|
|
@ -29,50 +30,17 @@ export const Navbar = props => {
|
|||
</li>
|
||||
<li>{!isAuthenticated ? <LogInButton /> : <LogOutButton />}</li>
|
||||
</ul>
|
||||
<a
|
||||
href="#"
|
||||
data-target="slide-out"
|
||||
class="sidenav-trigger "
|
||||
onClick={openNavClick}
|
||||
>
|
||||
<i class="material-icons">menu</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* <ul id="slide-out" class="sidenav show-on-small">
|
||||
<li>
|
||||
<div class="user-view">
|
||||
<div class="background">
|
||||
<img src="images/office.jpg" />
|
||||
</div>
|
||||
<a href="#user">
|
||||
<img class="circle" src="images/yuna.jpg" />
|
||||
</a>
|
||||
<a href="#name">
|
||||
<span class="white-text name">John Doe</span>
|
||||
</a>
|
||||
<a href="#email">
|
||||
<span class="white-text email">jdandturk@gmail.com</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#!">
|
||||
<i class="material-icons">cloud</i>First Link With Icon
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#!">Second Link</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="divider"></div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="subheader">Subheader</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="waves-effect" href="#!">
|
||||
Third Link With Waves
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="#" data-target="slide-out" class="sidenav-trigger">
|
||||
<i class="material-icons">menu</i>
|
||||
</a> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,28 +4,38 @@ import { Link } from "react-router-dom";
|
|||
export const SearchBar = props => {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="row center-align">
|
||||
<form>
|
||||
<input
|
||||
className="input-field col s10"
|
||||
type="text"
|
||||
name="search"
|
||||
value={props.searchString}
|
||||
placeholder="Search a recipe"
|
||||
onChange={props.handleChange}
|
||||
/>
|
||||
<Link to="/search">
|
||||
<button
|
||||
className="btn-floating waves-effect waves-light"
|
||||
type="submit"
|
||||
name="searchButton"
|
||||
value="Search"
|
||||
onClick={props.onSubmit}
|
||||
>
|
||||
<i className="material-icons right">send</i>
|
||||
</button>
|
||||
</Link>
|
||||
</form>
|
||||
<div className=" nav-wrapper">
|
||||
<div className="row center-align">
|
||||
<form>
|
||||
<div className="input-field col s10">
|
||||
<input
|
||||
className=""
|
||||
id="search"
|
||||
type="search"
|
||||
required
|
||||
name="search"
|
||||
value={props.searchString}
|
||||
placeholder="Search for a recipe"
|
||||
onChange={props.handleChange}
|
||||
/>
|
||||
<label className="label-icon" htmlFor="search">
|
||||
<i className="material-icons">search</i>
|
||||
</label>
|
||||
<i className="material-icons">close</i>
|
||||
</div>
|
||||
<Link to="/search">
|
||||
<button
|
||||
className="btn-floating waves-effect waves-light"
|
||||
type="submit"
|
||||
name="searchButton"
|
||||
value="Search"
|
||||
onClick={props.onSubmit}
|
||||
>
|
||||
<i className="material-icons right">send</i>
|
||||
</button>
|
||||
</Link>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
59
src/components/SideNav.jsx
Normal file
59
src/components/SideNav.jsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
// import { useAuth0 } from "../utils/auth0-spa";
|
||||
// import { Logo } from "./Logo";
|
||||
// import { RandomButton } from "./RandomButton";
|
||||
// import { FooterLink } from "./FooterLink";
|
||||
// import { LogInButton } from "./LogInButton";
|
||||
// import { LogOutButton } from "./LogOutButton";
|
||||
|
||||
export const SideNav = props => {
|
||||
const { showNav } = props;
|
||||
|
||||
let transformStyle = {
|
||||
transform: showNav ? "translateX(0%)" : "translateX(-105%)"
|
||||
};
|
||||
// let sideNavStyle = { width: showNav ? "250px" : "0" };
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul id="slide-out" class="sidenav" style={transformStyle}>
|
||||
<li>
|
||||
<div class="user-view">
|
||||
<div class="background">
|
||||
<img src={require("../images/chef.svg")} />
|
||||
</div>
|
||||
<a href="#user">
|
||||
<img class="circle" src={require("../images/chef.svg")} />
|
||||
</a>
|
||||
<a href="#name">
|
||||
<span class="white-text name">John Doe</span>
|
||||
</a>
|
||||
<a href="#email">
|
||||
<span class="white-text email">jdandturk@gmail.com</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#!">
|
||||
<i class="material-icons">cloud</i>First Link With Icon
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#!">Second Link</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="divider"></div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="subheader">Subheader</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="waves-effect" href="#!">
|
||||
Third Link With Waves
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import { App } from "./App";
|
||||
import { App } from "./pages/App.jsx";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { Auth0Provider } from "./utils/auth0-spa";
|
||||
import history from "./utils/history";
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
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 "./pages/Home";
|
||||
import { Meal } from "./pages/Meal";
|
||||
import { SearchPage } from "./pages/Search";
|
||||
import { CategoryListPage } from "./pages/CategoryList";
|
||||
import { CategoryPage } from "./pages/Category";
|
||||
import { ContactPage } from "./pages/Contact";
|
||||
import { NotFoundPage } from "./pages/NotFound";
|
||||
import { Navbar } from "./components/Navbar";
|
||||
import { SearchBar } from "./components/SearchBar";
|
||||
import { Footer } from "./components/Footer";
|
||||
import "./index.css";
|
||||
import { getData } from "./utils/methods";
|
||||
import history from "./utils/history";
|
||||
import { Profile } from "./pages/Profile";
|
||||
import { PrivateRoute } from "./components/PrivateRoute";
|
||||
import { PreLoader } from "./components/PreLoader";
|
||||
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";
|
||||
|
||||
export const App = () => {
|
||||
const { loading } = useAuth0();
|
||||
|
|
@ -35,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: "#",
|
||||
|
|
@ -109,80 +110,108 @@ export const App = () => {
|
|||
|
||||
const buttonUrl = "/random";
|
||||
|
||||
const [showNav, setShowNav] = useState(false);
|
||||
const openNavClick = ev => {
|
||||
ev.preventDefault();
|
||||
setShowNav(true);
|
||||
document.addEventListener("keydown", handleEscKey);
|
||||
};
|
||||
const closeNavClick = ev => {
|
||||
ev.preventDefault();
|
||||
setShowNav(false);
|
||||
document.removeEventListener("keydown", handleEscKey);
|
||||
};
|
||||
const handleEscKey = ev => {
|
||||
if (ev.key === "Escape") {
|
||||
setShowNav(false);
|
||||
}
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="container center-align valign-wrapper">
|
||||
<PreLoader />
|
||||
</div>
|
||||
) : (
|
||||
<Router history={history}>
|
||||
<header>
|
||||
<Navbar handleClick={getRandomMeal} buttonUrl={buttonUrl} />
|
||||
</header>
|
||||
<SearchBar
|
||||
searchString={searchString}
|
||||
handleChange={handleChange}
|
||||
onSubmit={getSearchResults}
|
||||
/>
|
||||
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Home buttonUrl={buttonUrl} />
|
||||
</Route>
|
||||
|
||||
<PrivateRoute exact path="/profile">
|
||||
<Profile />
|
||||
</PrivateRoute>
|
||||
|
||||
<Route exact path={buttonUrl}>
|
||||
{meal !== undefined && meal.meals !== null ? (
|
||||
<Meal meal={meal} getMeal={getRandomMeal} />
|
||||
) : (
|
||||
<NotFoundPage handleClick={getRandomMeal} />
|
||||
)}
|
||||
</Route>
|
||||
|
||||
<Route exact path="/categories">
|
||||
<CategoryListPage items={categories} getCategories={getCategories} />
|
||||
</Route>
|
||||
|
||||
<Route path="/categories/:strCategory/">
|
||||
<CategoryPage
|
||||
getData={getData}
|
||||
getMeal={getMeal}
|
||||
setMeal={setMeal}
|
||||
meal={meal}
|
||||
<>
|
||||
<Router history={history}>
|
||||
<header>
|
||||
<Navbar
|
||||
handleClick={getRandomMeal}
|
||||
buttonUrl={buttonUrl}
|
||||
openNavClick={openNavClick}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route exact path="/search">
|
||||
<SearchPage
|
||||
<SearchBar
|
||||
searchString={searchString}
|
||||
searchResults={searchResults}
|
||||
handleChange={handleChange}
|
||||
onSubmit={getSearchResults}
|
||||
/>
|
||||
</Route>
|
||||
<SideNav showNav={showNav} />
|
||||
</header>
|
||||
|
||||
<Route path="/contact">
|
||||
<ContactPage />
|
||||
</Route>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Home buttonUrl={buttonUrl} />
|
||||
</Route>
|
||||
|
||||
<Route path="/404">
|
||||
<NotFoundPage handleClick={getRandomMeal} />
|
||||
</Route>
|
||||
<PrivateRoute exact path="/profile">
|
||||
<Profile />
|
||||
</PrivateRoute>
|
||||
|
||||
<Route path="/:idMeal">
|
||||
{meal !== undefined && meal.meals !== null ? (
|
||||
<Meal meal={meal} getMeal={getMeal} />
|
||||
) : (
|
||||
<Route exact path={buttonUrl}>
|
||||
{meal !== undefined && meal.meals !== null ? (
|
||||
<Meal meal={meal} getMeal={getRandomMeal} />
|
||||
) : (
|
||||
<NotFoundPage handleClick={getRandomMeal} />
|
||||
)}
|
||||
</Route>
|
||||
|
||||
<Route exact path="/categories">
|
||||
<CategoryListPage
|
||||
items={categories}
|
||||
getCategories={getCategories}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/categories/:strCategory/">
|
||||
<CategoryPage
|
||||
getData={getData}
|
||||
getMeal={getMeal}
|
||||
setMeal={setMeal}
|
||||
meal={meal}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route exact path="/search">
|
||||
<SearchPage
|
||||
searchString={searchString}
|
||||
searchResults={searchResults}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/contact">
|
||||
<ContactPage />
|
||||
</Route>
|
||||
|
||||
<Route path="/404">
|
||||
<NotFoundPage handleClick={getRandomMeal} />
|
||||
)}
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="*">
|
||||
<Redirect to="/404" />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Route path="/:idMeal">
|
||||
{meal !== undefined && meal.meals !== null ? (
|
||||
<Meal meal={meal} getMeal={getMeal} />
|
||||
) : (
|
||||
<NotFoundPage handleClick={getRandomMeal} />
|
||||
)}
|
||||
</Route>
|
||||
|
||||
<Footer />
|
||||
</Router>
|
||||
<Route path="*">
|
||||
<Redirect to="/404" />
|
||||
</Route>
|
||||
</Switch>
|
||||
|
||||
<Footer />
|
||||
</Router>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -16,8 +16,8 @@ export const ContactPage = props => {
|
|||
</div>
|
||||
) : (
|
||||
<div className="container">
|
||||
<h1 className="logo">Contact Us</h1>
|
||||
<ContactForm handleSubmit={setIsSubmitted} />
|
||||
<h2 className="logo">Contact Us</h2>
|
||||
<ContactForm onSubmit={setIsSubmitted} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
17
src/utils/inputHook.js
Normal file
17
src/utils/inputHook.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { useState } from "react";
|
||||
|
||||
export const useInput = initialValue => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
return {
|
||||
value,
|
||||
setValue,
|
||||
reset: () => setValue(""),
|
||||
bind: {
|
||||
value,
|
||||
onChange: ev => {
|
||||
setValue(ev.target.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
Reference in a new issue