strict typing (#7)

* strict=true

* typing and props
This commit is contained in:
Ruidy 2021-03-31 16:39:07 +02:00 committed by GitHub
parent 0d8cc9c9b3
commit cb101b22ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 447 additions and 291 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
build/
node_modules/
/.idea
/.idea
.env

View file

@ -14,3 +14,4 @@
- [ ] Use Css-in-Js
- [ ] Redirect to 404
- [x] Typescript
- [ ] strict typing

View file

@ -37,6 +37,7 @@
"devDependencies": {
"@types/node": "^14.14.37",
"@types/react": "^17.0.3",
"@types/react-router-dom": "^5.1.7",
"typescript": "^4.2.3"
}
}

View file

@ -5,13 +5,16 @@ import MainLayout from "./layouts/MainLayout";
import { AppRouter } from "./router";
import { Router } from "./router/Router";
import { getData } from "./services/api";
import { MealSummary } from "./types/meal";
import { useAuth0 } from "./utils/auth0-spa";
export const App: FC = () => {
const { loading } = useAuth0();
const [searchString, setSearchString] = useState("");
const [searchResults, setSearchResults] = useState({ meals: [] });
const [meal, setMeal] = useState(null);
const [searchResults, setSearchResults] = useState({
meals: [] as MealSummary[],
});
const [_, setMeal] = useState(null);
const getRandomMeal = () => {
getData("random", setMeal);

View file

@ -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>
);
};

View file

@ -1,12 +1,10 @@
import React from "react";
import { FC } from "react";
export const CopyrightText = () => {
return (
<span className="grey-text text-darken-1">
© 2020 - <span className="logo">Chef's</span> - Made with{" "}
<span role="img" aria-label="heart">
</span>
export const CopyrightText: FC = () => (
<span className="grey-text text-darken-1">
© 2020 - <span className="logo">Chef's</span> - Made with{" "}
<span role="img" aria-label="heart">
</span>
);
};
</span>
);

View file

@ -1,8 +1,13 @@
import React from "react";
import { FC } from "react";
import { Link } from "react-router-dom";
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`;
return (
<li>

View file

@ -1,14 +1,12 @@
import React from "react";
import { FC } from "react";
export const GitHubLink = () => {
return (
<a
className="grey-text text-darken-1 right"
href="https://github.com/rjNemo/meal_planner"
target="blank"
rel="noopener"
>
GitHub
</a>
);
};
export const GitHubLink: FC = () => (
<a
className="grey-text text-darken-1 right"
href="https://github.com/rjNemo/meal_planner"
target="blank"
rel="noopener"
>
GitHub
</a>
);

View file

@ -1,7 +1,11 @@
import React from "react";
import { FC } from "react";
import { useAuth0 } from "../utils/auth0-spa";
export const LogInButton = ({ color }) => {
type Props = {
color: string;
};
export const LogInButton: FC<Props> = ({ color }) => {
const { loginWithRedirect } = useAuth0();
const handleClick = () => {
loginWithRedirect({});

View file

@ -1,7 +1,7 @@
import React from "react";
import { FC } from "react";
import { useAuth0 } from "../utils/auth0-spa";
export const LogOutButton = () => {
export const LogOutButton: FC = () => {
const { logout } = useAuth0();
const handleClick = () => {
logout();

View file

@ -1,11 +1,10 @@
import React from "react";
import { FC } from "react";
import { Link } from "react-router-dom";
export const Logo = () => {
export const Logo: FC = () => {
return (
<Link to="/" className="brand-logo">
<img
// className="responsive-img"
src="/logo192.png"
alt="chef's logo"
height="30px"

View file

@ -1,3 +1,4 @@
import React, { FC } from "react";
import { Link } from "react-router-dom";
import { buttonURL, links } from "../constants";
import { useAuth0 } from "../utils/auth0-spa";
@ -7,7 +8,12 @@ import { Logo } from "./Logo";
import { LogOutButton } from "./LogOutButton";
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();
return (

View file

@ -1,19 +1,17 @@
import React from "react";
import { FC } from "react";
export const PreLoader = () => {
return (
<div className="preloader-wrapper active">
<div className="spinner-layer spinner-red-only">
<div className="circle-clipper left">
<div className="circle"></div>
</div>
<div className="gap-patch">
<div className="circle"></div>
</div>
<div className="circle-clipper right">
<div className="circle"></div>
</div>
export const PreLoader: FC = () => (
<div className="preloader-wrapper active">
<div className="spinner-layer spinner-red-only">
<div className="circle-clipper left">
<div className="circle" />
</div>
<div className="gap-patch">
<div className="circle" />
</div>
<div className="circle-clipper right">
<div className="circle" />
</div>
</div>
);
};
</div>
);

View file

@ -1,7 +1,19 @@
import React from "react";
import { FC } from "react";
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}`;
return (
<Link to={url}>

View file

@ -1,13 +1,22 @@
import { ChangeEvent } from "react";
import React, { ChangeEvent, FC } from "react";
import { Link } from "react-router-dom";
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,
setSearchString,
setSearchResults,
}) => {
const getSearchResults = (e) => {
const getSearchResults: React.MouseEventHandler<HTMLButtonElement> = (e) => {
searchString === ""
? e.preventDefault()
: getData(searchString, setSearchResults, "search");

View file

@ -1,3 +1,4 @@
import React, { FC } from "react";
import { Link } from "react-router-dom";
import { buttonURL, links } from "../constants";
import ChefImage from "../images/chef.svg";
@ -8,7 +9,13 @@ import { LogInButton } from "./LogInButton";
import { LogOutButton } from "./LogOutButton";
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();
let transformStyle = {
transform: showNav ? "translateX(0%)" : "translateX(-105%)",

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { PreLoader } from "../../components/PreLoader";
import { getData } from "../../services/api";
import { CategoriesPage } from "./components/CategoriesPage";

View file

@ -4,7 +4,7 @@ import { getData } from "../../services/api";
import { CategoryPage } from "./components/CategoryPage";
export const Category: FC = () => {
const { strCategory } = useParams();
const { strCategory } = useParams<{ strCategory: string }>();
const [meals, setMeals] = useState({ meals: [] });
useEffect(() => {

View 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>
);
};

View 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>
);
};

View file

@ -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>
);

View 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>
);
}

View 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>
);
};

View file

@ -1,20 +1,13 @@
import { FC, useState } from "react";
import PageLayout from "../../layouts/PageLayout";
import { ContactForm } from "../../components/ContactForm";
import { ContactForm } from "./components/ContactForm";
import { ContactFormSubmitted } from "./components/ContactFormSubmitted";
export const Contact: FC = () => {
const [isSubmitted, setIsSubmitted] = useState(false);
return isSubmitted ? (
<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>
<ContactFormSubmitted />
) : (
<PageLayout title="Contact Us">
<ContactForm setIsSubmitted={setIsSubmitted} />

View file

@ -1,9 +1,9 @@
import React from "react";
import { FC } from "react";
import { RandomButton } from "../../components/RandomButton";
import { buttonURL } from "../../constants";
import HeroImage from "../../images/chef.svg";
export const Home = () => (
export const Home: FC = () => (
<section className="container ">
<div className="row">
<div className="col s12 m6">

View file

@ -1,7 +1,7 @@
import { FC } from "react";
type Props = {
ingredients: string[];
ingredients: string[][];
};
export const MealIngredientList: FC<Props> = ({ ingredients }) => (

View file

@ -5,7 +5,7 @@ import { MealPresentation } from "./MealPresentation";
import { MealRecipe } from "./MealRecipe";
type Props = {
ingredients: string[];
ingredients: string[][];
recipe: string;
meal: Meal;
handleFavChange: () => void;

View file

@ -1,21 +1,37 @@
import React, { FC, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { NotFound } from "../NotFound";
import { getData } from "../../services/api";
import { useFirebase } from "../../services/Firebase";
import { MealApi as MealType } from "../../types/meal";
import { useAuth0 } from "../../utils/auth0-spa";
import { NotFound } from "../NotFound";
import { MealPage } from "./components/MealPage";
import { getMeal, getRandomMeal } from "./service";
import { buildIngredientList, buildMealProps } from "./service";
export const Meal: FC = () => {
// hooks
const { user, isAuthenticated } = useAuth0();
const { id } = useParams();
const { id } = useParams<{ id: string }>();
const fb = useFirebase();
// local state
const [meal, setMeal] = useState(null);
const [meal, setMeal] = useState({ meals: [] as MealType[] });
const [isFav, setIsFav] = useState<boolean>();
// variables
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
/** Fetch meal from db */
useEffect(() => {
@ -24,7 +40,7 @@ export const Meal: FC = () => {
/** Updates fav status in db */
useEffect(() => {
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]);
// other logic
@ -44,23 +60,8 @@ export const Meal: FC = () => {
}
};
const item = {
mealName: mealItem?.strMeal,
imgAddress: mealItem?.strMealThumb,
videoAddress: mealItem?.strYoutube,
mealCategory: mealItem?.strCategory,
mealArea: mealItem?.strArea,
isFav,
};
let ingredients = [];
for (let i = 1; i <= 20; i++) {
let strIng = `strIngredient${i}`;
let strMes = `strMeasure${i}`;
if (!!mealItem?.[strIng] && !!mealItem?.[strIng]) {
ingredients.push([mealItem?.[strIng], mealItem?.[strMes]]);
}
}
const item = buildMealProps(mealItem, isFav!);
const ingredients = buildIngredientList(mealItem);
return !!meal?.meals ? (
<MealPage

View file

@ -1,9 +1,24 @@
import { getData } from "../../services/api";
import { MealApi } from "../../types/meal";
export const getMeal = (id, setMeal) => {
getData(id, setMeal, "lookup");
export const buildIngredientList = (mealItem: MealApi): string[][] => {
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) => {
getData("random", setMeal);
};
export const buildMealProps = (mealItem: MealApi, isFav: boolean) => ({
mealName: mealItem?.strMeal,
imgAddress: mealItem?.strMealThumb,
videoAddress: mealItem?.strYoutube,
mealCategory: mealItem?.strCategory,
mealArea: mealItem?.strArea,
isFav,
});

View file

@ -1,6 +1,5 @@
import { FC } from "react";
import { RandomButton } from "../../components/RandomButton";
import { getRandomMeal } from "../Meal/service";
export const NotFound: FC = () => (
<div className="container center-align">
@ -16,11 +15,7 @@ export const NotFound: FC = () => (
/>
</div>
<div className="card-content">
<RandomButton
url="/random"
handleClick={getRandomMeal}
color={null}
/>
<RandomButton url="/random" handleClick={() => {}} />
</div>
</div>
</div>

View file

@ -1,12 +1,13 @@
import { FC, useEffect, useState } from "react";
import { PreLoader } from "../../components/PreLoader";
import { useFirebase } from "../../services/Firebase";
import { MealSummary } from "../../types/meal";
import { useAuth0 } from "../../utils/auth0-spa";
import { ProfilePage } from "./components/ProfilePage";
export const Profile: FC = () => {
const { loading, user } = useAuth0();
const [favs, setFavs] = useState([]);
const [favs, setFavs] = useState([] as MealSummary[]);
const db = useFirebase();
useEffect(() => {

View file

@ -29,6 +29,6 @@ nav {
font-family: "Marck Script", cursive;
}
i.material-icons {
a>i.material-icons {
color: #ff9800;
}

View file

@ -1,12 +1,11 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import {App} from "./App";
import { App } from "./App";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./utils/auth0-spa";
import history from "./utils/history";
import Firebase, { FirebaseContext } from "./services/Firebase";
import config from "./utils/auth_config.json";
const onRedirectCallBack = (appState) => {
history.push(
@ -18,14 +17,13 @@ const onRedirectCallBack = (appState) => {
ReactDOM.render(
<Auth0Provider
// domain={process.env.DOMAIN}
// client_id={process.env.CLIENT_ID}
domain={config.DOMAIN}
client_id={config.CLIENT_ID}
domain={process.env.REACT_APP_DOMAIN}
client_id={process.env.REACT_APP_CLIENT_ID}
redirect_uri={window.location.origin}
onRedirectCallBack={onRedirectCallBack}
>
<FirebaseContext.Provider value={new Firebase()}>
{/*<FirebaseContext.Provider value={new Firebase()}> todo fix Firebase app*/}
<FirebaseContext.Provider>
<App />
</FirebaseContext.Provider>
</Auth0Provider>,

View file

@ -1,11 +1,19 @@
import React, { useState } from "react";
import React, { FC, useState } from "react";
import { Footer } from "../components/Footer";
import { Navbar } from "../components/Navbar";
import { SearchBar } from "../components/SearchBar";
import { SideNav } from "../components/SideNav";
import { MealSummary } from "../types/meal";
// TODO FC...
const MainLayout = ({
type Props = {
getRandomMeal: () => void;
searchString: string;
setSearchString: React.Dispatch<React.SetStateAction<string>>;
setSearchResults: React.Dispatch<
React.SetStateAction<{ meals: MealSummary[] }>
>;
};
const MainLayout: FC<Props> = ({
getRandomMeal,
searchString,
setSearchString,
@ -14,19 +22,19 @@ const MainLayout = ({
}) => {
const [showNav, setShowNav] = useState(false);
const openNavClick = (e) => {
const openNavClick: React.MouseEventHandler = (e) => {
e.preventDefault();
setShowNav(true);
document.addEventListener("keydown", handleEscKey);
};
const closeNavClick = (e) => {
const closeNavClick = (e: React.MouseEvent) => {
e.preventDefault();
setShowNav(false);
document.removeEventListener("keydown", handleEscKey);
};
const handleEscKey = (e) => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
setShowNav(false);
}

View file

@ -1,3 +1,4 @@
import { FC } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import { buttonURL } from "../constants";
import { Categories } from "../containers/Categories";
@ -8,11 +9,14 @@ import { Profile } from "../containers/Profile";
import { Search } from "../containers/Search";
import { Contact } from "../containers/Contact";
import { NotFound } from "../containers/NotFound";
import { MealSummary } from "../types/meal";
import { PrivateRoute } from "./PrivateRoute";
//TODO: remove state from router move to containers
const AppRouter = ({ searchString, searchResults }) => (
type Props = {
searchString: string;
searchResults: { meals: MealSummary[] };
};
const AppRouter: FC<Props> = ({ searchString, searchResults }) => (
<Switch>
<Route exact path="/">
<Home />

View file

@ -1,9 +1,16 @@
import { useEffect } from "react";
import { Route } from "react-router-dom";
import { FC, useEffect } from "react";
import { Route, RouteProps } from "react-router-dom";
import { useAuth0 } from "../utils/auth0-spa";
// TODO use FC and props
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
type Props = {
component: FC;
} & RouteProps;
export const PrivateRoute: FC<Props> = ({
component: Component,
path,
...rest
}) => {
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
useEffect(() => {
@ -18,7 +25,8 @@ export const PrivateRoute = ({ component: Component, path, ...rest }) => {
fn();
}, [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} />;
};

View file

@ -11,17 +11,17 @@
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
export function register(config: any) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
@ -31,7 +31,7 @@ export function register(config) {
return;
}
window.addEventListener('load', () => {
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://bit.ly/CRA-PWA"
);
});
} else {
@ -54,24 +54,24 @@ export function register(config) {
}
}
function registerValidSW(swUrl, config) {
function registerValidSW(swUrl: any, config: any) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
"New content is available and will be used when all " +
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
);
// Execute callback
@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached.
// It's the perfect time to display a
// "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
if (config && config.onSuccess) {
@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) {
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
.catch((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.
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.
const contentType = response.headers.get('content-type');
const contentType = response.headers.get("content-type");
if (
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.
navigator.serviceWorker.ready.then(registration => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@ -123,14 +123,14 @@ function checkValidServiceWorker(swUrl, config) {
})
.catch(() => {
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() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
});
}

View file

@ -1,7 +1,8 @@
import { createContext, useContext } from "react";
import Firebase from "./firebase";
// 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 default FirebaseContext;

View file

@ -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/";
if (!option) {
return `${ROOT}${keyword}.php`;
} else if (option === "filter") {
return `${ROOT}${option}.php?c=${keyword}`;
} else if (option === "lookup") {
return `${ROOT}${option}.php?i=${keyword}`;
} else if (option === "search") {
return `${ROOT}${option}.php?s=${keyword}`;
}
switch (option) {
case "filter": {
return `${ROOT}${option}.php?c=${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);
fetch(URI)
.then((response) => response.json())
.catch((error) => console.warn(error + "url:" + URI))

View file

@ -4,7 +4,7 @@ export default interface Meal {
videoAddress: string;
mealCategory: string;
mealArea: string;
isFav: boolean;
isFav?: boolean;
}
export interface MealSummary {
@ -12,3 +12,13 @@ export interface MealSummary {
strMeal: string;
strMealThumb: string;
}
export interface MealApi {
idMeal: string;
strMeal: string;
strMealThumb: string;
strYoutube: string;
strCategory: string;
strArea: string;
strInstructions: string;
}

View file

@ -1,4 +0,0 @@
{
"DOMAIN": "chefs-meal-planner.eu.auth0.com",
"CLIENT_ID": "EXe8HCfFd0jSSfqzjAvpdk72ce0y2Hh9"
}

View file

@ -10,7 +10,10 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"strict": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",

View file

@ -2037,6 +2037,11 @@
dependencies:
"@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":
version "5.1.1"
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"
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"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==