mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-12 13:26:45 +00:00
parent
0d8cc9c9b3
commit
cb101b22ec
43 changed files with 447 additions and 291 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
build/
|
build/
|
||||||
node_modules/
|
node_modules/
|
||||||
/.idea
|
/.idea
|
||||||
|
.env
|
||||||
1
TODO.md
1
TODO.md
|
|
@ -14,3 +14,4 @@
|
||||||
- [ ] Use Css-in-Js
|
- [ ] Use Css-in-Js
|
||||||
- [ ] Redirect to 404
|
- [ ] Redirect to 404
|
||||||
- [x] Typescript
|
- [x] Typescript
|
||||||
|
- [ ] strict typing
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^14.14.37",
|
"@types/node": "^14.14.37",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.3",
|
||||||
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,16 @@ import MainLayout from "./layouts/MainLayout";
|
||||||
import { AppRouter } from "./router";
|
import { AppRouter } from "./router";
|
||||||
import { Router } from "./router/Router";
|
import { Router } from "./router/Router";
|
||||||
import { getData } from "./services/api";
|
import { getData } from "./services/api";
|
||||||
|
import { MealSummary } from "./types/meal";
|
||||||
import { useAuth0 } from "./utils/auth0-spa";
|
import { useAuth0 } from "./utils/auth0-spa";
|
||||||
|
|
||||||
export const App: FC = () => {
|
export const App: FC = () => {
|
||||||
const { loading } = useAuth0();
|
const { loading } = useAuth0();
|
||||||
const [searchString, setSearchString] = useState("");
|
const [searchString, setSearchString] = useState("");
|
||||||
const [searchResults, setSearchResults] = useState({ meals: [] });
|
const [searchResults, setSearchResults] = useState({
|
||||||
const [meal, setMeal] = useState(null);
|
meals: [] as MealSummary[],
|
||||||
|
});
|
||||||
|
const [_, setMeal] = useState(null);
|
||||||
|
|
||||||
const getRandomMeal = () => {
|
const getRandomMeal = () => {
|
||||||
getData("random", setMeal);
|
getData("random", setMeal);
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
// import { notificationMail, confirmationMail } from "../utils/mail";
|
|
||||||
|
|
||||||
export const ContactForm = ({ setIsSubmitted }) => {
|
|
||||||
const [firstName, setFirstName] = useState("");
|
|
||||||
const [lastName, setLastName] = useState("");
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [phone, setPhone] = useState("");
|
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// confirmationMail(email);
|
|
||||||
// const body = `Sender: ${firstName} ${lastName}\nPhone: ${phone}\nMessage: ${message}`;
|
|
||||||
// notificationMail(email, `New message from ${firstName} ${lastName}`, body);
|
|
||||||
setIsSubmitted(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col s12">
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<ContactFormInput
|
|
||||||
id="First Name"
|
|
||||||
value={firstName}
|
|
||||||
dispatch={setFirstName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<ContactFormInput
|
|
||||||
id="Last Name"
|
|
||||||
value={lastName}
|
|
||||||
dispatch={setLastName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<ContactFormInput
|
|
||||||
id="Email"
|
|
||||||
type="email"
|
|
||||||
value={email}
|
|
||||||
dispatch={setEmail}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12 m6">
|
|
||||||
<ContactFormInput
|
|
||||||
id="Phone"
|
|
||||||
value={phone}
|
|
||||||
type="tel"
|
|
||||||
dispatch={setPhone}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12">
|
|
||||||
<ContactFormTextArea
|
|
||||||
id="Message"
|
|
||||||
value={message}
|
|
||||||
dispatch={setMessage}
|
|
||||||
/>
|
|
||||||
<ContactFormSubmit text="Send Message" color="orange darken-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContactFormInput = ({ id, type = "text", value, dispatch }) => {
|
|
||||||
const handleChange = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="input-field">
|
|
||||||
{/* <i className="material-icons prefix">account_circle</i> */}
|
|
||||||
<input
|
|
||||||
className="validate"
|
|
||||||
type={type}
|
|
||||||
id={id}
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<label htmlFor={id}>{id}</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContactFormTextArea = ({ id, value, dispatch }) => {
|
|
||||||
const handleChange = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="input-field">
|
|
||||||
<label htmlFor={id}>{id}</label>
|
|
||||||
<textarea
|
|
||||||
className="materialize-textarea validate"
|
|
||||||
rows={12}
|
|
||||||
name={id}
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContactFormSubmit = ({ text, color }) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={`waves-effect waves-light btn ${color}`}
|
|
||||||
type="submit"
|
|
||||||
name="submit"
|
|
||||||
>
|
|
||||||
<i className="material-icons right">send</i> {text}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
export const CopyrightText = () => {
|
export const CopyrightText: FC = () => (
|
||||||
return (
|
<span className="grey-text text-darken-1">
|
||||||
<span className="grey-text text-darken-1">
|
© 2020 - <span className="logo">Chef's</span> - Made with{" "}
|
||||||
© 2020 - <span className="logo">Chef's</span> - Made with{" "}
|
<span role="img" aria-label="heart">
|
||||||
<span role="img" aria-label="heart">
|
❤️
|
||||||
❤️
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
);
|
</span>
|
||||||
};
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { upFirstChar } from "../utils/methods";
|
import { upFirstChar } from "../utils/methods";
|
||||||
|
|
||||||
export const FooterLink = ({ link, textColor = "" }) => {
|
type Props = {
|
||||||
|
link: string;
|
||||||
|
textColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FooterLink: FC<Props> = ({ link, textColor = "" }) => {
|
||||||
const textColorClass = `${textColor}-text`;
|
const textColorClass = `${textColor}-text`;
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
export const GitHubLink = () => {
|
export const GitHubLink: FC = () => (
|
||||||
return (
|
<a
|
||||||
<a
|
className="grey-text text-darken-1 right"
|
||||||
className="grey-text text-darken-1 right"
|
href="https://github.com/rjNemo/meal_planner"
|
||||||
href="https://github.com/rjNemo/meal_planner"
|
target="blank"
|
||||||
target="blank"
|
rel="noopener"
|
||||||
rel="noopener"
|
>
|
||||||
>
|
GitHub
|
||||||
GitHub
|
</a>
|
||||||
</a>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
import { useAuth0 } from "../utils/auth0-spa";
|
||||||
|
|
||||||
export const LogInButton = ({ color }) => {
|
type Props = {
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogInButton: FC<Props> = ({ color }) => {
|
||||||
const { loginWithRedirect } = useAuth0();
|
const { loginWithRedirect } = useAuth0();
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
loginWithRedirect({});
|
loginWithRedirect({});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
import { useAuth0 } from "../utils/auth0-spa";
|
||||||
|
|
||||||
export const LogOutButton = () => {
|
export const LogOutButton: FC = () => {
|
||||||
const { logout } = useAuth0();
|
const { logout } = useAuth0();
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
logout();
|
logout();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export const Logo = () => {
|
export const Logo: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Link to="/" className="brand-logo">
|
<Link to="/" className="brand-logo">
|
||||||
<img
|
<img
|
||||||
// className="responsive-img"
|
|
||||||
src="/logo192.png"
|
src="/logo192.png"
|
||||||
alt="chef's logo"
|
alt="chef's logo"
|
||||||
height="30px"
|
height="30px"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { buttonURL, links } from "../constants";
|
import { buttonURL, links } from "../constants";
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
import { useAuth0 } from "../utils/auth0-spa";
|
||||||
|
|
@ -7,7 +8,12 @@ import { Logo } from "./Logo";
|
||||||
import { LogOutButton } from "./LogOutButton";
|
import { LogOutButton } from "./LogOutButton";
|
||||||
import { RandomButton } from "./RandomButton";
|
import { RandomButton } from "./RandomButton";
|
||||||
|
|
||||||
export const Navbar = ({ openNavClick, handleClick }) => {
|
type Props = {
|
||||||
|
openNavClick: React.MouseEventHandler;
|
||||||
|
handleClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Navbar: FC<Props> = ({ openNavClick, handleClick }) => {
|
||||||
const { isAuthenticated } = useAuth0();
|
const { isAuthenticated } = useAuth0();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
export const PreLoader = () => {
|
export const PreLoader: FC = () => (
|
||||||
return (
|
<div className="preloader-wrapper active">
|
||||||
<div className="preloader-wrapper active">
|
<div className="spinner-layer spinner-red-only">
|
||||||
<div className="spinner-layer spinner-red-only">
|
<div className="circle-clipper left">
|
||||||
<div className="circle-clipper left">
|
<div className="circle" />
|
||||||
<div className="circle"></div>
|
</div>
|
||||||
</div>
|
<div className="gap-patch">
|
||||||
<div className="gap-patch">
|
<div className="circle" />
|
||||||
<div className="circle"></div>
|
</div>
|
||||||
</div>
|
<div className="circle-clipper right">
|
||||||
<div className="circle-clipper right">
|
<div className="circle" />
|
||||||
<div className="circle"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
};
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export const RandomButton = ({ url, size = "large", handleClick, color }) => {
|
type Props = {
|
||||||
|
url: string;
|
||||||
|
size?: string;
|
||||||
|
handleClick: () => void;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RandomButton: FC<Props> = ({
|
||||||
|
url,
|
||||||
|
size = "large",
|
||||||
|
handleClick,
|
||||||
|
color,
|
||||||
|
}) => {
|
||||||
const classString = `waves-effect waves-light btn-${size} ${color}`;
|
const classString = `waves-effect waves-light btn-${size} ${color}`;
|
||||||
return (
|
return (
|
||||||
<Link to={url}>
|
<Link to={url}>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
import { ChangeEvent } from "react";
|
import React, { ChangeEvent, FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getData } from "../services/api";
|
import { getData } from "../services/api";
|
||||||
|
import { MealSummary } from "../types/meal";
|
||||||
|
|
||||||
export const SearchBar = ({
|
type Props = {
|
||||||
|
searchString: string;
|
||||||
|
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setSearchResults: React.Dispatch<
|
||||||
|
React.SetStateAction<{ meals: MealSummary[] }>
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchBar: FC<Props> = ({
|
||||||
searchString,
|
searchString,
|
||||||
setSearchString,
|
setSearchString,
|
||||||
setSearchResults,
|
setSearchResults,
|
||||||
}) => {
|
}) => {
|
||||||
const getSearchResults = (e) => {
|
const getSearchResults: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
searchString === ""
|
searchString === ""
|
||||||
? e.preventDefault()
|
? e.preventDefault()
|
||||||
: getData(searchString, setSearchResults, "search");
|
: getData(searchString, setSearchResults, "search");
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { buttonURL, links } from "../constants";
|
import { buttonURL, links } from "../constants";
|
||||||
import ChefImage from "../images/chef.svg";
|
import ChefImage from "../images/chef.svg";
|
||||||
|
|
@ -8,7 +9,13 @@ import { LogInButton } from "./LogInButton";
|
||||||
import { LogOutButton } from "./LogOutButton";
|
import { LogOutButton } from "./LogOutButton";
|
||||||
import { RandomButton } from "./RandomButton";
|
import { RandomButton } from "./RandomButton";
|
||||||
|
|
||||||
export const SideNav = ({ showNav, closeNavClick, handleClick }) => {
|
type Props = {
|
||||||
|
showNav: boolean;
|
||||||
|
closeNavClick: React.MouseEventHandler;
|
||||||
|
handleClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SideNav: FC<Props> = ({ showNav, closeNavClick, handleClick }) => {
|
||||||
const { isAuthenticated, user } = useAuth0();
|
const { isAuthenticated, user } = useAuth0();
|
||||||
let transformStyle = {
|
let transformStyle = {
|
||||||
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
|
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { PreLoader } from "../../components/PreLoader";
|
import { PreLoader } from "../../components/PreLoader";
|
||||||
import { getData } from "../../services/api";
|
import { getData } from "../../services/api";
|
||||||
import { CategoriesPage } from "./components/CategoriesPage";
|
import { CategoriesPage } from "./components/CategoriesPage";
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { getData } from "../../services/api";
|
||||||
import { CategoryPage } from "./components/CategoryPage";
|
import { CategoryPage } from "./components/CategoryPage";
|
||||||
|
|
||||||
export const Category: FC = () => {
|
export const Category: FC = () => {
|
||||||
const { strCategory } = useParams();
|
const { strCategory } = useParams<{ strCategory: string }>();
|
||||||
const [meals, setMeals] = useState({ meals: [] });
|
const [meals, setMeals] = useState({ meals: [] });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
71
src/containers/Contact/components/ContactForm.tsx
Normal file
71
src/containers/Contact/components/ContactForm.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { FC, useState } from "react";
|
||||||
|
import { ContactFormInput } from "./ContactFormInput";
|
||||||
|
import { ContactFormSubmitButton } from "./ContactFormSubmitButton";
|
||||||
|
import { ContactFormTextArea } from "./ContactFormTextArea";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setIsSubmitted: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContactForm: FC<Props> = ({ setIsSubmitted }) => {
|
||||||
|
const [firstName, setFirstName] = useState("");
|
||||||
|
const [lastName, setLastName] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [phone, setPhone] = useState("");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitted(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col s12">
|
||||||
|
<div className="col s12 m6">
|
||||||
|
<ContactFormInput
|
||||||
|
id="First Name"
|
||||||
|
value={firstName}
|
||||||
|
dispatch={setFirstName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col s12 m6">
|
||||||
|
<ContactFormInput
|
||||||
|
id="Last Name"
|
||||||
|
value={lastName}
|
||||||
|
dispatch={setLastName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col s12 m6">
|
||||||
|
<ContactFormInput
|
||||||
|
id="Email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
dispatch={setEmail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col s12 m6">
|
||||||
|
<ContactFormInput
|
||||||
|
id="Phone"
|
||||||
|
value={phone}
|
||||||
|
type="tel"
|
||||||
|
dispatch={setPhone}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col s12">
|
||||||
|
<ContactFormTextArea
|
||||||
|
id="Message"
|
||||||
|
value={message}
|
||||||
|
dispatch={setMessage}
|
||||||
|
/>
|
||||||
|
<ContactFormSubmitButton
|
||||||
|
text="Send Message"
|
||||||
|
color="orange darken-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
35
src/containers/Contact/components/ContactFormInput.tsx
Normal file
35
src/containers/Contact/components/ContactFormInput.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
type?: string;
|
||||||
|
value: string;
|
||||||
|
dispatch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContactFormInput: FC<Props> = ({
|
||||||
|
id,
|
||||||
|
type = "text",
|
||||||
|
value,
|
||||||
|
dispatch,
|
||||||
|
}) => {
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="input-field">
|
||||||
|
{/* <i className="material-icons prefix">account_circle</i> */}
|
||||||
|
<input
|
||||||
|
className="validate"
|
||||||
|
type={type}
|
||||||
|
id={id}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label htmlFor={id}>{id}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContactFormSubmitButton: FC<Props> = ({ text, color }) => (
|
||||||
|
<button
|
||||||
|
className={`waves-effect waves-light btn ${color}`}
|
||||||
|
type="submit"
|
||||||
|
name="submit"
|
||||||
|
>
|
||||||
|
<i className="material-icons right">send</i> {text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
13
src/containers/Contact/components/ContactFormSubmitted.tsx
Normal file
13
src/containers/Contact/components/ContactFormSubmitted.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
export function ContactFormSubmitted() {
|
||||||
|
return (
|
||||||
|
<div className="container center-align">
|
||||||
|
<img
|
||||||
|
className="responsive-img"
|
||||||
|
src={require("../../../images/mail_sent.svg")}
|
||||||
|
alt="mail_sent"
|
||||||
|
width="30%"
|
||||||
|
/>
|
||||||
|
<h4>Thank you for your message</h4>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/containers/Contact/components/ContactFormTextArea.tsx
Normal file
28
src/containers/Contact/components/ContactFormTextArea.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
dispatch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContactFormTextArea: FC<Props> = ({ id, value, dispatch }) => {
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="input-field">
|
||||||
|
<label htmlFor={id}>{id}</label>
|
||||||
|
<textarea
|
||||||
|
className="materialize-textarea validate"
|
||||||
|
rows={12}
|
||||||
|
name={id}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
import { FC, 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";
|
||||||
|
import { ContactFormSubmitted } from "./components/ContactFormSubmitted";
|
||||||
|
|
||||||
export const Contact: FC = () => {
|
export const Contact: FC = () => {
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
|
||||||
return isSubmitted ? (
|
return isSubmitted ? (
|
||||||
<div className="container center-align">
|
<ContactFormSubmitted />
|
||||||
<img
|
|
||||||
className="responsive-img"
|
|
||||||
src={require("../../images/mail_sent.svg")}
|
|
||||||
alt="mail_sent"
|
|
||||||
width="30%"
|
|
||||||
/>
|
|
||||||
<h4>Thank you for your message</h4>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<PageLayout title="Contact Us">
|
<PageLayout title="Contact Us">
|
||||||
<ContactForm setIsSubmitted={setIsSubmitted} />
|
<ContactForm setIsSubmitted={setIsSubmitted} />
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import { FC } from "react";
|
||||||
import { RandomButton } from "../../components/RandomButton";
|
import { RandomButton } from "../../components/RandomButton";
|
||||||
import { buttonURL } from "../../constants";
|
import { buttonURL } from "../../constants";
|
||||||
import HeroImage from "../../images/chef.svg";
|
import HeroImage from "../../images/chef.svg";
|
||||||
|
|
||||||
export const Home = () => (
|
export const Home: FC = () => (
|
||||||
<section className="container ">
|
<section className="container ">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col s12 m6">
|
<div className="col s12 m6">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ingredients: string[];
|
ingredients: string[][];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MealIngredientList: FC<Props> = ({ ingredients }) => (
|
export const MealIngredientList: FC<Props> = ({ ingredients }) => (
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { MealPresentation } from "./MealPresentation";
|
||||||
import { MealRecipe } from "./MealRecipe";
|
import { MealRecipe } from "./MealRecipe";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ingredients: string[];
|
ingredients: string[][];
|
||||||
recipe: string;
|
recipe: string;
|
||||||
meal: Meal;
|
meal: Meal;
|
||||||
handleFavChange: () => void;
|
handleFavChange: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,37 @@
|
||||||
import React, { FC, useEffect, useState } from "react";
|
import React, { FC, useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { NotFound } from "../NotFound";
|
import { getData } from "../../services/api";
|
||||||
import { useFirebase } from "../../services/Firebase";
|
import { useFirebase } from "../../services/Firebase";
|
||||||
|
import { MealApi as MealType } from "../../types/meal";
|
||||||
import { useAuth0 } from "../../utils/auth0-spa";
|
import { useAuth0 } from "../../utils/auth0-spa";
|
||||||
|
import { NotFound } from "../NotFound";
|
||||||
import { MealPage } from "./components/MealPage";
|
import { MealPage } from "./components/MealPage";
|
||||||
import { getMeal, getRandomMeal } from "./service";
|
import { buildIngredientList, buildMealProps } from "./service";
|
||||||
|
|
||||||
export const Meal: FC = () => {
|
export const Meal: FC = () => {
|
||||||
// hooks
|
// hooks
|
||||||
const { user, isAuthenticated } = useAuth0();
|
const { user, isAuthenticated } = useAuth0();
|
||||||
const { id } = useParams();
|
const { id } = useParams<{ id: string }>();
|
||||||
const fb = useFirebase();
|
const fb = useFirebase();
|
||||||
// local state
|
// local state
|
||||||
const [meal, setMeal] = useState(null);
|
const [meal, setMeal] = useState({ meals: [] as MealType[] });
|
||||||
const [isFav, setIsFav] = useState<boolean>();
|
const [isFav, setIsFav] = useState<boolean>();
|
||||||
// variables
|
// variables
|
||||||
const mealItem = meal?.meals?.[0];
|
const mealItem = meal?.meals?.[0];
|
||||||
|
|
||||||
|
const getMeal = (
|
||||||
|
id: string,
|
||||||
|
setMeal: React.Dispatch<React.SetStateAction<{ meals: MealType[] }>>
|
||||||
|
) => {
|
||||||
|
getData(id, setMeal, "lookup");
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomMeal = (
|
||||||
|
setMeal: React.Dispatch<React.SetStateAction<{ meals: MealType[] }>>
|
||||||
|
) => {
|
||||||
|
getData("random", setMeal);
|
||||||
|
};
|
||||||
|
|
||||||
// effects
|
// effects
|
||||||
/** Fetch meal from db */
|
/** Fetch meal from db */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -24,7 +40,7 @@ export const Meal: FC = () => {
|
||||||
/** Updates fav status in db */
|
/** Updates fav status in db */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
fb.isFav(user.email, mealItem?.idMeal).then((res) => setIsFav(res));
|
fb?.isFav(user.email, mealItem?.idMeal).then((res) => setIsFav(res));
|
||||||
}
|
}
|
||||||
}, [user, fb, mealItem?.idMeal, isAuthenticated]);
|
}, [user, fb, mealItem?.idMeal, isAuthenticated]);
|
||||||
// other logic
|
// other logic
|
||||||
|
|
@ -44,23 +60,8 @@ export const Meal: FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const item = {
|
const item = buildMealProps(mealItem, isFav!);
|
||||||
mealName: mealItem?.strMeal,
|
const ingredients = buildIngredientList(mealItem);
|
||||||
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 ? (
|
return !!meal?.meals ? (
|
||||||
<MealPage
|
<MealPage
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,24 @@
|
||||||
import { getData } from "../../services/api";
|
import { MealApi } from "../../types/meal";
|
||||||
|
|
||||||
export const getMeal = (id, setMeal) => {
|
export const buildIngredientList = (mealItem: MealApi): string[][] => {
|
||||||
getData(id, setMeal, "lookup");
|
let ingredients = [];
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
let strIng = `strIngredient${i}`;
|
||||||
|
let strMes = `strMeasure${i}`;
|
||||||
|
// @ts-ignore
|
||||||
|
if (!!mealItem?.[strIng] && !!mealItem?.[strIng]) {
|
||||||
|
// @ts-ignore
|
||||||
|
ingredients.push([mealItem?.[strIng], mealItem?.[strMes]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ingredients;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRandomMeal = (setMeal) => {
|
export const buildMealProps = (mealItem: MealApi, isFav: boolean) => ({
|
||||||
getData("random", setMeal);
|
mealName: mealItem?.strMeal,
|
||||||
};
|
imgAddress: mealItem?.strMealThumb,
|
||||||
|
videoAddress: mealItem?.strYoutube,
|
||||||
|
mealCategory: mealItem?.strCategory,
|
||||||
|
mealArea: mealItem?.strArea,
|
||||||
|
isFav,
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { RandomButton } from "../../components/RandomButton";
|
import { RandomButton } from "../../components/RandomButton";
|
||||||
import { getRandomMeal } from "../Meal/service";
|
|
||||||
|
|
||||||
export const NotFound: FC = () => (
|
export const NotFound: FC = () => (
|
||||||
<div className="container center-align">
|
<div className="container center-align">
|
||||||
|
|
@ -16,11 +15,7 @@ export const NotFound: FC = () => (
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-content">
|
<div className="card-content">
|
||||||
<RandomButton
|
<RandomButton url="/random" handleClick={() => {}} />
|
||||||
url="/random"
|
|
||||||
handleClick={getRandomMeal}
|
|
||||||
color={null}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { PreLoader } from "../../components/PreLoader";
|
import { PreLoader } from "../../components/PreLoader";
|
||||||
import { useFirebase } from "../../services/Firebase";
|
import { useFirebase } from "../../services/Firebase";
|
||||||
|
import { MealSummary } from "../../types/meal";
|
||||||
import { useAuth0 } from "../../utils/auth0-spa";
|
import { useAuth0 } from "../../utils/auth0-spa";
|
||||||
import { ProfilePage } from "./components/ProfilePage";
|
import { ProfilePage } from "./components/ProfilePage";
|
||||||
|
|
||||||
export const Profile: FC = () => {
|
export const Profile: FC = () => {
|
||||||
const { loading, user } = useAuth0();
|
const { loading, user } = useAuth0();
|
||||||
const [favs, setFavs] = useState([]);
|
const [favs, setFavs] = useState([] as MealSummary[]);
|
||||||
const db = useFirebase();
|
const db = useFirebase();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,6 @@ nav {
|
||||||
font-family: "Marck Script", cursive;
|
font-family: "Marck Script", cursive;
|
||||||
}
|
}
|
||||||
|
|
||||||
i.material-icons {
|
a>i.material-icons {
|
||||||
color: #ff9800;
|
color: #ff9800;
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
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";
|
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";
|
||||||
import Firebase, { FirebaseContext } from "./services/Firebase";
|
import Firebase, { FirebaseContext } from "./services/Firebase";
|
||||||
import config from "./utils/auth_config.json";
|
|
||||||
|
|
||||||
const onRedirectCallBack = (appState) => {
|
const onRedirectCallBack = (appState) => {
|
||||||
history.push(
|
history.push(
|
||||||
|
|
@ -18,14 +17,13 @@ const onRedirectCallBack = (appState) => {
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Auth0Provider
|
<Auth0Provider
|
||||||
// domain={process.env.DOMAIN}
|
domain={process.env.REACT_APP_DOMAIN}
|
||||||
// client_id={process.env.CLIENT_ID}
|
client_id={process.env.REACT_APP_CLIENT_ID}
|
||||||
domain={config.DOMAIN}
|
|
||||||
client_id={config.CLIENT_ID}
|
|
||||||
redirect_uri={window.location.origin}
|
redirect_uri={window.location.origin}
|
||||||
onRedirectCallBack={onRedirectCallBack}
|
onRedirectCallBack={onRedirectCallBack}
|
||||||
>
|
>
|
||||||
<FirebaseContext.Provider value={new Firebase()}>
|
{/*<FirebaseContext.Provider value={new Firebase()}> todo fix Firebase app*/}
|
||||||
|
<FirebaseContext.Provider>
|
||||||
<App />
|
<App />
|
||||||
</FirebaseContext.Provider>
|
</FirebaseContext.Provider>
|
||||||
</Auth0Provider>,
|
</Auth0Provider>,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
import React, { useState } from "react";
|
import React, { FC, useState } from "react";
|
||||||
import { Footer } from "../components/Footer";
|
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 { SideNav } from "../components/SideNav";
|
import { SideNav } from "../components/SideNav";
|
||||||
|
import { MealSummary } from "../types/meal";
|
||||||
|
|
||||||
// TODO FC...
|
type Props = {
|
||||||
const MainLayout = ({
|
getRandomMeal: () => void;
|
||||||
|
searchString: string;
|
||||||
|
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setSearchResults: React.Dispatch<
|
||||||
|
React.SetStateAction<{ meals: MealSummary[] }>
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
const MainLayout: FC<Props> = ({
|
||||||
getRandomMeal,
|
getRandomMeal,
|
||||||
searchString,
|
searchString,
|
||||||
setSearchString,
|
setSearchString,
|
||||||
|
|
@ -14,19 +22,19 @@ const MainLayout = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [showNav, setShowNav] = useState(false);
|
const [showNav, setShowNav] = useState(false);
|
||||||
|
|
||||||
const openNavClick = (e) => {
|
const openNavClick: React.MouseEventHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowNav(true);
|
setShowNav(true);
|
||||||
document.addEventListener("keydown", handleEscKey);
|
document.addEventListener("keydown", handleEscKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNavClick = (e) => {
|
const closeNavClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowNav(false);
|
setShowNav(false);
|
||||||
document.removeEventListener("keydown", handleEscKey);
|
document.removeEventListener("keydown", handleEscKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEscKey = (e) => {
|
const handleEscKey = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
setShowNav(false);
|
setShowNav(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { FC } from "react";
|
||||||
import { Redirect, Route, Switch } from "react-router-dom";
|
import { Redirect, Route, Switch } from "react-router-dom";
|
||||||
import { buttonURL } from "../constants";
|
import { buttonURL } from "../constants";
|
||||||
import { Categories } from "../containers/Categories";
|
import { Categories } from "../containers/Categories";
|
||||||
|
|
@ -8,11 +9,14 @@ import { Profile } from "../containers/Profile";
|
||||||
import { Search } from "../containers/Search";
|
import { Search } from "../containers/Search";
|
||||||
import { Contact } from "../containers/Contact";
|
import { Contact } from "../containers/Contact";
|
||||||
import { NotFound } from "../containers/NotFound";
|
import { NotFound } from "../containers/NotFound";
|
||||||
|
import { MealSummary } from "../types/meal";
|
||||||
import { PrivateRoute } from "./PrivateRoute";
|
import { PrivateRoute } from "./PrivateRoute";
|
||||||
|
|
||||||
//TODO: remove state from router move to containers
|
type Props = {
|
||||||
|
searchString: string;
|
||||||
const AppRouter = ({ searchString, searchResults }) => (
|
searchResults: { meals: MealSummary[] };
|
||||||
|
};
|
||||||
|
const AppRouter: FC<Props> = ({ searchString, searchResults }) => (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/">
|
<Route exact path="/">
|
||||||
<Home />
|
<Home />
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
import { useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { Route } from "react-router-dom";
|
import { Route, RouteProps } from "react-router-dom";
|
||||||
import { useAuth0 } from "../utils/auth0-spa";
|
import { useAuth0 } from "../utils/auth0-spa";
|
||||||
|
|
||||||
// TODO use FC and props
|
type Props = {
|
||||||
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
component: FC;
|
||||||
|
} & RouteProps;
|
||||||
|
|
||||||
|
export const PrivateRoute: FC<Props> = ({
|
||||||
|
component: Component,
|
||||||
|
path,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -18,7 +25,8 @@ export const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
||||||
fn();
|
fn();
|
||||||
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
||||||
|
|
||||||
const render = (props) => (isAuthenticated ? <Component {...props} /> : null);
|
const render = (props: any) =>
|
||||||
|
isAuthenticated ? <Component {...props} /> : null;
|
||||||
|
|
||||||
return <Route path={path} render={render} {...rest} />;
|
return <Route path={path} render={render} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,17 @@
|
||||||
// opt-in, read https://bit.ly/CRA-PWA
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
const isLocalhost = Boolean(
|
||||||
window.location.hostname === 'localhost' ||
|
window.location.hostname === "localhost" ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === "[::1]" ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config: any) {
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
|
@ -31,7 +31,7 @@ export function register(config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener("load", () => {
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
|
|
@ -42,8 +42,8 @@ export function register(config) {
|
||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'This web app is being served cache-first by a service ' +
|
"This web app is being served cache-first by a service " +
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
"worker. To learn more, visit https://bit.ly/CRA-PWA"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,24 +54,24 @@ export function register(config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
function registerValidSW(swUrl: any, config: any) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then(registration => {
|
.then((registration) => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing;
|
||||||
if (installingWorker == null) {
|
if (installingWorker == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
installingWorker.onstatechange = () => {
|
installingWorker.onstatechange = () => {
|
||||||
if (installingWorker.state === 'installed') {
|
if (installingWorker.state === "installed") {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
"New content is available and will be used when all " +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
|
|
@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
console.log('Content is cached for offline use.');
|
console.log("Content is cached for offline use.");
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
|
|
@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('Error during service worker registration:', error);
|
console.error("Error during service worker registration:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
function checkValidServiceWorker(swUrl: any, config: any) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { 'Service-Worker': 'script' }
|
headers: { "Service-Worker": "script" },
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get("content-type");
|
||||||
if (
|
if (
|
||||||
response.status === 404 ||
|
response.status === 404 ||
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
(contentType != null && contentType.indexOf("javascript") === -1)
|
||||||
) {
|
) {
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
@ -123,14 +123,14 @@ function checkValidServiceWorker(swUrl, config) {
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
"No internet connection found. App is running in offline mode."
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregister() {
|
export function unregister() {
|
||||||
if ('serviceWorker' in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
registration.unregister();
|
registration.unregister();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
import Firebase from "./firebase";
|
||||||
|
|
||||||
// create a Firebase context to make state available anywhere in the App.
|
// create a Firebase context to make state available anywhere in the App.
|
||||||
const FirebaseContext = createContext(null);
|
const FirebaseContext = createContext(new Firebase());
|
||||||
|
|
||||||
export const useFirebase = () => useContext(FirebaseContext);
|
export const useFirebase = () => useContext(FirebaseContext);
|
||||||
export default FirebaseContext;
|
export default FirebaseContext;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
export const createURI = (keyword: string, option: string) => {
|
import React from "react";
|
||||||
|
|
||||||
|
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) {
|
if (!option) {
|
||||||
return `${ROOT}${keyword}.php`;
|
return `${ROOT}${keyword}.php`;
|
||||||
} else if (option === "filter") {
|
}
|
||||||
return `${ROOT}${option}.php?c=${keyword}`;
|
|
||||||
} else if (option === "lookup") {
|
switch (option) {
|
||||||
return `${ROOT}${option}.php?i=${keyword}`;
|
case "filter": {
|
||||||
} else if (option === "search") {
|
return `${ROOT}${option}.php?c=${keyword}`;
|
||||||
return `${ROOT}${option}.php?s=${keyword}`;
|
}
|
||||||
|
case "lookup": {
|
||||||
|
return `${ROOT}${option}.php?i=${keyword}`;
|
||||||
|
}
|
||||||
|
case "search": {
|
||||||
|
return `${ROOT}${option}.php?s=${keyword}`;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw Error("Unexpected URI");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getData = (keyword: string, set, option: string = null) => {
|
export const getData = (
|
||||||
|
keyword: string,
|
||||||
|
set: React.Dispatch<React.SetStateAction<any>>,
|
||||||
|
option?: string
|
||||||
|
) => {
|
||||||
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.warn(error + "url:" + URI))
|
.catch((error) => console.warn(error + "url:" + URI))
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export default interface Meal {
|
||||||
videoAddress: string;
|
videoAddress: string;
|
||||||
mealCategory: string;
|
mealCategory: string;
|
||||||
mealArea: string;
|
mealArea: string;
|
||||||
isFav: boolean;
|
isFav?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MealSummary {
|
export interface MealSummary {
|
||||||
|
|
@ -12,3 +12,13 @@ export interface MealSummary {
|
||||||
strMeal: string;
|
strMeal: string;
|
||||||
strMealThumb: string;
|
strMealThumb: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MealApi {
|
||||||
|
idMeal: string;
|
||||||
|
strMeal: string;
|
||||||
|
strMealThumb: string;
|
||||||
|
strYoutube: string;
|
||||||
|
strCategory: string;
|
||||||
|
strArea: string;
|
||||||
|
strInstructions: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"DOMAIN": "chefs-meal-planner.eu.auth0.com",
|
|
||||||
"CLIENT_ID": "EXe8HCfFd0jSSfqzjAvpdk72ce0y2Hh9"
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,10 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": false,
|
"strict": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
|
|
||||||
24
yarn.lock
24
yarn.lock
|
|
@ -2037,6 +2037,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/history@*":
|
||||||
|
version "4.7.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
|
||||||
|
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
|
||||||
|
|
||||||
"@types/html-minifier-terser@^5.0.0":
|
"@types/html-minifier-terser@^5.0.0":
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
|
||||||
|
|
@ -2129,7 +2134,24 @@
|
||||||
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":
|
"@types/react-router-dom@^5.1.7":
|
||||||
|
version "5.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.7.tgz#a126d9ea76079ffbbdb0d9225073eb5797ab7271"
|
||||||
|
integrity sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==
|
||||||
|
dependencies:
|
||||||
|
"@types/history" "*"
|
||||||
|
"@types/react" "*"
|
||||||
|
"@types/react-router" "*"
|
||||||
|
|
||||||
|
"@types/react-router@*":
|
||||||
|
version "5.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.13.tgz#051c0d229bd48ad90558a1db500708127cc512f7"
|
||||||
|
integrity sha512-ZIuaO9Yrln54X6elg8q2Ivp6iK6p4syPsefEYAhRDAoqNh48C8VYUmB9RkXjKSQAJSJV0mbIFCX7I4vZDcHrjg==
|
||||||
|
dependencies:
|
||||||
|
"@types/history" "*"
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react@*", "@types/react@^17.0.3":
|
||||||
version "17.0.3"
|
version "17.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
|
||||||
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
|
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue