remove old imlpementation

This commit is contained in:
Ruidy 2024-12-15 01:10:20 +01:00
parent 27c7ffacf6
commit 77ea021b59
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
61 changed files with 12 additions and 1324 deletions

View file

@ -95,13 +95,16 @@ address: [link](https://mood2food.netlify.app/).
## Built With ## Built With
- [Nuxt](https://nuxt.com/) - The Intuitive Vue Framework - [Nuxt](https://nuxt.com/) - The Intuitive Vue Framework
- [Tailwindcss](https://tailwindcss.com) -Rapidly build modern websites without ever leaving your HTML. - [Tailwindcss](https://tailwindcss.com) -Rapidly build modern websites without
- [TheMealDb](https://www.themealdb.com/api.php) - An open, crowd-sourced database of Recipes from around the world ever leaving your HTML.
- [TheMealDb](https://www.themealdb.com/api.php) - An open, crowd-sourced database
of Recipes from around the world
## Contributing ## Contributing
Please read [CONTRIBUTING.md](https://github.com/rjNemo/meal_planner/contributors) for details on our code of conduct, Please read [CONTRIBUTING.md](https://github.com/rjNemo/meal_planner/contributors)
and the process for submitting pull requests to us. for details on our code of conduct, and the process for submitting pull requests
to us.
## Versioning ## Versioning
@ -112,9 +115,10 @@ the [tags on this repository](https://github.com/rjNemo/meal_planner/tags).
- **Ruidy Nemausat** - _Initial work_ - [GitHub](https://github.com/rjNemo) - **Ruidy Nemausat** - _Initial work_ - [GitHub](https://github.com/rjNemo)
See also the list of [contributors](https://github.com/rjNemo/meal_planner/contributors) who participated in this See also the list of [contributors](https://github.com/rjNemo/meal_planner/contributors)
project. who participated in this project.
## License ## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md)
file for details

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -24,6 +24,7 @@ if (error.value) {
message: error.value.message, message: error.value.message,
}); });
} }
const url = useRequestURL(); const url = useRequestURL();
useSeoMeta({ useSeoMeta({
title: `${recipe.value!.title} | Mood2Food`, title: `${recipe.value!.title} | Mood2Food`,

View file

@ -1,11 +0,0 @@
import "./index.css";
import MainLayout from "./layouts/MainLayout";
import { AppRouter, Router } from "./router";
export const App = () => (
<Router>
<MainLayout>
<AppRouter />
</MainLayout>
</Router>
);

View file

@ -1,27 +0,0 @@
import { Link } from "react-router-dom";
import { MealSummary } from "../types/meal";
type Props = {
meal: MealSummary;
className?: string;
};
export const CardEntry = ({
meal: { idMeal, strMeal, strMealThumb },
className = "col s12 m6",
}: Props) => (
<Link to={`/${idMeal}`}>
<li>
<div className={className}>
<div className="card hoverable">
<div className="card-image">
<img src={strMealThumb} alt={strMeal} />
</div>
<div className="card-content">
<h4>{strMeal}</h4>
</div>
</div>
</div>
</li>
</Link>
);

View file

@ -1,8 +0,0 @@
export const CopyrightText = () => (
<span className="grey-text text-darken-1">
© {new Date().getFullYear()} - <span className="logo">Chef's</span> - Made with{" "}
<span role="img" aria-label="heart">
</span>
</span>
);

View file

@ -1,31 +0,0 @@
import { links } from "../constants";
import { CopyrightText } from "./CopyrightText";
import { FooterLink } from "./FooterLink";
import { GitHubLink } from "./GitHubLink";
export const Footer = () => {
const footerLinks = [...links, "random"];
return (
<footer className="page-footer">
<div className="row">
<div className="container">
<div className=" s12">
<h5 className="black-text">Navigation</h5>
<ul>
{footerLinks.map((link, i) => (
<FooterLink key={i} link={link} textColor="black" />
))}
</ul>
</div>
</div>
</div>
<div className="footer-copyright">
<div className="container">
<CopyrightText />
<GitHubLink />
</div>
</div>
</footer>
);
};

View file

@ -1,18 +0,0 @@
import { Link } from "react-router-dom";
import { upFirstChar } from "../utils/string";
type Props = {
link: string;
textColor?: string;
};
export const FooterLink = ({ link, textColor = "" }: Props) => {
const textColorClass = `${textColor}-text`;
return (
<li>
<Link className={`${textColorClass} waves-effect text-lighten-3`} to={`/${link}`}>
{upFirstChar(link)}
</Link>
</li>
);
};

View file

@ -1,10 +0,0 @@
export const GitHubLink = () => (
<a
className="grey-text text-darken-1 right"
href="https://github.com/rjNemo/meal_planner"
target="blank"
rel="noopener"
>
GitHub
</a>
);

View file

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

View file

@ -1,40 +0,0 @@
import { MouseEventHandler } from "react";
import { Link } from "react-router-dom";
import { buttonURL, links } from "../constants";
import { FooterLink } from "./FooterLink";
import { Logo } from "./Logo";
import { RandomButton } from "./RandomButton";
type Props = { openNavClick: MouseEventHandler };
export const Navbar = ({ openNavClick }: Props) => (
<div className="navbar-fixed">
<nav>
<div className="nav-wrapper">
<div className="container ">
<Logo />
<ul id="nav-mobile" className="right hide-on-med-and-down">
{links.map((link, i) => (
<FooterLink key={i} link={link} textColor="black" />
))}
<li>
<RandomButton
url={buttonURL}
size="small"
color="orange darken-2"
/>
</li>
</ul>
<Link
to="#"
data-target="slide-out"
className="sidenav-trigger "
onClick={openNavClick}
>
<i className="material-icons">menu</i>
</Link>
</div>
</div>
</nav>
</div>
);

View file

@ -1,15 +0,0 @@
export const PreLoader = () => (
<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,21 +0,0 @@
import { Link } from "react-router-dom";
import { useMeal } from "../store/meal";
import { fetchRandomMeal } from "../store/meal/async";
type Props = {
url: string;
size?: string;
color?: string;
};
export const RandomButton = ({ url, size = "large", color }: Props) => {
const classString = `waves-effect waves-light btn-${size} ${color}`;
const { dispatch } = useMeal();
return (
<Link to={url}>
<button className={classString} onClick={() => fetchRandomMeal(dispatch)}>
Random Recipe
</button>
</Link>
);
};

View file

@ -1,64 +0,0 @@
import { ChangeEvent, MouseEventHandler, useState } from "react";
import { Link } from "react-router-dom";
import { useMeal } from "../store/meal";
import { fetchSearchResults } from "../store/meal/async";
export const SearchBar = () => {
const { dispatch } = useMeal();
const [searchString, setSearchString] = useState("");
const getSearchResults: MouseEventHandler<HTMLButtonElement> = (e) => {
searchString === "" ? e.preventDefault() : fetchSearchResults(dispatch, searchString);
};
const clearSearchBar = () => {
setSearchString("");
dispatch({ type: "clearSearchResults" });
};
const handleChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) =>
setSearchString(value);
return (
<div className="section">
<div className="container">
<div className=" nav-wrapper">
<div className="row center-align">
<form>
<div className="input-field col s10">
<input
className="validate"
id="search"
type="search"
required
name="search"
value={searchString}
placeholder="Search for a recipe"
onChange={handleChange}
/>
<label className="label-icon" htmlFor="search">
<i className="material-icons">search</i>
</label>
<i className="material-icons" onClick={clearSearchBar}>
close
</i>
</div>
<div className="col s2 valign-wrapper">
<Link to="/search">
<button
className="btn-floating waves-effect waves-light orange lighten-2"
type="submit"
name="searchButton"
value="Search"
onClick={getSearchResults}
>
<i className="material-icons right">send</i>
</button>
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
);
};

View file

@ -1,63 +0,0 @@
import { MouseEventHandler } from "react";
import { Link } from "react-router-dom";
import { buttonURL, links } from "../constants";
import SpecialEventImage from "../images/special_event.svg";
import { FooterLink } from "./FooterLink";
import { RandomButton } from "./RandomButton";
type Props = {
showNav: boolean;
closeNavClick: MouseEventHandler;
};
export const SideNav = ({ showNav, closeNavClick }: Props) => {
let transformStyle = {
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
transition: "0.5s",
};
return (
<ul id="slide-out" className="sidenav" style={transformStyle}>
<li>
<div className="user-view" style={{ height: "30vh" }}>
<div className="background">
<img
style={{
position: "fixed" /* Sit on top of the page content */,
width: "100%" /* Full width (cover the whole page) */,
height: "30vh" /* Full width (cover the whole page) */,
top: "0",
left: "0",
right: "0",
bottom: "0",
backgroundColor: "rgba(0,0,0,0.5)",
zIndex: 2,
}}
src={SpecialEventImage}
alt="sidenav_background"
/>
</div>
<i className="material-icons right" onClick={closeNavClick}>
close
</i>
</div>
</li>
<li>
<RandomButton url={buttonURL} size="small" color="orange darken-2" />
</li>
<li>
<div className="divider" />
</li>
<li>
<Link to="#" className="subheader">
Navigation
</Link>
</li>
{links.map((link, i) => (
<FooterLink key={i} link={link} />
))}
</ul>
);
};

View file

@ -1,3 +0,0 @@
export const apiRoot = "https://www.themealdb.com/api/json/v1/1/";
export const buttonURL = "/random";
export const links = ["categories", "contact"];

View file

@ -1,33 +0,0 @@
import { Link, useRouteMatch } from "react-router-dom";
type Props = {
strCategory: string;
strCategoryThumb: string;
};
const CategoriesEntry = ({ strCategory, strCategoryThumb }: Props) => {
const { url } = useRouteMatch();
return (
<Link to={`${url}/${strCategory}`}>
<li>
<div className="row">
<div className="col s12">
<div className="card horizontal hoverable">
<div className="card-image valign-wrapper">
<img src={strCategoryThumb} alt={strCategory} />
</div>
<div className="card-stacked">
<div className="card-content black-text">
<h2 className="logo">{strCategory}</h2>
</div>
</div>
</div>
</div>
</div>
</li>
</Link>
);
};
export default CategoriesEntry;

View file

@ -1,16 +0,0 @@
import PageLayout from "../../../layouts/PageLayout";
import CategoriesEntry from "./CategoriesEntry";
type Props = {
categories: { strCategory: string; strCategoryThumb: string }[];
};
export const CategoriesPage = ({ categories }: Props) => (
<PageLayout title="Chef's Categories">
<ul>
{categories.map(({ strCategory, strCategoryThumb }, i) => (
<CategoriesEntry key={i} strCategory={strCategory} strCategoryThumb={strCategoryThumb} />
))}
</ul>
</PageLayout>
);

View file

@ -1,20 +0,0 @@
import { useEffect, useState } from "react";
import { PreLoader } from "../../components/PreLoader";
import { getData } from "../../services/api";
import { CategoriesPage } from "./components/CategoriesPage";
export const Categories = () => {
const [categories, setCategories] = useState({ categories: [] });
const getCategories = () => getData("categories").then((data) => setCategories(data));
useEffect(() => {
getCategories();
}, []);
return categories.categories.length === 0 ? (
<PreLoader />
) : (
<CategoriesPage categories={categories.categories} />
);
};

View file

@ -1,20 +0,0 @@
import { CardEntry } from "../../../components/CardEntry";
import PageLayout from "../../../layouts/PageLayout";
import { MealSummary } from "../../../types/meal";
type Props = {
meals: { meals: MealSummary[] };
strCategory: string;
};
export const CategoryPage = ({ meals, strCategory }: Props) => (
<PageLayout title={`Chef's ${strCategory} Recipes`}>
<ul>
<div className="row">
{meals.meals.map((meal) => (
<CardEntry meal={meal} key={meal.idMeal} />
))}
</div>
</ul>
</PageLayout>
);

View file

@ -1,20 +0,0 @@
import { useEffect, useState } from "react";
import { Redirect, useParams } from "react-router-dom";
import { getData } from "../../services/api";
import { CategoryPage } from "./components/CategoryPage";
export const Category = () => {
const { strCategory } = useParams<{ strCategory: string }>();
const [meals, setMeals] = useState({ meals: [] });
useEffect(() => {
const getMeals = () => getData(strCategory, "filter");
getMeals().then((data) => setMeals(data));
}, [strCategory]);
return !meals.meals ? (
<Redirect to="/404" />
) : (
<CategoryPage meals={meals} strCategory={strCategory} />
);
};

View file

@ -1,46 +0,0 @@
import { useState, FormEvent } from "react";
import { ContactFormInput } from "./ContactFormInput";
import { ContactFormSubmitButton } from "./ContactFormSubmitButton";
import { ContactFormTextArea } from "./ContactFormTextArea";
type Props = {
setIsSubmitted: (value: boolean) => void;
};
export const ContactForm = ({ setIsSubmitted }: Props) => {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [message, setMessage] = useState("");
const handleSubmit = (e: 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

@ -1,30 +0,0 @@
import { ChangeEventHandler, Dispatch, SetStateAction } from "react";
type Props = {
id: string;
type?: string;
value: string;
dispatch: Dispatch<SetStateAction<string>>;
};
export const ContactFormInput = ({ id, type = "text", value, dispatch }: Props) => {
const handleChange: 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

@ -1,10 +0,0 @@
type Props = {
text: string;
color: string;
};
export const ContactFormSubmitButton = ({ text, color }: Props) => (
<button className={`waves-effect waves-light btn ${color}`} type="submit" name="submit">
<i className="material-icons right">send</i> {text}
</button>
);

View file

@ -1,11 +0,0 @@
export const ContactFormSubmitted = () => (
<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

@ -1,28 +0,0 @@
import { ChangeEventHandler, Dispatch, FC, SetStateAction } from "react";
type Props = {
id: string;
value: string;
dispatch: Dispatch<SetStateAction<string>>;
};
export const ContactFormTextArea: FC<Props> = ({ id, value, dispatch }) => {
const handleChange: 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,16 +0,0 @@
import { useState } from "react";
import PageLayout from "../../layouts/PageLayout";
import { ContactForm } from "./components/ContactForm";
import { ContactFormSubmitted } from "./components/ContactFormSubmitted";
export const Contact = () => {
const [isSubmitted, setIsSubmitted] = useState(false);
return isSubmitted ? (
<ContactFormSubmitted />
) : (
<PageLayout title="Contact Us">
<ContactForm setIsSubmitted={setIsSubmitted} />
</PageLayout>
);
};

View file

@ -1,17 +0,0 @@
import { RandomButton } from "../../components/RandomButton";
import { buttonURL } from "../../constants";
import HeroImage from "../../images/chef.svg";
export const Home = () => (
<section className="container ">
<div className="row">
<div className="col s12 m6">
<h1 className="logo">Chef's Online Cookbook</h1>
<RandomButton url={buttonURL} size="large" color="orange darken-2" />
</div>
<picture className="col s12 m6">
<img src={HeroImage} alt="hero_image" width="100%" />
</picture>
</div>
</section>
);

View file

@ -1,22 +0,0 @@
type Props = { ingredients: string[][] };
export const MealIngredientList = ({ ingredients }: Props) => (
<div className="ingredientList">
<table className="striped highlight responsive-table">
<thead>
<tr>
<th>Ingredient</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{ingredients.map((ing, i) => (
<tr key={i}>
<td>{ing[0]}</td>
<td>{ing[1]}</td>
</tr>
))}
</tbody>
</table>
</div>
);

View file

@ -1,24 +0,0 @@
import { Meal } from "../../../types/meal";
import { MealIngredientList } from "./MealIngredientList";
import { MealPresentation } from "./MealPresentation";
import { MealRecipe } from "./MealRecipe";
type Props = {
ingredients: string[][];
recipe: string;
meal: Meal;
};
export const MealPage = ({ meal, ingredients, recipe }: Props) => (
<section className="container">
<div className="row">
<div className="col s12 l6">
<MealPresentation meal={meal} />
</div>
<div className="col s12 l6">
<MealIngredientList ingredients={ingredients} />
</div>
</div>
<MealRecipe recipe={recipe} />
</section>
);

View file

@ -1,44 +0,0 @@
import { Link } from "react-router-dom";
import { Meal } from "../../../types/meal";
type Props = {
meal: Meal;
};
export const MealPresentation = ({
meal: { mealName, imgAddress, videoAddress, mealCategory, mealArea },
}: Props) => {
return (
<div className="row">
<div className="col s12">
<div className="card orange lighten-1">
<div className="card-content black-text">
<span className="card-title">{mealName}</span>
<img className="responsive-img" src={imgAddress} alt={mealName} />
<ul>
<li>
<div className="chip">
<b>Video:</b>
<a href={videoAddress} target="blank" rel="noopener">
<i className="close material-icons">video_library</i>
</a>
</div>
<div className="chip">
<b>Category: </b> {mealCategory}
<Link to={`/categories/${mealCategory}`}>
<i className="close material-icons">call_made</i>
</Link>
</div>
<div className="chip">
<b>Origin:</b> {mealArea}
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
);
};

View file

@ -1,9 +0,0 @@
type Props = { recipe: string };
export const MealRecipe = ({ recipe }: Props) => (
<div className="recipe">
<div className="divider" />
<h3>Instructions</h3>
<p className="flow-text">{recipe}</p>
</div>
);

View file

@ -1,33 +0,0 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { useMeal } from "../../store/meal";
import { fetchMeal, fetchRandomMeal } from "../../store/meal/async";
import { NotFound } from "../NotFound";
import { MealPage } from "./components/MealPage";
import { buildIngredientList, buildMealProps } from "./service";
export const Meal = () => {
// hooks
const { id } = useParams<{ id: string }>();
const { state, dispatch } = useMeal();
// variables
const mealItem = state.meals?.[0];
// effects
/** Fetch meal from db */
useEffect(() => {
!id ? fetchRandomMeal(dispatch) : fetchMeal(dispatch, id);
}, [id, dispatch]);
const item = buildMealProps(mealItem);
const ingredients = buildIngredientList(mealItem);
return !!state.meals ? (
<MealPage
meal={item}
ingredients={ingredients}
recipe={mealItem?.strInstructions}
/>
) : (
<NotFound />
);
};

View file

@ -1,23 +0,0 @@
import { MealApi } from "../../types/meal";
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 buildMealProps = (mealItem: MealApi) => ({
mealName: mealItem?.strMeal,
imgAddress: mealItem?.strMealThumb,
videoAddress: mealItem?.strYoutube,
mealCategory: mealItem?.strCategory,
mealArea: mealItem?.strArea,
});

View file

@ -1,23 +0,0 @@
import { RandomButton } from "../../components/RandomButton";
export const NotFound = () => (
<div className="container center-align">
<div className="row">
<h1>Wrong Way!</h1>
<div className="col s12 offset-m3 m6">
<div className="card hoverable">
<div className="card-image">
<img
className="responsive-img"
src="https://images.otstatic.com/prod/26153735/2/large.jpg"
alt="404 not found"
/>
</div>
<div className="card-content">
<RandomButton url="/random" />
</div>
</div>
</div>
</div>
</div>
);

View file

@ -1,28 +0,0 @@
import BreakfastImage from "../../../images/breakfast.svg";
import PageLayout from "../../../layouts/PageLayout";
import { MealSummary } from "../../../types/meal";
import { SearchResult } from "./SearchResult";
type Props = {
searchString: string;
searchResults: MealSummary[];
};
export const SearchPage = ({ searchString, searchResults }: Props) => (
<PageLayout title={`Results for: ${searchString}`}>
{!searchResults ? (
<div className="center-align">
<p>No results to display, instead there is a picture of my breakfast.</p>
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
</div>
) : (
<div className="row">
<ul>
{searchResults.map((meal, i) => (
<SearchResult key={i} meal={meal} />
))}
</ul>
</div>
)}
</PageLayout>
);

View file

@ -1,6 +0,0 @@
import { CardEntry } from "../../../components/CardEntry";
import { MealSummary } from "../../../types/meal";
type Props = { meal: MealSummary };
export const SearchResult = ({ meal }: Props) => <CardEntry meal={meal} />;

View file

@ -1,9 +0,0 @@
import { useMeal } from "../../store/meal";
import { SearchPage } from "./components/SearchPage";
export const Search = () => {
const {
state: { searchString, search },
} = useMeal();
return <SearchPage searchString={searchString} searchResults={search} />;
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1 +0,0 @@
<svg id="fd577a60-d552-4fe8-bffb-d9cccd3352c0" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="788.38181" height="719" viewBox="0 0 788.38181 719"><defs><linearGradient id="aeee3e51-5d06-43ae-b234-e7a12d326f50" x1="365" y1="605" x2="365" y2="286" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity="0.12"/><stop offset="0.55135" stop-opacity="0.09"/><stop offset="1" stop-opacity="0.02"/></linearGradient><linearGradient id="acb63a3b-00bb-44a8-8877-bb0607d49a63" x1="1117.61899" y1="-43.05793" x2="1117.61899" y2="-102.40539" gradientTransform="matrix(-0.64881, 0.76095, 0.76095, 0.64881, 817.40491, -656.85567)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.53514" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient></defs><title>Mail sent</title><polygon points="125 410 0 268 374 0 748 268 621 410 125 410" fill="#ee6e73"/><polygon points="125 410 0 268 374 0 748 268 621 410 125 410" fill="#514abf"/><rect y="286" width="730" height="319" fill="#ee6e73"/><rect y="286" width="730" height="319" fill="url(#aeee3e51-5d06-43ae-b234-e7a12d326f50)"/><polygon points="748 719 0 719 0 268 374 494 748 268 748 719" fill="#ee6e73"/><polygon points="652.582 116.818 573.252 55.99 363.113 330.042 246.402 240.55 185.573 319.879 341.938 439.598 341.938 439.598 382.036 469.649 652.582 116.818" fill="#3ad29f"/><polygon points="3.153 166.882 0.179 168.334 0.521 167.726 0.26 167.81 0.626 167.539 40.653 96.307 61.114 113.753 78.667 130.004 73.627 132.465 79.293 142.459 3.153 166.882" fill="url(#acb63a3b-00bb-44a8-8877-bb0607d49a63)"/><polygon points="59.087 115.617 75.131 130.493 2.411 166.646 41.11 117.046 59.087 115.617" fill="#ee6e73"/><polygon points="59.087 115.617 75.131 130.493 2.411 166.646 41.11 117.046 59.087 115.617" opacity="0.2"/><polygon points="40.373 99.66 2.411 166.646 59.087 115.617 40.373 99.66" fill="#ee6e73"/><polygon points="75.599 142.01 2.489 166.16 63.604 120.539 75.599 142.01" fill="#ee6e73"/><polygon points="686.959 38.73 670.942 66.499 788.382 85.622 712.742 32.949 686.959 38.73" fill="#ee6e73"/><polygon points="686.959 38.73 670.942 66.499 788.382 85.622 712.742 32.949 686.959 38.73" opacity="0.2"/><polygon points="706.247 8.293 788.382 85.622 686.959 38.73 706.247 8.293" fill="#ee6e73"/><polygon points="675.274 82.821 788.062 84.976 682.769 47.576 675.274 82.821" fill="#ee6e73"/></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,34 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
min-height: 100vh;
flex-direction: column;
background-color: #ffffff;
}
div {
white-space: pre-wrap;
}
nav {
background-color: #ffffff;
}
.page-footer {
padding-top: 20px;
color: #fff;
background-color: #ffe0b2;
}
.logo {
font-family: "Marck Script", cursive;
}
a>i.material-icons {
color: #ff9800;
}

View file

@ -1,15 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { App } from "./App";
import * as serviceWorker from "./serviceWorker";
import { AppProvider } from "./store/meal";
ReactDOM.render(
<AppProvider>
<App />
</AppProvider>,
document.getElementById("root")
);
serviceWorker.register();

View file

@ -1,41 +0,0 @@
import { FC, MouseEvent, MouseEventHandler, useState } from "react";
import { Footer } from "../components/Footer";
import { Navbar } from "../components/Navbar";
import { SearchBar } from "../components/SearchBar";
import { SideNav } from "../components/SideNav";
const MainLayout: FC = ({ children }) => {
const [showNav, setShowNav] = useState(false);
const openNavClick: MouseEventHandler = (e) => {
e.preventDefault();
setShowNav(true);
document.addEventListener("keydown", handleEscKey);
};
const closeNavClick = (e: MouseEvent) => {
e.preventDefault();
setShowNav(false);
document.removeEventListener("keydown", handleEscKey);
};
const handleEscKey = ({ key }: KeyboardEvent) => {
if (key === "Escape") {
setShowNav(false);
}
};
return (
<>
<header>
<Navbar openNavClick={openNavClick} />
<SearchBar />
<SideNav showNav={showNav} closeNavClick={closeNavClick} />
</header>
<main>{children}</main>
<Footer />
</>
);
};
export default MainLayout;

View file

@ -1,12 +0,0 @@
import { FC } from "react";
type Props = { title: string };
const PageLayout: FC<Props> = ({ title, children }) => (
<div className="container">
<h1 className="logo">{title}</h1>
<main>{children}</main>
</div>
);
export default PageLayout;

View file

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View file

@ -1,49 +0,0 @@
import { Redirect, Route, Switch } from "react-router-dom";
import { buttonURL } from "../constants";
import { Categories } from "../containers/Categories";
import { Category } from "../containers/Category";
import { Contact } from "../containers/Contact";
import { Home } from "../containers/Home";
import { Meal } from "../containers/Meal";
import { NotFound } from "../containers/NotFound";
import { Search } from "../containers/Search";
export const AppRouter = () => (
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path={buttonURL}>
<Meal />
</Route>
<Route exact path="/categories">
<Categories />
</Route>
<Route path="/categories/:strCategory/">
<Category />
</Route>
<Route exact path="/search">
<Search />
</Route>
<Route path="/contact">
<Contact />
</Route>
<Route path="/404">
<NotFound />
</Route>
<Route path="/:id">
<Meal />
</Route>
<Route path="*">
<Redirect to="/404" />
</Route>
</Switch>
);

View file

@ -1,18 +0,0 @@
import { FC, useEffect } from "react";
import { Router as RouterOriginal, useLocation } from "react-router-dom";
import history from "../utils/history";
export const Router: FC = ({ children }) => (
<RouterOriginal history={history}>
<ScrollToTop />
{children}
</RouterOriginal>
);
export const ScrollToTop: FC = () => {
const location = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [location.pathname]);
return null;
};

View file

@ -1,2 +0,0 @@
export { AppRouter } from "./AppRouter";
export { Router } from "./Router";

View file

@ -1,137 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
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: 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) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// 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"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: any, config: any) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
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."
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// 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.");
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error("Error during service worker registration:", error);
});
}
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" },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
(contentType != null && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
"No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
});
}
}

View file

@ -1,35 +0,0 @@
import { apiRoot } from "../constants";
type Option = "filter" | "lookup" | "search";
const createURI = (keyword: string, option?: Option) => {
if (!option) {
return `${apiRoot}${keyword}.php`;
}
switch (option) {
case "filter": {
return `${apiRoot}${option}.php?c=${keyword}`;
}
case "lookup": {
return `${apiRoot}${option}.php?i=${keyword}`;
}
case "search": {
return `${apiRoot}${option}.php?s=${keyword}`;
}
default: {
throw Error("Unexpected URI");
}
}
};
export const getData = async (keyword: string, option?: Option) => {
const URI = createURI(keyword, option);
try {
const response = await fetch(URI);
return await response.json();
} catch (error) {
return console.warn(error + "url:" + URI);
}
};

View file

@ -1 +0,0 @@
import "@testing-library/jest-dom/extend-expect";

View file

@ -1,23 +0,0 @@
import { getData } from "../../services/api";
import { Dispatch } from "./reducer";
export const fetchRandomMeal = async (dispatch: Dispatch) => {
const meal = await getData("random");
dispatch({ type: "setMeal", payload: meal?.meals });
};
export const fetchMeal = async (dispatch: Dispatch, id: string) => {
const meal = await getData(id, "lookup");
dispatch({ type: "setMeal", payload: meal?.meals });
};
export const fetchSearchResults = async (
dispatch: Dispatch,
searchString: string
) => {
const meals = await getData(searchString, "search");
dispatch({
type: "setSearchResults",
payload: { search: meals?.meals, searchString },
});
};

View file

@ -1,35 +0,0 @@
//https://kentcdodds.com/blog/how-to-use-react-context-effectively
import { createContext, FC, useContext, useReducer } from "react";
import { MealApi, MealSummary } from "../../types/meal";
import { appReducer, Dispatch } from "./reducer";
export type AppState = {
meals: MealApi[];
search: MealSummary[];
searchString: string;
};
const initState = {
meals: [] as MealApi[],
search: [] as MealSummary[],
searchString: "",
};
type ContextType = { state: AppState; dispatch: Dispatch } | undefined;
const AppContext = createContext<ContextType>(undefined);
export const useMeal = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error("useMeal must be used within a AppProvider");
}
return context;
};
export const AppProvider: FC = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initState);
const value = { state, dispatch };
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

View file

@ -1,27 +0,0 @@
import { MealSummary } from "../../types/meal";
import { AppState } from "./index";
export const appReducer = (state: AppState, action: Action) => {
switch (action.type) {
case "setMeal":
return { ...state, meals: action.payload };
case "setSearchResults":
return {
...state,
search: action.payload.search,
searchString: action.payload.searchString,
};
case "clearSearchResults":
return { ...state, search: [] as MealSummary[] };
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
};
export type Action = {
payload?: any;
type: "setMeal" | "setSearchResults" | "clearSearchResults";
};
export type Dispatch = (action: Action) => void;

View file

@ -1,24 +0,0 @@
export interface Meal {
mealName: string;
imgAddress: string;
videoAddress: string;
mealCategory: string;
mealArea: string;
isFav?: boolean;
}
export interface MealSummary {
idMeal: string;
strMeal: string;
strMealThumb: string;
}
export interface MealApi {
idMeal: string;
strMeal: string;
strMealThumb: string;
strYoutube: string;
strCategory: string;
strArea: string;
strInstructions: string;
}

View file

@ -1,2 +0,0 @@
import { createBrowserHistory } from "history";
export default createBrowserHistory();

View file

@ -1,43 +0,0 @@
// This must be set on the server using or firebase functions
import { createTransport } from "nodemailer";
import { mailAdress, mailPassword } from "./secret";
const myMail = mailAdress;
const myPass = mailPassword;
const handleMail = (mailTo, subject, text) => {
let transporter = createTransport({
service: "gmail",
auth: {
user: myMail,
pass: myPass,
},
});
let mailOptions = {
from: myMail,
to: mailTo,
subject: subject,
text: text,
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
};
export const confirmationMail = (
mailTo,
subject = "Your message has been sent",
text = "Thanks for your message. We'll reply you soon."
) => {
handleMail(mailTo, subject, text);
};
export const notificationMail = (mailTo = myMail, subject, text) => {
handleMail(mailTo, subject, text);
};

View file

@ -1,2 +0,0 @@
export const upFirstChar = (lower: string): string =>
lower.replace(/^\w/, (c) => c.toUpperCase());