├── .prettierrc.json
├── nodemon.json
├── .eslintignore
├── client
├── src
│ ├── components
│ │ ├── Header
│ │ │ ├── index.js
│ │ │ ├── Header.stories.js
│ │ │ └── Header.js
│ │ ├── Corner
│ │ │ ├── Corner.stories.js
│ │ │ └── Corner.js
│ │ ├── BottomNav
│ │ │ ├── BottomNav.stories.js
│ │ │ ├── IconButton.stories.js
│ │ │ ├── MenueButton.stories.js
│ │ │ ├── IconButton.js
│ │ │ ├── MenueButton.js
│ │ │ └── BottomNav.js
│ │ ├── InputField
│ │ │ ├── InputField.stories.js
│ │ │ └── InputField.js
│ │ ├── OverlayItems
│ │ │ ├── OverlayItem.stories.js
│ │ │ └── OverlayItem.js
│ │ ├── RecipeList
│ │ │ ├── RecipeList.stories.js
│ │ │ ├── RecipeListItem.stories.js
│ │ │ ├── RecipeList.js
│ │ │ └── RecipeListItem.js
│ │ ├── GroceryList
│ │ │ ├── GroceryList.stories.js
│ │ │ ├── GroceryListItem.stories.js
│ │ │ ├── HandleData.js
│ │ │ ├── GroceryList.js
│ │ │ └── GroceryListItem.js
│ │ ├── WeekListItem
│ │ │ ├── WeekListItem.stories.js
│ │ │ ├── WeekListItem.js
│ │ │ └── DatePicker.css
│ │ ├── Recipe
│ │ │ ├── RecipeIngredients.stories.js
│ │ │ ├── RecipePreparation.stories.js
│ │ │ ├── RecipePreparation.js
│ │ │ └── RecipeIngredients.js
│ │ └── styledComponents
│ │ │ ├── LayoutPages.js
│ │ │ └── Lines.js
│ ├── pages
│ │ ├── WeeklyPage
│ │ │ ├── index.js
│ │ │ ├── WeeklyPage.stories.js
│ │ │ └── WeeklyPage.js
│ │ ├── OverlayMenue
│ │ │ ├── Overlay.stories.js
│ │ │ └── Overlay.js
│ │ ├── EmptyPage
│ │ │ ├── EmptyPage.stories.js
│ │ │ └── EmptyPage.js
│ │ ├── SplashPage
│ │ │ ├── SplashPage.stories.js
│ │ │ └── SplashPage.js
│ │ ├── RecipeListPage
│ │ │ ├── RecipeListPage.stories.js
│ │ │ └── RecipeListPage.js
│ │ ├── SingleRecipePage
│ │ │ ├── SingleRecipePage.stories.js
│ │ │ └── SingleRecipePage.js
│ │ └── GroceryListPage
│ │ │ ├── GroceryListPage.stories.js
│ │ │ └── GroceryListPage.js
│ ├── assets
│ │ ├── favicon.png
│ │ ├── diamond.svg
│ │ ├── deleteIcon.svg
│ │ ├── checkbox.svg
│ │ ├── diamondActive.svg
│ │ ├── checkboxChecked.svg
│ │ ├── mainButton.svg
│ │ ├── calendarIcon.svg
│ │ ├── calendarCheck.svg
│ │ ├── home.svg
│ │ ├── calendarFilled.svg
│ │ ├── recipeIcon.svg
│ │ ├── corner.svg
│ │ └── grocerieIcon.svg
│ ├── App.test.js
│ ├── setupTests.js
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── App.js
│ ├── GlobalStyle.js
│ ├── api
│ │ └── connectJSON.js
│ ├── service-worker.js
│ └── serviceWorkerRegistration.js
├── public
│ ├── robots.txt
│ ├── manifest.json
│ ├── favicon.svg
│ └── index.html
├── .storybook
│ ├── main.js
│ └── preview.js
├── .gitignore
├── package.json
└── README.md
├── .prettierignore
├── lib
├── database.js
├── connectRecipes.js
├── helpFunctions.js
├── connectWeek.js
└── connectShoppingList.js
├── .eslintrc.json
├── .github
└── workflows
│ └── node.js.yml
├── README.md
├── .gitignore
├── package.json
└── server.js
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": ["client/*"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/
2 | **/build/
3 | **/storybook-static/
--------------------------------------------------------------------------------
/client/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export { Header } from "./Header";
2 |
--------------------------------------------------------------------------------
/client/src/pages/WeeklyPage/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Page";
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | **/build/
3 | **/coverage/
4 | **/storybook-static/
5 | db.json
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boldCM/AppEatIt/HEAD/client/src/assets/favicon.png
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react";
2 | import React from "react";
3 | import App from "./App";
4 |
5 | test("renders App", () => {
6 | render();
7 | });
8 |
--------------------------------------------------------------------------------
/client/src/pages/OverlayMenue/Overlay.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Overlay from "./Overlay";
3 |
4 | export default {
5 | title: "Page",
6 | component: Overlay,
7 | };
8 |
9 | export const overlay = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
3 | addons: [
4 | "@storybook/addon-links",
5 | "@storybook/addon-essentials",
6 | "@storybook/preset-create-react-app",
7 | ],
8 | };
9 |
--------------------------------------------------------------------------------
/client/src/components/Header/Header.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Header } from "./Header";
4 |
5 | export default {
6 | title: "Components",
7 | component: Header,
8 | };
9 |
10 | export const header = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/pages/EmptyPage/EmptyPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import EmptyPage from "./EmptyPage";
3 |
4 | export default {
5 | title: "Page",
6 | component: EmptyPage,
7 | };
8 |
9 | export const emptyPage = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom";
6 |
--------------------------------------------------------------------------------
/client/src/components/Corner/Corner.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CornerElement from "./Corner";
3 |
4 | export default {
5 | title: "Components",
6 | component: CornerElement,
7 | };
8 |
9 | export const corner = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/src/pages/SplashPage/SplashPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import SplashPage from "./SplashPage";
4 |
5 | export default {
6 | title: "Page",
7 | component: SplashPage,
8 | };
9 |
10 | export const splashPage = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/pages/WeeklyPage/WeeklyPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import WeeklyPage from "./WeeklyPage";
4 |
5 | export default {
6 | title: "Page",
7 | component: WeeklyPage,
8 | };
9 |
10 | export const weeklyPage = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/BottomNav.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import BottomNav from "./BottomNav";
4 |
5 | export default {
6 | title: "Components",
7 | component: BottomNav,
8 | };
9 |
10 | export const bottomNav = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/IconButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import IconButton from "./IconButton";
3 |
4 | export default {
5 | title: "Components",
6 | component: IconButton,
7 | };
8 |
9 | export const iconButton = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/src/components/InputField/InputField.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import InputField from "./InputField";
4 |
5 | export default {
6 | title: "Components",
7 | component: InputField,
8 | };
9 |
10 | export const inputField = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/OverlayItems/OverlayItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import OverlayItem from "./OverlayItem";
3 |
4 | export default {
5 | title: "Components",
6 | component: OverlayItem,
7 | };
8 |
9 | export const overlayItem = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/src/components/RecipeList/RecipeList.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RecipeList from "./RecipeList";
4 |
5 | export default {
6 | title: "Components",
7 | component: RecipeList,
8 | };
9 |
10 | export const recipeList = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/MenueButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import MenueButton from "./MenueButton";
4 |
5 | export default {
6 | title: "Components",
7 | component: MenueButton,
8 | };
9 |
10 | export const menueButton = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/GroceryList/GroceryList.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import GroceryList from "./GroceryList";
4 |
5 | export default {
6 | title: "Components",
7 | component: GroceryList,
8 | };
9 |
10 | export const groceryList = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/WeekListItem/WeekListItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { WeekListItem } from "./WeekListItem";
3 |
4 | export default {
5 | title: "Components",
6 | component: WeekListItem,
7 | };
8 |
9 | export const weekListItem = (args) => ;
10 |
--------------------------------------------------------------------------------
/client/src/pages/RecipeListPage/RecipeListPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RecipeListPage from "./RecipeListPage";
4 |
5 | export default {
6 | title: "Page",
7 | component: RecipeListPage,
8 | };
9 |
10 | export const recipeListPage = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/pages/SingleRecipePage/SingleRecipePage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import SingleRecipe from "./SingleRecipePage";
4 |
5 | export default {
6 | title: "Page",
7 | component: SingleRecipe,
8 | };
9 |
10 | export const singleRecipe = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/RecipeList/RecipeListItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RecipeListItem from "./RecipeListItem";
4 |
5 | export default {
6 | title: "Components",
7 | component: RecipeListItem,
8 | };
9 |
10 | export const recipeListItem = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/pages/GroceryListPage/GroceryListPage.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import GroceryListPage from "./GroceryListPage";
4 |
5 | export default {
6 | title: "Page",
7 | component: GroceryListPage,
8 | };
9 |
10 | export const groceryListPage = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/GroceryList/GroceryListItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import GroceryListItem from "./GroceryListItem";
4 |
5 | export default {
6 | title: "Components",
7 | component: GroceryListItem,
8 | };
9 |
10 | export const groceryListItem = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/Recipe/RecipeIngredients.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RecipeIngredients from "./RecipeIngredients";
4 |
5 | export default {
6 | title: "Components",
7 | component: RecipeIngredients,
8 | };
9 |
10 | export const recipeIngredients = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/src/components/Recipe/RecipePreparation.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import RecipePreparation from "./RecipePreparation";
4 |
5 | export default {
6 | title: "Components",
7 | component: RecipePreparation,
8 | };
9 |
10 | export const recipePreparation = (args) => ;
11 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "AppEatIt",
3 | "name": "AppEatIt",
4 | "icons": [
5 | {
6 | "src": "favicon.svg",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/components/styledComponents/LayoutPages.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components/macro";
2 |
3 | const Layout = styled.div`
4 | padding-bottom: 120px;
5 | height: 100vh;
6 | `;
7 |
8 | const ContentContainer = styled.div`
9 | overflow: auto;
10 | max-height: 100%;
11 | overscroll-behavior: contain;
12 | `;
13 |
14 | export { Layout, ContentContainer };
15 |
--------------------------------------------------------------------------------
/client/src/pages/EmptyPage/EmptyPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import BottomNav from "../../components/BottomNav/BottomNav";
3 | import CornerElement from "../../components/Corner/Corner";
4 |
5 | const EmptyPage = () => {
6 | return (
7 | <>
8 |
9 |
10 | >
11 | );
12 | };
13 |
14 | export default EmptyPage;
15 |
--------------------------------------------------------------------------------
/client/src/assets/diamond.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/client/src/assets/deleteIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/client/src/assets/checkbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /storybook-static
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/client/src/assets/diamondActive.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/client/src/assets/checkboxChecked.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/client/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import GlobalStyle from "../src/GlobalStyle";
3 | import { MemoryRouter } from "react-router-dom";
4 |
5 | export const decorators = [
6 | (Story) => (
7 |
8 |
9 |
10 |
11 | ),
12 | ];
13 |
14 | export const parameters = {
15 | actions: { argTypesRegex: "^on[A-Z].*" },
16 | layout: "fullscreen",
17 | };
18 |
19 | // für die Preview im Storybook
20 |
--------------------------------------------------------------------------------
/lib/database.js:
--------------------------------------------------------------------------------
1 | const { MongoClient } = require("mongodb");
2 |
3 | let client;
4 | let db;
5 |
6 | async function connect(url, dbName) {
7 | client = await MongoClient.connect(url, {
8 | useUnifiedTopology: true,
9 | });
10 | db = client.db(dbName);
11 | }
12 |
13 | // brauche ich momentan nicht
14 | function close() {
15 | return client.close();
16 | }
17 |
18 | function collection(name) {
19 | return db.collection(name);
20 | }
21 |
22 | exports.connect = connect;
23 | exports.close = close;
24 | exports.collection = collection;
25 |
--------------------------------------------------------------------------------
/client/src/pages/RecipeListPage/RecipeListPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import BottomNav from "../../components/BottomNav/BottomNav";
3 | import { Header } from "../../components/Header";
4 | import RecipeList from "../../components/RecipeList/RecipeList";
5 | import { Layout } from "../../components/styledComponents/LayoutPages";
6 |
7 | const RecipeListPage = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 | export default RecipeListPage;
17 |
--------------------------------------------------------------------------------
/client/src/components/Recipe/RecipePreparation.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import PropTypes from "prop-types";
4 |
5 | const Preparation = styled.p`
6 | padding: 0 20px 60px 0;
7 | line-height: 1.5rem;
8 | `;
9 |
10 | const RecipePreparation = ({ Instructions }) => {
11 | return (
12 | <>
13 |
Zubereitung
14 | {Instructions}
15 | >
16 | );
17 | };
18 |
19 | RecipePreparation.propTypes = {
20 | Instructions: PropTypes.string,
21 | };
22 | export default RecipePreparation;
23 |
--------------------------------------------------------------------------------
/client/src/pages/GroceryListPage/GroceryListPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import BottomNav from "../../components/BottomNav/BottomNav";
3 | import GroceryList from "../../components/GroceryList/GroceryList";
4 |
5 | import { Header } from "../../components/Header";
6 | import {
7 | Layout,
8 | ContentContainer,
9 | } from "../../components/styledComponents/LayoutPages";
10 |
11 | const GroceryListPage = () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default GroceryListPage;
24 |
--------------------------------------------------------------------------------
/lib/connectRecipes.js:
--------------------------------------------------------------------------------
1 | const { collection } = require("./database");
2 | const { findOneElement, findAndSort } = require("./helpFunctions");
3 |
4 | async function getRecipiesMongo() {
5 | return findAndSort("recipes", { RecipeName: 1 });
6 | }
7 |
8 | async function getRecipeByNameMongo(recipeName) {
9 | return findOneElement("recipes", { RecipeName: recipeName });
10 | }
11 |
12 | async function insertRecipeInWeekMongo(wholeRecipe) {
13 | await collection("week").insertOne({
14 | ...wholeRecipe,
15 | });
16 | }
17 |
18 | exports.getRecipiesMongo = getRecipiesMongo;
19 | exports.getRecipeByNameMongo = getRecipeByNameMongo;
20 | exports.insertRecipeInWeekMongo = insertRecipeInWeekMongo;
21 |
--------------------------------------------------------------------------------
/client/src/assets/mainButton.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/client/src/components/GroceryList/HandleData.js:
--------------------------------------------------------------------------------
1 | const HandleData = (recipeObject) => {
2 | const necessaryIngredients = recipeObject.filter((object) => {
3 | return !["Salz", "Wasser", "Olivenöl", "Öl", "Pfeffer", "Zucker"].includes(
4 | object.Grocery
5 | );
6 | });
7 |
8 | if (necessaryIngredients.length > 0) {
9 | necessaryIngredients.forEach(() => {
10 | const index = necessaryIngredients.findIndex((item) => {
11 | return item.Quantity === 0;
12 | });
13 | if (index > 0) {
14 | Object.assign((necessaryIngredients[index].Quantity = "etwas"));
15 | }
16 | });
17 | }
18 |
19 | return necessaryIngredients;
20 | };
21 |
22 | export { HandleData };
23 |
--------------------------------------------------------------------------------
/client/src/assets/calendarIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/client/src/components/styledComponents/Lines.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components/macro";
2 |
3 | const DiamondLine = styled.hr`
4 | width: 80px;
5 | margin: 0 7px 0 -2px;
6 | background-color: var(--secondary-color);
7 | @media screen and (max-width: 400px) {
8 | width: 60px;
9 | }
10 | `;
11 |
12 | const OverlayLine = styled(DiamondLine)`
13 | margin: 0 10px 0 auto;
14 | `;
15 |
16 | const UnderlineHeader = styled.hr`
17 | background-color: var(--active-color);
18 | width: 100px;
19 | margin-top: -7px;
20 | /* spacing looked best with the odd number of -7px */
21 | `;
22 |
23 | const UnderlineHeaderOverlay = styled(UnderlineHeader)`
24 | margin: -7px 0 20px auto;
25 | `;
26 |
27 | export { DiamondLine, OverlayLine, UnderlineHeader, UnderlineHeaderOverlay };
28 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "commonjs": true,
6 | "node": true
7 | },
8 | "extends": [
9 | "react-app",
10 | "eslint:recommended",
11 | "prettier",
12 | "plugin:react/recommended"
13 | ],
14 | "parserOptions": {
15 | "ecmaFeatures": {
16 | "jsx": true
17 | },
18 | "ecmaVersion": 12,
19 | "sourceType": "module"
20 | },
21 | "settings": {
22 | "react": {
23 | "version": "latest"
24 | }
25 | },
26 | "plugins": ["react"],
27 | "rules": {
28 | "no-unused-vars": [
29 | "warn",
30 | { "vars": "all", "args": "after-used", "ignoreRestSiblings": true }
31 | ],
32 | "react/no-unescaped-entities": "off",
33 | "import/no-anonymous-default-export": "off"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/IconButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components/macro";
4 |
5 | const StyledButton = styled.button`
6 | display: flex;
7 | align-items: center;
8 | font-size: 1.1rem;
9 | font-weight: 300;
10 | img {
11 | padding-left: 7px;
12 | }
13 | `;
14 |
15 | const IconButton = ({ iconSrc, iconAlt, onClick, content }) => {
16 | return (
17 |
18 | {content}
19 |
20 |
21 | );
22 | };
23 |
24 | export default IconButton;
25 |
26 | IconButton.propTypes = {
27 | iconSrc: PropTypes.string,
28 | iconAlt: PropTypes.string,
29 | onClick: PropTypes.func,
30 | content: PropTypes.string,
31 | };
32 |
--------------------------------------------------------------------------------
/client/src/components/Corner/Corner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import Corner from "../../assets/corner.svg";
4 |
5 | const CornerContainer = styled.div`
6 | background-color: transparent;
7 | position: fixed;
8 | top: 0;
9 | right: 0;
10 | `;
11 |
12 | const WhiteCorner = styled.div`
13 | background-color: white;
14 | width: 57px;
15 | height: 57px;
16 | position: fixed;
17 | top: 0;
18 | right: 0;
19 | z-index: -1;
20 | `;
21 |
22 | const FoldedCorner = styled.img`
23 | margin-right: -5px;
24 | margin-top: -3px;
25 | `;
26 |
27 | const CornerElement = () => {
28 | return (
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default CornerElement;
37 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [main]
9 | pull_request:
10 | branches: [main]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [10.x, 12.x, 14.x]
19 |
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - run: npm ci
27 | - run: npm run build --if-present
28 | - run: npm test
29 |
--------------------------------------------------------------------------------
/lib/helpFunctions.js:
--------------------------------------------------------------------------------
1 | const { collection } = require("./database");
2 |
3 | async function findAndSort(collectionName, argument) {
4 | return await collection(collectionName).find({}).sort(argument).toArray();
5 | }
6 |
7 | // not sure if findOne and deleteOne are actually making it shorter?
8 | async function findOneElement(collectionName, argument) {
9 | return await collection(collectionName).findOne(argument);
10 | }
11 |
12 | async function deleteOneElement(collectionName, argument) {
13 | return await collection(collectionName).deleteOne(argument);
14 | }
15 |
16 | async function deleteAllElements(collectionName) {
17 | return await collection(collectionName).deleteMany({});
18 | }
19 |
20 | exports.findOneElement = findOneElement;
21 | exports.findAndSort = findAndSort;
22 | exports.deleteOneElement = deleteOneElement;
23 | exports.deleteAllElements = deleteAllElements;
24 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import * as serviceWorkerRegistration from "./serviceWorkerRegistration";
5 | import reportWebVitals from "./reportWebVitals";
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById("root")
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://cra.link/PWA
17 | serviceWorkerRegistration.unregister();
18 |
19 | // If you want to start measuring performance in your app, pass a function
20 | // to log results (for example: reportWebVitals(console.log))
21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
22 | reportWebVitals();
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # AppEatIt 
4 |
5 | Final project for Web Dev Bootcamp with Neue Fische Cologne.
6 |
7 | You know this situation where you have appetite but the fridge seems to be empty? Well, here's a solution: AppEatIt.
8 | With this App you can organize your meals in advance. Pick the recipes, choose a date when to cook them and get immediatly the shoppinglist for the needed ingrediences. Plus you can edit your shopping list and add f.e. toiletpaper
9 |
10 | ## Deployment
11 |
12 | This App is deployed on Heroku: [AppEatIt](https://appeatit.herokuapp.com/)
13 |
14 | ## Install all dependencies
15 |
16 | To use this code, type `npm install`. There is a postinstall-script which searches the client folder automatically and installs the required dependencies as well.
17 |
--------------------------------------------------------------------------------
/lib/connectWeek.js:
--------------------------------------------------------------------------------
1 | const { collection } = require("./database");
2 | const {
3 | deleteOneElement,
4 | findOneElement,
5 | findAndSort,
6 | } = require("./helpFunctions");
7 |
8 | async function getRecipeByNameWeekMongo(recipeName) {
9 | return await findOneElement("week", { RecipeName: recipeName });
10 | }
11 |
12 | async function deleteRecipeInWeekMongo(recipeName) {
13 | return await deleteOneElement("week", { RecipeName: recipeName });
14 | }
15 |
16 | async function getWeekMongo() {
17 | return findAndSort("week", { date: 1 });
18 | }
19 |
20 | async function updateRecipeInWeekMongo(date, id) {
21 | await collection("week").updateOne({ _id: id }, { $set: date });
22 | }
23 |
24 | exports.getRecipeByNameWeekMongo = getRecipeByNameWeekMongo;
25 | exports.deleteRecipeInWeekMongo = deleteRecipeInWeekMongo;
26 | exports.getWeekMongo = getWeekMongo;
27 | exports.updateRecipeInWeekMongo = updateRecipeInWeekMongo;
28 |
--------------------------------------------------------------------------------
/client/src/assets/calendarCheck.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/client/src/assets/home.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/client/src/pages/SplashPage/SplashPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import DiamondActiveSrc from "../../assets/diamondActive.svg";
3 | import { Diamond } from "../../components/WeekListItem/WeekListItem";
4 | import styled from "styled-components/macro";
5 |
6 | const CenterLogo = styled.div`
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | height: 100vh;
11 | `;
12 |
13 | const AnimatedDiamond = styled(Diamond)`
14 | background-image: url(${DiamondActiveSrc});
15 | height: 10rem;
16 | width: 10rem;
17 | padding: 0 5px 15px 0;
18 | animation: turner 2.5s ease-in alternate 1 none running;
19 | }
20 | @keyframes turner {
21 | 0%, 100% {
22 | transform: scale(1) ;
23 | }
24 | 25%{
25 | transform: scale(1.5) rotate(-360deg);
26 | }
27 |
28 | 75%{
29 | transform: scale(0.8);
30 | }
31 | }
32 | `;
33 |
34 | const Splashpage = () => {
35 | return (
36 |
37 | AppEatIt
38 |
39 | );
40 | };
41 |
42 | export default Splashpage;
43 |
--------------------------------------------------------------------------------
/client/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import CornerElement from "../Corner/Corner";
4 | import PropTypes from "prop-types";
5 | import { UnderlineHeader } from "../styledComponents/Lines";
6 |
7 | const ContainerHeader = styled.header`
8 | /* background: linear-gradient(var(--secondary-color), #f3d7b4); */
9 | /* display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center; */
13 | /* width: 100%; */
14 | /* top: 0; */
15 | display: grid;
16 | grid-row: 1/2;
17 | h1 {
18 | text-align: center;
19 | width: 100%;
20 | padding-right: 54px;
21 | padding-left: 54px;
22 | }
23 | `;
24 |
25 | const Header = ({ title }) => {
26 | return (
27 | <>
28 |
29 | {title}
30 |
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export { Header, ContainerHeader };
38 |
39 | Header.propTypes = {
40 | title: PropTypes.string,
41 | };
42 |
--------------------------------------------------------------------------------
/client/src/components/RecipeList/RecipeList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components/macro";
3 | import { getRecipies } from "../../api/connectJSON";
4 | import RecipeListItem from "./RecipeListItem";
5 | import { ContentContainer } from "../styledComponents/LayoutPages";
6 |
7 | const ListContainer = styled(ContentContainer)`
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | text-align: center;
12 | padding-bottom: 56px;
13 | hr {
14 | margin: 0;
15 | }
16 | `;
17 |
18 | const RecipeList = () => {
19 | const [recipes, setRecipes] = useState([]);
20 |
21 | useEffect(() => {
22 | async function fetchData() {
23 | const getRecipes = await getRecipies();
24 | setRecipes(getRecipes);
25 | }
26 | fetchData();
27 | }, []);
28 |
29 | return (
30 |
31 | {recipes?.map((recipes) => (
32 |
33 | ))}
34 |
35 | );
36 | };
37 |
38 | export default RecipeList;
39 |
--------------------------------------------------------------------------------
/client/src/components/Recipe/RecipeIngredients.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import PropTypes from "prop-types";
4 |
5 | const Ingredient = styled.ul`
6 | margin-bottom: 25px;
7 | li {
8 | padding: 0.3rem 0;
9 | margin-left: -38px;
10 | }
11 | `;
12 |
13 | const RecipeIngredients = ({ Ingredients }) => {
14 | if (Ingredients.length > 0) {
15 | const index = Ingredients.findIndex((item) => {
16 | return item.Quantity === 0;
17 | });
18 | if (index > 0) {
19 | Object.assign((Ingredients[index].Quantity = "etwas"));
20 | }
21 | }
22 |
23 | return (
24 | <>
25 | Zutaten
26 |
27 | {Ingredients?.map((ingredient) => (
28 |
29 | {ingredient.Quantity +
30 | " " +
31 | ingredient.Unit +
32 | " " +
33 | ingredient.Grocery}
34 |
35 | ))}
36 |
37 | >
38 | );
39 | };
40 |
41 | RecipeIngredients.propTypes = {
42 | Ingredients: PropTypes.array,
43 | };
44 |
45 | export default RecipeIngredients;
46 |
--------------------------------------------------------------------------------
/client/src/components/GroceryList/GroceryList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components/macro";
3 | import GroceryListItem from "./GroceryListItem";
4 | import { checkShoppingList } from "../../api/connectJSON";
5 | import { HandleData } from "./HandleData";
6 |
7 | const ContainerGroceries = styled.div`
8 | display: flex;
9 | flex-direction: column;
10 | margin-left: 10px;
11 | `;
12 |
13 | const GroceryList = () => {
14 | const [recipeObject, setRecipeObject] = useState([]);
15 |
16 | useEffect(() => {
17 | async function doFetch() {
18 | const checkList = await checkShoppingList();
19 | setRecipeObject(checkList);
20 | return;
21 | }
22 | doFetch();
23 | }, []);
24 | const calculatedArray = HandleData(recipeObject);
25 |
26 | return (
27 |
28 | {calculatedArray?.map((object) => (
29 |
36 | ))}
37 |
38 | );
39 | };
40 |
41 | export default GroceryList;
42 |
--------------------------------------------------------------------------------
/client/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/client/src/assets/calendarFilled.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/client/src/assets/recipeIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import GlobalStyle from "./GlobalStyle";
3 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
4 | import WeeklyPage from "./pages/WeeklyPage/WeeklyPage";
5 | import SingleRecipe from "./pages/SingleRecipePage/SingleRecipePage";
6 | import RecipeListPage from "./pages/RecipeListPage/RecipeListPage";
7 | import GroceryListPage from "./pages/GroceryListPage/GroceryListPage";
8 | import Splashpage from "./pages/SplashPage/SplashPage";
9 |
10 | function App() {
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | setTimeout(() => setLoading(false), 4100);
15 | });
16 |
17 | return (
18 | <>
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {loading ? : }
35 |
36 |
37 | >
38 | );
39 | }
40 |
41 | export default App;
42 |
--------------------------------------------------------------------------------
/client/src/pages/OverlayMenue/Overlay.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CornerElement from "../../components/Corner/Corner";
3 | import styled from "styled-components/macro";
4 | import OverlayItem from "../../components/OverlayItems/OverlayItem";
5 | import PropTypes from "prop-types";
6 |
7 | const Blur = styled.div`
8 | width: 100%;
9 | height: 100%;
10 | background: var(--gradient-color);
11 | position: fixed;
12 | opacity: 0;
13 | top: 0;
14 | right: 0;
15 | backdrop-filter: blur(10px);
16 | ${({ open }) => (!open ? "opacity:0; visibility:hidden;" : "opacity:0.7;")};
17 | transition: opacity 0.3s ease-in-out;
18 | `;
19 |
20 | const ContainerOverlay = styled.div`
21 | right: 0;
22 | top: 0;
23 | text-align: right;
24 | transform: ${({ open }) => (open ? "translateX(0);" : "translateX(100%);")};
25 | transition: transform 0.3s ease-in-out;
26 | display: flex;
27 | flex-direction: column;
28 | justify-content: center;
29 | height: 100%;
30 | width: max-content;
31 | position: fixed;
32 | `;
33 |
34 | const Overlay = ({ open }) => {
35 | return (
36 | <>
37 |
38 |
39 |
40 |
41 |
42 | >
43 | );
44 | };
45 |
46 | Overlay.propTypes = {
47 | open: PropTypes.bool,
48 | };
49 |
50 | export default Overlay;
51 |
--------------------------------------------------------------------------------
/client/src/GlobalStyle.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components/macro";
2 |
3 | const GlobalStyle = createGlobalStyle`
4 |
5 |
6 |
7 | *,*::before,*::after{
8 | box-sizing:border-box
9 | }
10 |
11 | :root {
12 | --gradient-color: linear-gradient(#F2D3AC, #FFFFFF );
13 | --active-color: #037F8C;
14 | --primary-color: #F8E4CF;
15 | --secondary-color: #F2D3AC;
16 | --text-color: #262626
17 | }
18 |
19 |
20 | button {
21 | border: none;
22 | background: none;
23 | font-family: 'Encode Sans', sans-serif;
24 | -webkit-font-smoothing: antialiased;
25 | -moz-osx-font-smoothing: grayscale;
26 | }
27 |
28 | button:focus {
29 | outline: none;
30 | }
31 |
32 | body {
33 | font-family: 'Encode Sans', sans-serif;
34 | -webkit-font-smoothing: antialiased;
35 | -moz-osx-font-smoothing: grayscale;
36 | font-size: 16px;
37 | font-weight: 300;
38 | background: var(--gradient-color);
39 | background-repeat: no-repeat;
40 | background-attachment: fixed;
41 | margin: 0;
42 | color: var(--text-color);
43 | height: 100vh;
44 | overflow-x: hidden;
45 | }
46 |
47 |
48 |
49 |
50 | h1{
51 | font-size: 24px;
52 | font-weight: 500;
53 | }
54 |
55 | h2{
56 | font-size: 18.72px;
57 | font-weight: 400;
58 | }
59 | a {
60 | text-decoration: none;
61 | color: var(--text-color)
62 | }
63 | hr{
64 | border: none;
65 | height: 1px;
66 | }
67 |
68 | ul {
69 | list-style-type:none;
70 | }
71 |
72 | `;
73 |
74 | export default GlobalStyle;
75 |
--------------------------------------------------------------------------------
/lib/connectShoppingList.js:
--------------------------------------------------------------------------------
1 | const { collection } = require("./database");
2 | const { deleteOneElement } = require("./helpFunctions");
3 |
4 | async function getShoppingListMongo() {
5 | return await collection("shoppingList").find({}).toArray();
6 | }
7 |
8 | async function insertShoppingItemsMongo(ingredients) {
9 | ingredients.forEach(async (object) => {
10 | await collection("shoppingList").findOneAndUpdate(
11 | // query:
12 | { Grocery: object.Grocery },
13 | // update: []=> symbolisieren Pipeline:
14 | [
15 | // als erstes brauche ich einen operator für mein update:
16 | {
17 | $set: {
18 | // dann möchte ich die Quantity addieren, falls es sie schon gibt.
19 | Quantity: { $add: [object.Quantity, "$Quantity"] },
20 | },
21 | },
22 | {
23 | $set: {
24 | Quantity: { $ifNull: ["$Quantity", object.Quantity] },
25 | },
26 | },
27 | { $set: { Unit: object.Unit } },
28 | ],
29 | { upsert: true },
30 | { returnNewDocument: true }
31 | );
32 | });
33 | }
34 |
35 | async function deleteShoppingItemMongo(shoppingItem) {
36 | return await deleteOneElement("shoppingList", { Grocery: shoppingItem });
37 | }
38 |
39 | exports.getShoppingListMongo = getShoppingListMongo;
40 | exports.insertShoppingItemsMongo = insertShoppingItemsMongo;
41 | exports.deleteShoppingItemMongo = deleteShoppingItemMongo;
42 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 |
32 | AppEATit
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/client/src/assets/corner.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/client/src/pages/SingleRecipePage/SingleRecipePage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import BottomNav from "../../components/BottomNav/BottomNav";
3 | import { Header } from "../../components/Header";
4 | import RecipeIngredients from "../../components/Recipe/RecipeIngredients";
5 | import RecipePreparation from "../../components/Recipe/RecipePreparation";
6 | import {
7 | Layout,
8 | ContentContainer,
9 | } from "../../components/styledComponents/LayoutPages";
10 | import styled from "styled-components/macro";
11 | import { getRecipeByRecipeName } from "../../api/connectJSON";
12 | import { useParams } from "react-router-dom";
13 |
14 | const RecipeLayout = styled(ContentContainer)`
15 | padding-left: 20px;
16 | `;
17 |
18 | const SingleRecipe = () => {
19 | const { RecipeName } = useParams();
20 |
21 | const [ingredients, setIngredients] = useState([]);
22 | const [instructions, setInstructions] = useState("");
23 |
24 | useEffect(() => {
25 | async function fetchData() {
26 | const result = await getRecipeByRecipeName(RecipeName);
27 | const { Ingredients, Instructions } = result;
28 | setIngredients(Ingredients);
29 | setInstructions(Instructions);
30 | }
31 | fetchData();
32 | }, [RecipeName]);
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default SingleRecipe;
47 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/MenueButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import { useState } from "react";
4 | import DiamondButton from "../../assets/mainButton.svg";
5 | import Overlay from "../../pages/OverlayMenue/Overlay";
6 |
7 | const MainButton = styled.button`
8 | background: none;
9 | border: none;
10 | bottom: 0;
11 | right: 0;
12 | position: fixed;
13 | z-index: 2;
14 | `;
15 |
16 | const MainButtonImg = styled.img`
17 | margin-right: 11px;
18 | margin-bottom: 12px;
19 | `;
20 |
21 | const Triangle = styled.div`
22 | width: 0;
23 | height: 0;
24 | border-left: 45px solid transparent;
25 | border-right: 45px solid transparent;
26 | border-top: 45px solid white;
27 | background-color: transparent;
28 | bottom: 0;
29 | right: 0;
30 | margin-right: 12px;
31 | margin-bottom: 11px;
32 | position: fixed;
33 | z-index: 2;
34 | `;
35 |
36 | const BorderTraingle = styled.div`
37 | width: 0;
38 | height: 0;
39 | border-left: 46px solid transparent;
40 | border-right: 46px solid transparent;
41 | border-top: 46px solid var(--active-color);
42 | background-color: transparent;
43 | bottom: 0;
44 | right: 0;
45 | margin-right: 11px;
46 | margin-bottom: 9px;
47 | position: fixed;
48 | z-index: 1;
49 | `;
50 |
51 | const MenueButton = () => {
52 | const [open, setOpen] = useState(false);
53 | return (
54 | <>
55 |
56 |
57 | setOpen(!open)}>
58 |
59 |
60 |
61 | >
62 | );
63 | };
64 |
65 | export default MenueButton;
66 |
--------------------------------------------------------------------------------
/client/src/components/BottomNav/BottomNav.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | // import IconButton from "./IconButton";
4 | // import DeleteIcon from "../../assets/deleteIcon.svg";
5 | // import ShareIcon from "../../assets/shareIcon.svg";
6 | import HomeIcon from "../../assets/home.svg";
7 | import MenueButton from "./MenueButton";
8 | import { Link } from "react-router-dom";
9 | import GroceryIcon from "../../assets/grocerieIcon.svg";
10 | import RecipeIcon from "../../assets/recipeIcon.svg";
11 |
12 | const Footer = styled.footer`
13 | position: fixed;
14 | bottom: 0;
15 | left: 0;
16 | width: 100%;
17 | `;
18 |
19 | const BottomNavBar = styled.div`
20 | height: 56px;
21 | background-color: var(--primary-color);
22 | border-top: 2px solid var(--active-color);
23 | display: flex;
24 | justify-content: space-around;
25 | padding-right: 6rem;
26 | `;
27 |
28 | const HomeButton = styled(Link)`
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | margin-bottom: 5px;
33 | `;
34 |
35 | const BottomNav = () => {
36 | return (
37 | <>
38 |
39 |
52 | >
53 | );
54 | };
55 |
56 | export default BottomNav;
57 |
--------------------------------------------------------------------------------
/client/src/pages/WeeklyPage/WeeklyPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import BottomNav from "../../components/BottomNav/BottomNav";
3 | import {
4 | Layout,
5 | ContentContainer,
6 | } from "../../components/styledComponents/LayoutPages";
7 | import { Header } from "../../components/Header";
8 | import { WeekListItem } from "../../components/WeekListItem/WeekListItem";
9 | import { getWeek } from "../../api/connectJSON";
10 | import styled from "styled-components/macro";
11 | import { Link } from "react-router-dom";
12 | import RecipeIcon from "../../assets/recipeIcon.svg";
13 |
14 | const LinkedItem = styled(Link)`
15 | margin: 1rem 0;
16 | text-align: center;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | margin-bottom: 5px;
21 | img {
22 | padding-left: 5px;
23 | }
24 | `;
25 |
26 | const WeeklyPage = () => {
27 | const [chosenRecipies, setChosenRecipies] = useState([]);
28 |
29 | useEffect(() => {
30 | async function fetchData() {
31 | const getChosenRecipies = await getWeek();
32 | setChosenRecipies(getChosenRecipies);
33 | }
34 | fetchData();
35 | }, []);
36 |
37 | return (
38 |
39 |
40 |
41 | {chosenRecipies.length <= 0 && (
42 |
43 | Choose a recipe
44 |
45 |
46 | )}
47 | {chosenRecipies?.map((recipe) => (
48 |
54 | ))}
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default WeeklyPage;
63 |
--------------------------------------------------------------------------------
/client/src/components/GroceryList/GroceryListItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useHistory, useParams } from "react-router-dom";
3 | import styled from "styled-components/macro";
4 | import IconButton from "../BottomNav/IconButton";
5 | import Checkbox from "../../assets/checkbox.svg";
6 | import CheckboxChecked from "../../assets/checkboxChecked.svg";
7 | import PropTypes from "prop-types";
8 | import DeleteIcon from "../../assets/deleteIcon.svg";
9 | import { deleteItemFromShoppingList } from "../../api/connectJSON";
10 |
11 | const ListItem = styled.p`
12 | display: inline-flex;
13 | align-items:center;
14 | margin: 0.6rem 0;
15 | button {
16 | margin: 1px 5px 0 0;
17 | }
18 | button:last-child{
19 | position: fixed;
20 | right: 5px;
21 | }
22 | }
23 | `;
24 |
25 | const GroceryListItem = ({ splittedGrocery, Grocery }) => {
26 | const history = useHistory();
27 | const { title } = useParams();
28 | const [ischecked, setIsChecked] = useState(false);
29 |
30 | const CheckboxSrc = !ischecked ? Checkbox : CheckboxChecked;
31 |
32 | const CheckboxAlt = !ischecked ? "Checkbox" : "Checkbox is ticked";
33 |
34 | const handleShoppingItemDelete = async () => {
35 | await deleteItemFromShoppingList(Grocery);
36 | console.log(typeof Grocery);
37 | console.log(Grocery);
38 | if (!title === "/Einkaufsliste") {
39 | history.push("/Einkaufsliste");
40 | } else {
41 | window.location.reload();
42 | }
43 | };
44 |
45 | return (
46 |
47 | setIsChecked(!ischecked)}
51 | />
52 | {splittedGrocery}
53 | handleShoppingItemDelete(Grocery)}
57 | />
58 |
59 | );
60 | };
61 |
62 | GroceryListItem.propTypes = {
63 | splittedGrocery: PropTypes.string,
64 | Grocery: PropTypes.string,
65 | };
66 |
67 | export default GroceryListItem;
68 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@babel/core": "^7.12.8",
7 | "@testing-library/jest-dom": "^5.11.6",
8 | "@testing-library/react": "^11.2.2",
9 | "@testing-library/user-event": "^12.2.2",
10 | "immer": "^9.0.6",
11 | "react": "^17.0.1",
12 | "react-datepicker": "^3.3.0",
13 | "react-dom": "^17.0.1",
14 | "react-is": "^17.0.1",
15 | "react-router-dom": "^5.2.0",
16 | "react-scripts": "5.0.1",
17 | "styled-components": "^5.2.1",
18 | "web-vitals": "^0.2.4",
19 | "workbox-background-sync": "^6.0.2",
20 | "workbox-broadcast-update": "^6.0.2",
21 | "workbox-cacheable-response": "^6.0.2",
22 | "workbox-core": "^6.0.2",
23 | "workbox-expiration": "^6.0.2",
24 | "workbox-google-analytics": "^6.0.2",
25 | "workbox-navigation-preload": "^6.0.2",
26 | "workbox-precaching": "^6.0.2",
27 | "workbox-range-requests": "^6.0.2",
28 | "workbox-routing": "^6.0.2",
29 | "workbox-strategies": "^6.0.2",
30 | "workbox-streams": "^6.0.2"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "test:watch": "react-scripts test",
36 | "test": "react-scripts test --watchAll=false",
37 | "eject": "react-scripts eject",
38 | "storybook": "start-storybook -p 6006 -s public",
39 | "build-storybook": "build-storybook -s public"
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | },
53 | "proxy": "http://localhost:3001",
54 | "devDependencies": {
55 | "@storybook/addon-actions": "^6.1.3",
56 | "@storybook/addon-essentials": "^6.5.13",
57 | "@storybook/addon-links": "^6.1.3",
58 | "@storybook/node-logger": "^6.1.3",
59 | "@storybook/preset-create-react-app": "^3.1.5",
60 | "@storybook/react": "^6.5.13"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 | db.json
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # TypeScript v1 declaration files
46 | typings/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Microbundle cache
58 | .rpt2_cache/
59 | .rts2_cache_cjs/
60 | .rts2_cache_es/
61 | .rts2_cache_umd/
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variables file
73 | .env
74 | .env.test
75 |
76 | # parcel-bundler cache (https://parceljs.org/)
77 | .cache
78 |
79 | # Next.js build output
80 | .next
81 |
82 | # Nuxt.js build / generate output
83 | .nuxt
84 | dist
85 |
86 | # Gatsby files
87 | .cache/
88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
89 | # https://nextjs.org/blog/next-9-1#public-directory-support
90 | # public
91 |
92 | # vuepress build output
93 | .vuepress/dist
94 |
95 | # Serverless directories
96 | .serverless/
97 |
98 | # FuseBox cache
99 | .fusebox/
100 |
101 | # DynamoDB Local files
102 | .dynamodb/
103 |
104 | # TernJS port file
105 | .tern-port
106 |
107 | # Prettier and esLint
108 | # .prettierignore
109 | # .eslintignore
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "starter-template",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "for starting new projects",
6 | "main": "server.js",
7 | "scripts": {
8 | "postinstall": "cd client && npm install",
9 | "build": "cd client && npm run build",
10 | "test:watch": "cd client && npm run test:watch",
11 | "test": "npm run lint && npm run prettier && cd client && npm test",
12 | "lint": "eslint . --ext .js",
13 | "prettify": "prettier --write .",
14 | "dev": "concurrently \"npm run server\" \"npm run client\" ",
15 | "client": "cd client && npm start",
16 | "prettier": "prettier --check \"**/*.{js,jsx,ts,tsx,md,mdx,html,css,json}\"",
17 | "server": "nodemon server.js",
18 | "storybook": "cd client && npm run storybook",
19 | "start": "node server.js"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/boldCM/Starter-Template.git"
24 | },
25 | "author": "Carolin Meier",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/boldCM/Starter-Template/issues"
29 | },
30 | "homepage": "https://github.com/boldCM/Starter-Template#readme",
31 | "devDependencies": {
32 | "@typescript-eslint/eslint-plugin": "^4.8.1",
33 | "@typescript-eslint/parser": "^4.8.1",
34 | "babel-eslint": "^10.1.0",
35 | "concurrently": "^5.3.0",
36 | "eslint": "^7.14.0",
37 | "eslint-config-prettier": "^6.15.0",
38 | "eslint-config-react-app": "^6.0.0",
39 | "eslint-plugin-flowtype": "^5.2.0",
40 | "eslint-plugin-import": "^2.22.1",
41 | "eslint-plugin-jsx-a11y": "^6.4.1",
42 | "eslint-plugin-react": "^7.21.5",
43 | "eslint-plugin-react-hooks": "^4.2.0",
44 | "husky": "^4.3.8",
45 | "lint-staged": "^10.5.1",
46 | "nodemon": "^2.0.20",
47 | "prettier": "^2.2.0",
48 | "typescript": "^4.1.2"
49 | },
50 | "husky": {
51 | "hooks": {
52 | "pre-commit": "lint-staged",
53 | "pre-push": "npm test"
54 | }
55 | },
56 | "lint-staged": {
57 | "*.js": "eslint --cache --fix",
58 | "*.{js,css,md}": "prettier --write"
59 | },
60 | "dependencies": {
61 | "dotenv": "^8.2.0",
62 | "express": "^4.18.2",
63 | "immer": "^9.0.6",
64 | "mongodb": "^3.6.3",
65 | "node": "^15.2.1"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/client/src/components/RecipeList/RecipeListItem.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { DiamondLine } from "../styledComponents/Lines";
3 | import styled from "styled-components/macro";
4 | import IconButton from "../BottomNav/IconButton";
5 | import CalendarCheck from "../../assets/calendarCheck.svg";
6 | import CalendarFilled from "../../assets/calendarFilled.svg";
7 | import PropTypes from "prop-types";
8 | import { Link } from "react-router-dom";
9 | import {
10 | addIngredients,
11 | deleteRecipeFromWeek,
12 | isRecipeInWeek,
13 | putRecipeInWeek,
14 | } from "../../api/connectJSON";
15 |
16 | const RecipeItem = styled.div`
17 | display: flex;
18 | a {
19 | margin: 1rem 0 0.8rem 0;
20 | }
21 | button {
22 | padding: 0;
23 | margin: 6px 0 0 8px;
24 | }
25 | `;
26 |
27 | const RecipeListItem = ({ WholeRecipe }) => {
28 | const [inCalender, setInCalender] = useState(null);
29 | const RecipeName = WholeRecipe.RecipeName;
30 |
31 | const startIsRecipeInWeek = async (RecipeName) =>
32 | await isRecipeInWeek(RecipeName);
33 |
34 | useEffect(() => {
35 | async function doFetch() {
36 | if (await startIsRecipeInWeek(RecipeName)) {
37 | setInCalender(true);
38 | } else {
39 | setInCalender(false);
40 | }
41 | }
42 | doFetch();
43 | }, [RecipeName]);
44 | const CalendarSrc = !inCalender ? CalendarCheck : CalendarFilled;
45 |
46 | const CalendarAlt = !inCalender
47 | ? "Item not in Calendar"
48 | : "Item is in Calendar";
49 |
50 | const handleClick = async (WholeRecipe) => {
51 | if (await startIsRecipeInWeek(RecipeName)) {
52 | await deleteRecipeFromWeek(RecipeName);
53 | setInCalender(false);
54 | return;
55 | } else {
56 | await putRecipeInWeek(WholeRecipe);
57 | await addIngredients(WholeRecipe.Ingredients);
58 | setInCalender(true);
59 | return;
60 | }
61 | };
62 |
63 | return (
64 | <>
65 |
66 | {RecipeName}
67 | handleClick(WholeRecipe)}
71 | />
72 |
73 |
74 | >
75 | );
76 | };
77 |
78 | RecipeListItem.propTypes = {
79 | RecipeName: PropTypes.string,
80 | RecipeId: PropTypes.string,
81 | WholeRecipe: PropTypes.object,
82 | };
83 |
84 | export default RecipeListItem;
85 |
--------------------------------------------------------------------------------
/client/src/assets/grocerieIcon.svg:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/client/src/api/connectJSON.js:
--------------------------------------------------------------------------------
1 | export async function getRecipies() {
2 | const response = await fetch("/api/recipes");
3 | const recipeList = response.json();
4 | return recipeList;
5 | }
6 |
7 | export async function getRecipeByRecipeName(recipeName) {
8 | const response = await fetch(`/api/recipes/${recipeName}`);
9 | const recipe = await response.json();
10 | return recipe;
11 | }
12 |
13 | export async function getWeek() {
14 | const response = await fetch("/api/week");
15 | const week = await response.json();
16 | return week;
17 | }
18 |
19 | export async function isRecipeInWeek(recipeName) {
20 | const weekResponse = await fetch(`/api/week/recipe/${recipeName}`);
21 | const recipeStatus = await weekResponse.json();
22 | if (recipeStatus) {
23 | return true;
24 | }
25 | return false;
26 | }
27 |
28 | export async function putRecipeInWeek(wholeRecipe) {
29 | await fetch("/api/week", {
30 | method: "POST",
31 | headers: {
32 | "Content-Type": "application/json;charset=utf-8",
33 | },
34 | body: JSON.stringify({ ...wholeRecipe }),
35 | });
36 | }
37 |
38 | export async function deleteRecipeFromWeek(recipeName) {
39 | await fetch(`/api/week/recipe/${recipeName}`, {
40 | method: "DELETE",
41 | });
42 | }
43 |
44 | export async function updateWeekByDate(date, id) {
45 | await fetch(`/api/week/${id}`, {
46 | method: "PATCH",
47 | headers: {
48 | "Content-Type": "application/json;charset=utf-8",
49 | },
50 | body: JSON.stringify({ date }),
51 | });
52 | }
53 |
54 | export async function deleteWholeWeek() {
55 | await fetch("/api/week/", {
56 | method: "DELETE",
57 | });
58 | }
59 |
60 | export async function checkShoppingList() {
61 | const shoppingResponse = await fetch("/api/shoppingItems");
62 | const listStatus = await shoppingResponse.json();
63 | return listStatus ? listStatus : false;
64 | }
65 |
66 | export async function addShoppingItem(quantity, unit, grocery) {
67 | await fetch("/api/shoppingItems", {
68 | method: "POST",
69 | headers: {
70 | "Content-Type": "application/json;charset=utf-8",
71 | },
72 | body: JSON.stringify({ Quantity: quantity, Unit: unit, Grocery: grocery }),
73 | });
74 | }
75 |
76 | export async function addIngredients(ingredients) {
77 | await fetch("/api/shoppingItems", {
78 | method: "POST",
79 | headers: {
80 | "Content-Type": "application/json;charset=utf-8",
81 | },
82 | body: JSON.stringify(ingredients),
83 | });
84 | }
85 |
86 | export async function deleteShoppingList() {
87 | await fetch("/api/shoppingList", {
88 | method: "DELETE",
89 | });
90 | }
91 |
92 | export async function deleteItemFromShoppingList(item) {
93 | console.log(item);
94 | await fetch(`/api/shoppingItems/${item}`, {
95 | method: "DELETE",
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/client/src/components/OverlayItems/OverlayItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useHistory, useParams } from "react-router-dom";
3 | import styled from "styled-components/macro";
4 | import { OverlayLine, UnderlineHeaderOverlay } from "../styledComponents/Lines";
5 | import IconButton from "../BottomNav/IconButton";
6 | import DeleteIcon from "../../assets/deleteIcon.svg";
7 | import GroceryIcon from "../../assets/grocerieIcon.svg";
8 | import { deleteWholeWeek, deleteShoppingList } from "../../api/connectJSON";
9 | import InputField from "../InputField/InputField";
10 | import RecipeIcon from "../../assets/recipeIcon.svg";
11 |
12 | const ContainerOverlayItems = styled.div`
13 | display: flex;
14 | flex-direction: column;
15 | padding: 1rem;
16 | background: var(--gradient-color);
17 | background-repeat: no-repeat;
18 | height: 100%;
19 | justify-content: center;
20 | margin-bottom: 50px;
21 | h2 {
22 | font-size: 24px;
23 | font-weight: 500;
24 | }
25 |
26 | button {
27 | margin: 0.5rem 0;
28 | display: flex;
29 | align-items: right;
30 | justify-content: flex-end;
31 | }
32 | `;
33 |
34 | const OverlayItem = () => {
35 | const history = useHistory();
36 | const { title } = useParams();
37 | const [grocery, setGrocery] = useState(false);
38 |
39 | const handleWeekDelete = async () => {
40 | await deleteWholeWeek();
41 | if (!title === "/Home") {
42 | history.push("/Home");
43 | } else {
44 | window.location.reload();
45 | }
46 | };
47 |
48 | const handleShoppingListDelete = async () => {
49 | await deleteShoppingList();
50 | if (!title === "/Einkaufsliste") {
51 | history.push("/Einkaufsliste");
52 | } else {
53 | window.location.reload();
54 | }
55 | };
56 |
57 | return (
58 |
59 | Übersicht
60 |
61 | setGrocery(!grocery)}
65 | content="Etwas zur Einkaufsliste hinzufügen"
66 | />
67 | {grocery && }
68 |
69 |
70 |
71 | handleShoppingListDelete()}
75 | content="Einkaufsliste leeren"
76 | />
77 |
78 |
79 | handleWeekDelete()}
83 | content="Wochenübersicht löschen"
84 | />
85 |
86 |
87 | handleWeekDelete()}
91 | content="Neues Rezept erstellen"
92 | />
93 |
94 |
95 | );
96 | };
97 |
98 | export default OverlayItem;
99 |
--------------------------------------------------------------------------------
/client/src/components/WeekListItem/WeekListItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components/macro";
3 | import DiamondImg from "../../assets/diamond.svg";
4 | import { DiamondLine } from "../styledComponents/Lines";
5 | import DatePicker from "react-datepicker";
6 | import "./DatePicker.css";
7 | import { registerLocale } from "react-datepicker";
8 | import de from "date-fns/locale/de";
9 | import { Link } from "react-router-dom";
10 | import PropTypes from "prop-types";
11 | import { updateWeekByDate } from "../../api/connectJSON";
12 | registerLocale("de", de);
13 |
14 | const StyledDatePicker = styled(DatePicker)`
15 | border: none;
16 | background: transparent;
17 | color: var(--active-color);
18 | text-align: center;
19 | font-family: "Encode Sans", sans-serif;
20 | font-size: 16px;
21 | font-weight: 300;
22 | :focus {
23 | outline: none;
24 | }
25 | ::placeholder {
26 | color: var(--active-color);
27 | }
28 | `;
29 |
30 | const ContainerWeek = styled.div`
31 | display: flex;
32 | align-items: center;
33 | margin: 10px 0 10px 0.5rem;
34 | `;
35 |
36 | const Diamond = styled.div`
37 | background-image: url(${DiamondImg});
38 | height: 5.8rem;
39 | width: 5.8rem;
40 | background-repeat: no-repeat;
41 | display: flex;
42 | flex-direction: column;
43 | justify-content: center;
44 | align-items: center;
45 | color: var(--active-color);
46 | text-align: center;
47 | padding-bottom: 5px;
48 | `;
49 |
50 | const TextWeek = styled(Link)`
51 | color: var(--text-color);
52 | width: 150px;
53 | margin-right: 5px;
54 | padding-bottom: 1px;
55 | /* 1px looked more aligned with the hr line. */
56 | `;
57 |
58 | const WeekListItem = ({ RecipeName, Id, ChosenDate }) => {
59 | const parseDate = Date.parse(ChosenDate);
60 | const [date, setDate] = useState(ChosenDate === undefined ? "" : parseDate);
61 |
62 | const getWeekDay = (date) => {
63 | return new Intl.DateTimeFormat("de-DE", { weekday: "short" }).format(date);
64 | };
65 |
66 | const updateDate = async (selectedDate) => {
67 | await updateWeekByDate(selectedDate, Id);
68 | };
69 |
70 | const handleOnChange = (selectedDate) => {
71 | setDate(selectedDate);
72 | updateDate(selectedDate);
73 | };
74 |
75 | return (
76 |
77 |
78 | {date && {getWeekDay(date)}
}
79 | handleOnChange(selected)}
84 | placeholderText="Click me"
85 | />
86 |
87 |
88 | {RecipeName}
89 |
90 | );
91 | };
92 |
93 | WeekListItem.propTypes = {
94 | RecipeName: PropTypes.string,
95 | Id: PropTypes.string,
96 | ChosenDate: PropTypes.any,
97 | };
98 |
99 | export { WeekListItem, Diamond };
100 |
--------------------------------------------------------------------------------
/client/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 |
3 | // This service worker can be customized!
4 | // See https://developers.google.com/web/tools/workbox/modules
5 | // for the list of available Workbox modules, or add any other
6 | // code you'd like.
7 | // You can also remove this file if you'd prefer not to use a
8 | // service worker, and the Workbox build step will be skipped.
9 |
10 | import { clientsClaim } from "workbox-core";
11 | import { ExpirationPlugin } from "workbox-expiration";
12 | import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching";
13 | import { registerRoute } from "workbox-routing";
14 | import { StaleWhileRevalidate } from "workbox-strategies";
15 |
16 | clientsClaim();
17 |
18 | // Precache all of the assets generated by your build process.
19 | // Their URLs are injected into the manifest variable below.
20 | // This variable must be present somewhere in your service worker file,
21 | // even if you decide not to use precaching. See https://cra.link/PWA
22 | precacheAndRoute(self.__WB_MANIFEST);
23 |
24 | // Set up App Shell-style routing, so that all navigation requests
25 | // are fulfilled with your index.html shell. Learn more at
26 | // https://developers.google.com/web/fundamentals/architecture/app-shell
27 | const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$");
28 | registerRoute(
29 | // Return false to exempt requests from being fulfilled by index.html.
30 | ({ request, url }) => {
31 | // If this isn't a navigation, skip.
32 | if (request.mode !== "navigate") {
33 | return false;
34 | } // If this is a URL that starts with /_, skip.
35 |
36 | if (url.pathname.startsWith("/_")) {
37 | return false;
38 | } // If this looks like a URL for a resource, because it contains // a file extension, skip.
39 |
40 | if (url.pathname.match(fileExtensionRegexp)) {
41 | return false;
42 | } // Return true to signal that we want to use the handler.
43 |
44 | return true;
45 | },
46 | createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html")
47 | );
48 |
49 | // An example runtime caching route for requests that aren't handled by the
50 | // precache, in this case same-origin .png requests like those from in public/
51 | registerRoute(
52 | // Add in any other file extensions or routing criteria as needed.
53 | ({ url }) =>
54 | url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst.
55 | new StaleWhileRevalidate({
56 | cacheName: "images",
57 | plugins: [
58 | // Ensure that once this runtime cache reaches a maximum size the
59 | // least-recently used images are removed.
60 | new ExpirationPlugin({ maxEntries: 50 }),
61 | ],
62 | })
63 | );
64 |
65 | // This allows the web app to trigger skipWaiting via
66 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
67 | self.addEventListener("message", (event) => {
68 | if (event.data && event.data.type === "SKIP_WAITING") {
69 | self.skipWaiting();
70 | }
71 | });
72 |
73 | // Any other custom service worker logic can go here.
74 |
--------------------------------------------------------------------------------
/client/src/components/InputField/InputField.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components/macro";
3 | import PropTypes from "prop-types";
4 | import { addShoppingItem } from "../../api/connectJSON";
5 | import { useHistory, useParams } from "react-router-dom";
6 |
7 | const Form = styled.form`
8 | display: flex;
9 | justify-content: flex-end;
10 | input[type="number"] {
11 | width: 4.5rem;
12 | margin-right: 5px;
13 | }
14 | input[type="text"] {
15 | margin-left: 5px;
16 | width: 8rem;
17 | }
18 |
19 | input[type="submit"] {
20 | margin-left: 5px;
21 | width: 2rem;
22 | }
23 | `;
24 |
25 | const Select = styled.select`
26 | border: 1px solid var(--active-color);
27 | background-color: var(--primary-color);
28 | font-family: "Encode Sans", sans-serif;
29 | -webkit-font-smoothing: antialiased;
30 | -moz-osx-font-smoothing: grayscale;
31 | font-size: 16px;
32 | font-weight: 300;
33 | `;
34 |
35 | const Input = styled.input`
36 | border: 1px solid var(--active-color);
37 | background-color: var(--primary-color);
38 | font-family: "Encode Sans", sans-serif;
39 | -webkit-font-smoothing: antialiased;
40 | -moz-osx-font-smoothing: grayscale;
41 | font-size: 16px;
42 | font-weight: 300;
43 | padding: 0.5rem;
44 |
45 | ::placeholder {
46 | text-align: center;
47 | }
48 | `;
49 |
50 | const InputField = () => {
51 | const history = useHistory();
52 | const { title } = useParams();
53 | const [quantity, setQuantity] = useState("");
54 | const [unit, setUnit] = useState("");
55 | const [shoppingItem, setShoppingItem] = useState("");
56 |
57 | const handleQuantityChange = (event) => {
58 | const number = parseInt(event.target.value);
59 | setQuantity(number);
60 | };
61 | const handleUnitChange = (event) => {
62 | setUnit(event.target.value);
63 | };
64 | const handleShoppingItemChange = (event) => {
65 | setShoppingItem(event.target.value);
66 | };
67 |
68 | const handleSubmit = async (e) => {
69 | e.preventDefault();
70 | await addShoppingItem(quantity, unit, shoppingItem);
71 | if (!title === "/Einkaufsliste") {
72 | history.push("/Einkaufsliste");
73 | } else {
74 | window.location.reload();
75 | }
76 | };
77 |
78 | return (
79 |
109 | );
110 | };
111 |
112 | InputField.propTypes = {
113 | handleSubmit: PropTypes.any,
114 | };
115 |
116 | export default InputField;
117 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/src/serviceWorkerRegistration.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://cra.link/PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === "localhost" ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === "[::1]" ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener("load", () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | "This web app is being served cache-first by a service " +
46 | "worker. To learn more, visit https://cra.link/PWA"
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then((registration) => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === "installed") {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | "New content is available and will be used when all " +
74 | "tabs for this page are closed. See https://cra.link/PWA."
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log("Content is cached for offline use.");
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch((error) => {
97 | console.error("Error during service worker registration:", error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { "Service-Worker": "script" },
105 | })
106 | .then((response) => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get("content-type");
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf("javascript") === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then((registration) => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | "No internet connection found. App is running in offline mode."
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ("serviceWorker" in navigator) {
133 | navigator.serviceWorker.ready
134 | .then((registration) => {
135 | registration.unregister();
136 | })
137 | .catch((error) => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const express = require("express");
3 | require("dotenv").config();
4 | const { connect } = require("./lib/database");
5 | const {
6 | getRecipiesMongo,
7 | getRecipeByNameMongo,
8 | insertRecipeInWeekMongo,
9 | } = require("./lib/connectRecipes");
10 | const {
11 | getRecipeByNameWeekMongo,
12 | deleteRecipeInWeekMongo,
13 | getWeekMongo,
14 | updateRecipeInWeekMongo,
15 | } = require("./lib/connectWeek");
16 | const {
17 | getShoppingListMongo,
18 | insertShoppingItemsMongo,
19 | deleteShoppingItemMongo,
20 | } = require("./lib/connectShoppingList");
21 | const { deleteAllElements } = require("./lib/helpFunctions");
22 |
23 | const port = process.env.PORT || 3001;
24 | const app = express();
25 | app.use(express.json());
26 |
27 | const collections = ["recipes", "shoppingList", "week"];
28 | collections.forEach((collection) => {
29 | app.route(`/api/${collection}`).delete(async (req, res) => {
30 | try {
31 | await deleteAllElements(`${collection}`);
32 | res.send(`Successfully deleted content from ${collection}`);
33 | } catch (error) {
34 | console.error(error);
35 | res.status(500).send("An internal server error occured");
36 | }
37 | });
38 | });
39 |
40 | app.get("/api/recipes/:recipeName", async (req, res) => {
41 | const recipeName = req.params["recipeName"];
42 | try {
43 | const getName = await getRecipeByNameMongo(recipeName);
44 | if (!getName) {
45 | res.status(404).send("Recipe is not in Database");
46 | return;
47 | }
48 | res.send(getName);
49 | } catch (error) {
50 | console.error(error);
51 | res.status(500).send("An internal server error occured");
52 | }
53 | });
54 |
55 | app.get("/api/recipes", async (req, res) => {
56 | try {
57 | const recipeList = await getRecipiesMongo();
58 | if (!recipeList) {
59 | res.status(404).send("Could not find any recipies");
60 | return;
61 | }
62 | res.send(recipeList);
63 | } catch (error) {
64 | console.error(error);
65 | res.status(500).send("An internal server error occured");
66 | }
67 | });
68 |
69 | app
70 | .route("/api/week/recipe/:RecipeName")
71 | .get(async (req, res) => {
72 | const { RecipeName } = req.params;
73 | try {
74 | const getName = await getRecipeByNameWeekMongo(RecipeName);
75 | if (getName === null) {
76 | res.send(false);
77 | return;
78 | }
79 | res.send(true);
80 | } catch (error) {
81 | console.error(error);
82 | res.status(500).send("An internal sever error occured");
83 | }
84 | })
85 | .delete(async (req, res) => {
86 | const { RecipeName } = req.params;
87 | try {
88 | await deleteRecipeInWeekMongo(RecipeName);
89 | res.send(`Successfully deleted ${RecipeName} from week`);
90 | } catch (error) {
91 | console.error(error);
92 | res
93 | .status(500)
94 | .send("An unexpected error occured. Please try again later!");
95 | }
96 | });
97 |
98 | app.patch("/api/week/:id", async (req, res) => {
99 | const id = req.params["id"];
100 | const date = req.body;
101 | try {
102 | await updateRecipeInWeekMongo(date, id);
103 | res.send(`Successfully updated ${date}`);
104 | } catch (error) {
105 | console.error(error);
106 | res
107 | .status(500)
108 | .send("An unexpected error occured. Please try again later!");
109 | }
110 | });
111 |
112 | app
113 | .route("/api/week")
114 | .get(async (req, res) => {
115 | try {
116 | const weekList = await getWeekMongo();
117 | if (!weekList) {
118 | res.status(404).send("Could not find any recipies");
119 | return;
120 | }
121 | res.send(weekList);
122 | } catch (error) {
123 | console.error(error);
124 | res.status(500).send("An internal server error occured");
125 | }
126 | })
127 | //this post-method could go into the forEachLoop, but doesn't make a difference down here or up there right now
128 | .post(async (req, res) => {
129 | const recipe = req.body;
130 | try {
131 | await insertRecipeInWeekMongo(recipe);
132 | res.send(`Successfully inserted ${recipe}`);
133 | } catch (error) {
134 | console.error(error);
135 | res
136 | .status(500)
137 | .send("An unexpected error occured. Please try again later!");
138 | }
139 | });
140 |
141 | app.delete("/api/shoppingItems/:item", async (req, res) => {
142 | const shoppingItem = req.params["item"];
143 | try {
144 | await deleteShoppingItemMongo(shoppingItem);
145 | res.send("Successfully deleted Shopping Item");
146 | } catch (error) {
147 | console.error(error);
148 | res
149 | .status(500)
150 | .send("An unexpected error occured. Please try again later!");
151 | }
152 | });
153 |
154 | app
155 | .route("/api/shoppingItems")
156 | //this get-method could go into the forEachLoop, but doesn't make a difference down here or up there right now
157 | .get(async (req, res) => {
158 | try {
159 | const shoppingItems = await getShoppingListMongo();
160 | if (!shoppingItems) {
161 | res.status(404).send("Could not find any shoppingItems");
162 | return;
163 | }
164 | res.send(shoppingItems);
165 | } catch (error) {
166 | console.error(error);
167 | res
168 | .status(500)
169 | .send("An internal server error occured. Please try again later");
170 | }
171 | })
172 | .post(async (req, res) => {
173 | try {
174 | const newShopppingItems = Array.isArray(req.body) ? req.body : [req.body];
175 | await insertShoppingItemsMongo(newShopppingItems);
176 | res.send(`Successfully inserted shopping items`);
177 | } catch (error) {
178 | console.error(error);
179 | res
180 | .status(500)
181 | .send("An unexpected error occured. Please try again later!");
182 | }
183 | });
184 |
185 | app.use(express.static(path.join(__dirname, "client/build")));
186 |
187 | app.get("*", (req, res) => {
188 | res.sendFile(path.join(__dirname, "client/build", "index.html"));
189 | });
190 |
191 | async function run() {
192 | console.log("Connecting to Database...");
193 | await connect(process.env.DB_URI, process.env.DB_DBNAME);
194 | console.log("Connected to database🎄");
195 |
196 | app.listen(port, () => {
197 | console.log(`Appeatit API listening at http://localhost:${port}`);
198 | });
199 | }
200 | run();
201 |
--------------------------------------------------------------------------------
/client/src/components/WeekListItem/DatePicker.css:
--------------------------------------------------------------------------------
1 | .react-datepicker-popper {
2 | margin-left: 50px;
3 | }
4 |
5 | .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle,
6 | .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle,
7 | .react-datepicker__year-read-view--down-arrow,
8 | .react-datepicker__month-read-view--down-arrow,
9 | .react-datepicker__month-year-read-view--down-arrow {
10 | margin-left: -8px;
11 | position: absolute;
12 | }
13 |
14 | .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle,
15 | .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle,
16 | .react-datepicker__year-read-view--down-arrow,
17 | .react-datepicker__month-read-view--down-arrow,
18 | .react-datepicker__month-year-read-view--down-arrow,
19 | .react-datepicker-popper[data-placement^="bottom"]
20 | .react-datepicker__triangle::before,
21 | .react-datepicker-popper[data-placement^="top"]
22 | .react-datepicker__triangle::before,
23 | .react-datepicker__year-read-view--down-arrow::before,
24 | .react-datepicker__month-read-view--down-arrow::before,
25 | .react-datepicker__month-year-read-view--down-arrow::before {
26 | box-sizing: content-box;
27 | position: absolute;
28 | border: 8px solid transparent;
29 | height: 0;
30 | width: 1px;
31 | }
32 |
33 | .react-datepicker-popper[data-placement^="bottom"]
34 | .react-datepicker__triangle::before,
35 | .react-datepicker-popper[data-placement^="top"]
36 | .react-datepicker__triangle::before,
37 | .react-datepicker__year-read-view--down-arrow::before,
38 | .react-datepicker__month-read-view--down-arrow::before,
39 | .react-datepicker__month-year-read-view--down-arrow::before {
40 | content: "";
41 | z-index: -1;
42 | border-width: 8px;
43 | left: -8px;
44 | /* border-bottom-color: #aeaeae; */
45 | border-bottom-color: #037f8c;
46 | }
47 |
48 | .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle {
49 | top: 0;
50 | margin-top: -8px;
51 | }
52 |
53 | .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle,
54 | .react-datepicker-popper[data-placement^="bottom"]
55 | .react-datepicker__triangle::before {
56 | border-top: none;
57 | border-bottom-color: #f2d3ac;
58 | /* border-bottom-color: #f0f0f0; */
59 | }
60 |
61 | .react-datepicker-popper[data-placement^="bottom"]
62 | .react-datepicker__triangle::before {
63 | top: -1px;
64 | /* border-bottom-color: #aeaeae; */
65 | border-bottom-color: #037f8c;
66 | }
67 |
68 | .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle,
69 | .react-datepicker__year-read-view--down-arrow,
70 | .react-datepicker__month-read-view--down-arrow,
71 | .react-datepicker__month-year-read-view--down-arrow {
72 | bottom: 0;
73 | margin-bottom: -8px;
74 | }
75 |
76 | .react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle,
77 | .react-datepicker__year-read-view--down-arrow,
78 | .react-datepicker__month-read-view--down-arrow,
79 | .react-datepicker__month-year-read-view--down-arrow,
80 | .react-datepicker-popper[data-placement^="top"]
81 | .react-datepicker__triangle::before,
82 | .react-datepicker__year-read-view--down-arrow::before,
83 | .react-datepicker__month-read-view--down-arrow::before,
84 | .react-datepicker__month-year-read-view--down-arrow::before {
85 | border-bottom: none;
86 | /* border-top-color: #fff; */
87 | border-top-color: #037f8c;
88 | }
89 |
90 | .react-datepicker-popper[data-placement^="top"]
91 | .react-datepicker__triangle::before,
92 | .react-datepicker__year-read-view--down-arrow::before,
93 | .react-datepicker__month-read-view--down-arrow::before,
94 | .react-datepicker__month-year-read-view--down-arrow::before {
95 | bottom: -1px;
96 | /* border-top-color: #aeaeae; */
97 | border-top-color: #037f8c;
98 | }
99 |
100 | .react-datepicker-wrapper {
101 | display: inline-block;
102 | padding: 0;
103 | border: 0;
104 | }
105 |
106 | .react-datepicker {
107 | font-family: "Encode Sans", sans-serif;
108 | font-size: 0.8rem;
109 | background-color: #f8e4cf;
110 | color: #262626;
111 | border: 1px solid #037f8c;
112 | border-radius: 0.3rem;
113 | display: inline-block;
114 | position: relative;
115 | }
116 |
117 | .react-datepicker--time-only .react-datepicker__triangle {
118 | left: 35px;
119 | }
120 |
121 | .react-datepicker--time-only .react-datepicker__time-container {
122 | border-left: 0;
123 | }
124 |
125 | .react-datepicker--time-only .react-datepicker__time,
126 | .react-datepicker--time-only .react-datepicker__time-box {
127 | border-bottom-left-radius: 0.3rem;
128 | border-bottom-right-radius: 0.3rem;
129 | }
130 |
131 | .react-datepicker__triangle {
132 | position: absolute;
133 | left: 50px;
134 | }
135 |
136 | .react-datepicker-popper {
137 | z-index: 1;
138 | }
139 |
140 | .react-datepicker-popper[data-placement^="bottom"] {
141 | margin-top: 10px;
142 | }
143 |
144 | .react-datepicker-popper[data-placement="bottom-end"]
145 | .react-datepicker__triangle,
146 | .react-datepicker-popper[data-placement="top-end"] .react-datepicker__triangle {
147 | left: auto;
148 | right: 50px;
149 | }
150 |
151 | .react-datepicker-popper[data-placement^="top"] {
152 | margin-bottom: 10px;
153 | }
154 |
155 | .react-datepicker-popper[data-placement^="right"] {
156 | margin-left: 8px;
157 | }
158 |
159 | .react-datepicker-popper[data-placement^="right"] .react-datepicker__triangle {
160 | left: auto;
161 | right: 42px;
162 | }
163 |
164 | .react-datepicker-popper[data-placement^="left"] {
165 | margin-right: 8px;
166 | }
167 |
168 | .react-datepicker-popper[data-placement^="left"] .react-datepicker__triangle {
169 | left: 42px;
170 | right: auto;
171 | }
172 |
173 | .react-datepicker__header {
174 | text-align: center;
175 | background-color: #f2d3ac;
176 | border-bottom: 1px solid #037f8c;
177 | border-top-left-radius: 0.3rem;
178 | padding-top: 8px;
179 | position: relative;
180 | }
181 |
182 | .react-datepicker__header--time {
183 | padding-bottom: 8px;
184 | padding-left: 5px;
185 | padding-right: 5px;
186 | }
187 |
188 | .react-datepicker__header--time:not(.react-datepicker__header--time--only) {
189 | border-top-left-radius: 0;
190 | }
191 |
192 | .react-datepicker__header:not(.react-datepicker__header--has-time-select) {
193 | border-top-right-radius: 0.3rem;
194 | }
195 |
196 | .react-datepicker__year-dropdown-container--select,
197 | .react-datepicker__month-dropdown-container--select,
198 | .react-datepicker__month-year-dropdown-container--select,
199 | .react-datepicker__year-dropdown-container--scroll,
200 | .react-datepicker__month-dropdown-container--scroll,
201 | .react-datepicker__month-year-dropdown-container--scroll {
202 | display: inline-block;
203 | margin: 0 2px;
204 | }
205 |
206 | .react-datepicker__current-month,
207 | .react-datepicker-time__header,
208 | .react-datepicker-year-header {
209 | margin-top: 0;
210 | color: #037f8c;
211 | font-weight: 400;
212 | font-size: 0.944rem;
213 | }
214 |
215 | .react-datepicker-time__header {
216 | text-overflow: ellipsis;
217 | white-space: nowrap;
218 | overflow: hidden;
219 | }
220 |
221 | .react-datepicker__navigation {
222 | background: none;
223 | line-height: 1.7rem;
224 | text-align: center;
225 | cursor: pointer;
226 | position: absolute;
227 | top: 10px;
228 | width: 0;
229 | padding: 0;
230 | border: 0.45rem solid transparent;
231 | z-index: 1;
232 | height: 10px;
233 | width: 10px;
234 | text-indent: -999em;
235 | overflow: hidden;
236 | }
237 |
238 | .react-datepicker__navigation--previous {
239 | left: 10px;
240 | border-right-color: #037f8c;
241 | /* border-right-color: #ccc; */
242 | }
243 |
244 | .react-datepicker__navigation--previous:hover {
245 | /* border-right-color: #b3b3b3; */
246 | border-right-color: #037f8c;
247 | color: #f8e4cf;
248 | }
249 |
250 | .react-datepicker__navigation--previous--disabled,
251 | .react-datepicker__navigation--previous--disabled:hover {
252 | /* border-right-color: #e6e6e6; */
253 | border-right-color: #037f8c;
254 | cursor: default;
255 | }
256 |
257 | .react-datepicker__navigation--next {
258 | right: 10px;
259 | /* border-left-color: #ccc; */
260 | border-left-color: #037f8c;
261 | }
262 |
263 | .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
264 | right: 95px;
265 | }
266 |
267 | .react-datepicker__navigation--next:hover {
268 | /* border-left-color: #b3b3b3; */
269 | border-left-color: #037f8c;
270 | }
271 |
272 | .react-datepicker__navigation--next--disabled,
273 | .react-datepicker__navigation--next--disabled:hover {
274 | /* border-left-color: #e6e6e6; */
275 | border-left-color: #037f8c;
276 | cursor: default;
277 | }
278 |
279 | .react-datepicker__navigation--years {
280 | position: relative;
281 | top: 0;
282 | display: block;
283 | margin-left: auto;
284 | margin-right: auto;
285 | }
286 |
287 | .react-datepicker__navigation--years-previous {
288 | top: 4px;
289 | border-top-color: #ccc;
290 | }
291 |
292 | .react-datepicker__navigation--years-previous:hover {
293 | /* border-top-color: #b3b3b3; */
294 | border-top-color: #037f8c;
295 | }
296 |
297 | .react-datepicker__navigation--years-upcoming {
298 | top: -4px;
299 | border-bottom-color: #ccc;
300 | }
301 |
302 | .react-datepicker__navigation--years-upcoming:hover {
303 | border-bottom-color: #037f8c;
304 | }
305 |
306 | .react-datepicker__month-container {
307 | float: left;
308 | }
309 |
310 | .react-datepicker__year {
311 | margin: 0.4rem;
312 | text-align: center;
313 | }
314 |
315 | .react-datepicker__year-wrapper {
316 | display: flex;
317 | flex-wrap: wrap;
318 | max-width: 180px;
319 | }
320 |
321 | .react-datepicker__year .react-datepicker__year-text {
322 | display: inline-block;
323 | width: 4rem;
324 | margin: 2px;
325 | }
326 |
327 | .react-datepicker__month {
328 | margin: 0.4rem;
329 | text-align: center;
330 | }
331 |
332 | .react-datepicker__month .react-datepicker__month-text,
333 | .react-datepicker__month .react-datepicker__quarter-text {
334 | display: inline-block;
335 | width: 4rem;
336 | margin: 2px;
337 | }
338 |
339 | .react-datepicker__input-time-container {
340 | clear: both;
341 | width: 100%;
342 | float: left;
343 | margin: 5px 0 10px 15px;
344 | text-align: left;
345 | }
346 |
347 | .react-datepicker__input-time-container .react-datepicker-time__caption {
348 | display: inline-block;
349 | }
350 |
351 | .react-datepicker__input-time-container
352 | .react-datepicker-time__input-container {
353 | display: inline-block;
354 | }
355 |
356 | .react-datepicker__input-time-container
357 | .react-datepicker-time__input-container
358 | .react-datepicker-time__input {
359 | display: inline-block;
360 | margin-left: 10px;
361 | }
362 |
363 | .react-datepicker__input-time-container
364 | .react-datepicker-time__input-container
365 | .react-datepicker-time__input
366 | input {
367 | width: 85px;
368 | }
369 |
370 | .react-datepicker__input-time-container
371 | .react-datepicker-time__input-container
372 | .react-datepicker-time__input
373 | input[type="time"]::-webkit-inner-spin-button,
374 | .react-datepicker__input-time-container
375 | .react-datepicker-time__input-container
376 | .react-datepicker-time__input
377 | input[type="time"]::-webkit-outer-spin-button {
378 | -webkit-appearance: none;
379 | margin: 0;
380 | }
381 |
382 | .react-datepicker__input-time-container
383 | .react-datepicker-time__input-container
384 | .react-datepicker-time__input
385 | input[type="time"] {
386 | -moz-appearance: textfield;
387 | }
388 |
389 | .react-datepicker__input-time-container
390 | .react-datepicker-time__input-container
391 | .react-datepicker-time__delimiter {
392 | margin-left: 5px;
393 | display: inline-block;
394 | }
395 |
396 | .react-datepicker__time-container {
397 | float: right;
398 | border-left: 1px solid #aeaeae;
399 | width: 85px;
400 | }
401 |
402 | .react-datepicker__time-container--with-today-button {
403 | display: inline;
404 | border: 1px solid #aeaeae;
405 | border-radius: 0.3rem;
406 | position: absolute;
407 | right: -72px;
408 | top: 0;
409 | }
410 |
411 | .react-datepicker__time-container .react-datepicker__time {
412 | position: relative;
413 | background: white;
414 | /* background: pink; */
415 | border-bottom-right-radius: 0.3rem;
416 | }
417 |
418 | .react-datepicker__time-container
419 | .react-datepicker__time
420 | .react-datepicker__time-box {
421 | width: 85px;
422 | overflow-x: hidden;
423 | margin: 0 auto;
424 | text-align: center;
425 | border-bottom-right-radius: 0.3rem;
426 | }
427 |
428 | .react-datepicker__time-container
429 | .react-datepicker__time
430 | .react-datepicker__time-box
431 | ul.react-datepicker__time-list {
432 | list-style: none;
433 | margin: 0;
434 | height: calc(195px + (1.7rem / 2));
435 | overflow-y: scroll;
436 | padding-right: 0px;
437 | padding-left: 0px;
438 | width: 100%;
439 | box-sizing: content-box;
440 | }
441 |
442 | .react-datepicker__time-container
443 | .react-datepicker__time
444 | .react-datepicker__time-box
445 | ul.react-datepicker__time-list
446 | li.react-datepicker__time-list-item {
447 | height: 30px;
448 | padding: 5px 10px;
449 | white-space: nowrap;
450 | }
451 |
452 | .react-datepicker__time-container
453 | .react-datepicker__time
454 | .react-datepicker__time-box
455 | ul.react-datepicker__time-list
456 | li.react-datepicker__time-list-item:hover {
457 | cursor: pointer;
458 | background-color: #037f8c;
459 | color: #f8e4cf;
460 | }
461 |
462 | .react-datepicker__time-container
463 | .react-datepicker__time
464 | .react-datepicker__time-box
465 | ul.react-datepicker__time-list
466 | li.react-datepicker__time-list-item--selected {
467 | background-color: #037f8c;
468 | color: #f8e4cf;
469 | font-weight: 400;
470 | }
471 |
472 | .react-datepicker__time-container
473 | .react-datepicker__time
474 | .react-datepicker__time-box
475 | ul.react-datepicker__time-list
476 | li.react-datepicker__time-list-item--selected:hover {
477 | background-color: #037f8c;
478 | color: #f8e4cf;
479 | }
480 |
481 | .react-datepicker__time-container
482 | .react-datepicker__time
483 | .react-datepicker__time-box
484 | ul.react-datepicker__time-list
485 | li.react-datepicker__time-list-item--disabled {
486 | color: #ccc;
487 | }
488 |
489 | .react-datepicker__time-container
490 | .react-datepicker__time
491 | .react-datepicker__time-box
492 | ul.react-datepicker__time-list
493 | li.react-datepicker__time-list-item--disabled:hover {
494 | cursor: default;
495 | background-color: transparent;
496 | }
497 |
498 | .react-datepicker__week-number {
499 | color: #ccc;
500 | display: inline-block;
501 | width: 1.7rem;
502 | line-height: 1.7rem;
503 | text-align: center;
504 | margin: 0.166rem;
505 | }
506 |
507 | .react-datepicker__week-number.react-datepicker__week-number--clickable {
508 | cursor: pointer;
509 | }
510 |
511 | .react-datepicker__week-number.react-datepicker__week-number--clickable:hover {
512 | border-radius: 0.3rem;
513 | background-color: #037f8c;
514 | color: #f8e4cf;
515 | }
516 |
517 | .react-datepicker__day-names,
518 | .react-datepicker__week {
519 | white-space: nowrap;
520 | }
521 |
522 | .react-datepicker__day-name,
523 | .react-datepicker__day,
524 | .react-datepicker__time-name {
525 | color: #262626;
526 | display: inline-block;
527 | width: 1.7rem;
528 | line-height: 1.7rem;
529 | text-align: center;
530 | margin: 0.166rem;
531 | }
532 |
533 | .react-datepicker__month--selected,
534 | .react-datepicker__month--in-selecting-range,
535 | .react-datepicker__month--in-range,
536 | .react-datepicker__quarter--selected,
537 | .react-datepicker__quarter--in-selecting-range,
538 | .react-datepicker__quarter--in-range {
539 | border-radius: 0.3rem;
540 | background-color: #037f8c;
541 | color: #f8e4cf;
542 | }
543 |
544 | .react-datepicker__month--selected:hover,
545 | .react-datepicker__month--in-selecting-range:hover,
546 | .react-datepicker__month--in-range:hover,
547 | .react-datepicker__quarter--selected:hover,
548 | .react-datepicker__quarter--in-selecting-range:hover,
549 | .react-datepicker__quarter--in-range:hover {
550 | background-color: #037f8c;
551 | color: #f8e4cf;
552 | }
553 |
554 | .react-datepicker__month--disabled,
555 | .react-datepicker__quarter--disabled {
556 | color: #ccc;
557 | pointer-events: none;
558 | }
559 |
560 | .react-datepicker__month--disabled:hover,
561 | .react-datepicker__quarter--disabled:hover {
562 | cursor: default;
563 | background-color: transparent;
564 | }
565 |
566 | .react-datepicker__day,
567 | .react-datepicker__month-text,
568 | .react-datepicker__quarter-text,
569 | .react-datepicker__year-text {
570 | cursor: pointer;
571 | }
572 |
573 | .react-datepicker__day:hover,
574 | .react-datepicker__month-text:hover,
575 | .react-datepicker__quarter-text:hover,
576 | .react-datepicker__year-text:hover {
577 | border-radius: 0.3rem;
578 | background-color: #037f8c;
579 | color: #f8e4cf;
580 | }
581 |
582 | .react-datepicker__day--today,
583 | .react-datepicker__month-text--today,
584 | .react-datepicker__quarter-text--today,
585 | .react-datepicker__year-text--today {
586 | font-weight: 500;
587 | }
588 |
589 | .react-datepicker__day--highlighted,
590 | .react-datepicker__month-text--highlighted,
591 | .react-datepicker__quarter-text--highlighted,
592 | .react-datepicker__year-text--highlighted {
593 | border-radius: 0.3rem;
594 | background-color: #3dcc4a;
595 | color: #fff;
596 | }
597 |
598 | .react-datepicker__day--highlighted:hover,
599 | .react-datepicker__month-text--highlighted:hover,
600 | .react-datepicker__quarter-text--highlighted:hover,
601 | .react-datepicker__year-text--highlighted:hover {
602 | background-color: #32be3f;
603 | }
604 |
605 | .react-datepicker__day--highlighted-custom-1,
606 | .react-datepicker__month-text--highlighted-custom-1,
607 | .react-datepicker__quarter-text--highlighted-custom-1,
608 | .react-datepicker__year-text--highlighted-custom-1 {
609 | color: magenta;
610 | }
611 |
612 | .react-datepicker__day--highlighted-custom-2,
613 | .react-datepicker__month-text--highlighted-custom-2,
614 | .react-datepicker__quarter-text--highlighted-custom-2,
615 | .react-datepicker__year-text--highlighted-custom-2 {
616 | color: green;
617 | }
618 |
619 | .react-datepicker__day--selected,
620 | .react-datepicker__day--in-selecting-range,
621 | .react-datepicker__day--in-range,
622 | .react-datepicker__month-text--selected,
623 | .react-datepicker__month-text--in-selecting-range,
624 | .react-datepicker__month-text--in-range,
625 | .react-datepicker__quarter-text--selected,
626 | .react-datepicker__quarter-text--in-selecting-range,
627 | .react-datepicker__quarter-text--in-range,
628 | .react-datepicker__year-text--selected,
629 | .react-datepicker__year-text--in-selecting-range,
630 | .react-datepicker__year-text--in-range {
631 | border-radius: 0.3rem;
632 | background-color: #037f8c;
633 | color: #f8e4cf;
634 | }
635 |
636 | .react-datepicker__day--selected:hover,
637 | .react-datepicker__day--in-selecting-range:hover,
638 | .react-datepicker__day--in-range:hover,
639 | .react-datepicker__month-text--selected:hover,
640 | .react-datepicker__month-text--in-selecting-range:hover,
641 | .react-datepicker__month-text--in-range:hover,
642 | .react-datepicker__quarter-text--selected:hover,
643 | .react-datepicker__quarter-text--in-selecting-range:hover,
644 | .react-datepicker__quarter-text--in-range:hover,
645 | .react-datepicker__year-text--selected:hover,
646 | .react-datepicker__year-text--in-selecting-range:hover,
647 | .react-datepicker__year-text--in-range:hover {
648 | background-color: #037f8c;
649 | color: #f8e4cf;
650 | }
651 |
652 | .react-datepicker__day--keyboard-selected,
653 | .react-datepicker__month-text--keyboard-selected,
654 | .react-datepicker__quarter-text--keyboard-selected,
655 | .react-datepicker__year-text--keyboard-selected {
656 | border-radius: 0.3rem;
657 | background-color: #037f8c;
658 | color: #f8e4cf;
659 | }
660 |
661 | .react-datepicker__day--keyboard-selected:hover,
662 | .react-datepicker__month-text--keyboard-selected:hover,
663 | .react-datepicker__quarter-text--keyboard-selected:hover,
664 | .react-datepicker__year-text--keyboard-selected:hover {
665 | background-color: #037f8c;
666 | color: #f8e4cf;
667 | }
668 |
669 | .react-datepicker__day--in-selecting-range,
670 | .react-datepicker__month-text--in-selecting-range,
671 | .react-datepicker__quarter-text--in-selecting-range,
672 | .react-datepicker__year-text--in-selecting-range {
673 | background-color: rgba(33, 107, 165, 0.5);
674 | }
675 |
676 | .react-datepicker__month--selecting-range .react-datepicker__day--in-range,
677 | .react-datepicker__month--selecting-range
678 | .react-datepicker__month-text--in-range,
679 | .react-datepicker__month--selecting-range
680 | .react-datepicker__quarter-text--in-range,
681 | .react-datepicker__month--selecting-range
682 | .react-datepicker__year-text--in-range {
683 | background-color: #f0f0f0;
684 | color: #000;
685 | }
686 |
687 | .react-datepicker__day--disabled,
688 | .react-datepicker__month-text--disabled,
689 | .react-datepicker__quarter-text--disabled,
690 | .react-datepicker__year-text--disabled {
691 | cursor: default;
692 | color: #ccc;
693 | }
694 |
695 | .react-datepicker__day--disabled:hover,
696 | .react-datepicker__month-text--disabled:hover,
697 | .react-datepicker__quarter-text--disabled:hover,
698 | .react-datepicker__year-text--disabled:hover {
699 | background-color: transparent;
700 | }
701 |
702 | .react-datepicker__month-text.react-datepicker__month--selected:hover,
703 | .react-datepicker__month-text.react-datepicker__month--in-range:hover,
704 | .react-datepicker__month-text.react-datepicker__quarter--selected:hover,
705 | .react-datepicker__month-text.react-datepicker__quarter--in-range:hover,
706 | .react-datepicker__quarter-text.react-datepicker__month--selected:hover,
707 | .react-datepicker__quarter-text.react-datepicker__month--in-range:hover,
708 | .react-datepicker__quarter-text.react-datepicker__quarter--selected:hover,
709 | .react-datepicker__quarter-text.react-datepicker__quarter--in-range:hover {
710 | /* background-color: #216ba5; */
711 | background-color: #f8e4cf;
712 | }
713 |
714 | .react-datepicker__month-text:hover,
715 | .react-datepicker__quarter-text:hover {
716 | background-color: #f8e4cf;
717 | }
718 |
719 | .react-datepicker__input-container {
720 | position: relative;
721 | display: inline-block;
722 | width: 100%;
723 | }
724 |
725 | .react-datepicker__input-container::-webkit-input-placeholder {
726 | color: var(--active-color);
727 | }
728 |
729 | .react-datepicker__year-read-view,
730 | .react-datepicker__month-read-view,
731 | .react-datepicker__month-year-read-view {
732 | border: 1px solid transparent;
733 | border-radius: 0.3rem;
734 | }
735 |
736 | .react-datepicker__year-read-view:hover,
737 | .react-datepicker__month-read-view:hover,
738 | .react-datepicker__month-year-read-view:hover {
739 | cursor: pointer;
740 | }
741 |
742 | .react-datepicker__year-read-view:hover
743 | .react-datepicker__year-read-view--down-arrow,
744 | .react-datepicker__year-read-view:hover
745 | .react-datepicker__month-read-view--down-arrow,
746 | .react-datepicker__month-read-view:hover
747 | .react-datepicker__year-read-view--down-arrow,
748 | .react-datepicker__month-read-view:hover
749 | .react-datepicker__month-read-view--down-arrow,
750 | .react-datepicker__month-year-read-view:hover
751 | .react-datepicker__year-read-view--down-arrow,
752 | .react-datepicker__month-year-read-view:hover
753 | .react-datepicker__month-read-view--down-arrow {
754 | border-top-color: #b3b3b3;
755 | }
756 |
757 | .react-datepicker__year-read-view--down-arrow,
758 | .react-datepicker__month-read-view--down-arrow,
759 | .react-datepicker__month-year-read-view--down-arrow {
760 | border-top-color: #ccc;
761 | float: right;
762 | margin-left: 20px;
763 | top: 8px;
764 | position: relative;
765 | border-width: 0.45rem;
766 | }
767 |
768 | .react-datepicker__year-dropdown,
769 | .react-datepicker__month-dropdown,
770 | .react-datepicker__month-year-dropdown {
771 | background-color: #f0f0f0;
772 | position: absolute;
773 | width: 50%;
774 | left: 25%;
775 | top: 30px;
776 | z-index: 1;
777 | text-align: center;
778 | border-radius: 0.3rem;
779 | border: 1px solid #aeaeae;
780 | }
781 |
782 | .react-datepicker__year-dropdown:hover,
783 | .react-datepicker__month-dropdown:hover,
784 | .react-datepicker__month-year-dropdown:hover {
785 | cursor: pointer;
786 | }
787 |
788 | .react-datepicker__year-dropdown--scrollable,
789 | .react-datepicker__month-dropdown--scrollable,
790 | .react-datepicker__month-year-dropdown--scrollable {
791 | height: 150px;
792 | overflow-y: scroll;
793 | }
794 |
795 | .react-datepicker__year-option,
796 | .react-datepicker__month-option,
797 | .react-datepicker__month-year-option {
798 | line-height: 20px;
799 | width: 100%;
800 | display: block;
801 | margin-left: auto;
802 | margin-right: auto;
803 | }
804 |
805 | .react-datepicker__year-option:first-of-type,
806 | .react-datepicker__month-option:first-of-type,
807 | .react-datepicker__month-year-option:first-of-type {
808 | border-top-left-radius: 0.3rem;
809 | border-top-right-radius: 0.3rem;
810 | }
811 |
812 | .react-datepicker__year-option:last-of-type,
813 | .react-datepicker__month-option:last-of-type,
814 | .react-datepicker__month-year-option:last-of-type {
815 | -webkit-user-select: none;
816 | -moz-user-select: none;
817 | -ms-user-select: none;
818 | user-select: none;
819 | border-bottom-left-radius: 0.3rem;
820 | border-bottom-right-radius: 0.3rem;
821 | }
822 |
823 | .react-datepicker__year-option:hover,
824 | .react-datepicker__month-option:hover,
825 | .react-datepicker__month-year-option:hover {
826 | background-color: #ccc;
827 | }
828 |
829 | .react-datepicker__year-option:hover
830 | .react-datepicker__navigation--years-upcoming,
831 | .react-datepicker__month-option:hover
832 | .react-datepicker__navigation--years-upcoming,
833 | .react-datepicker__month-year-option:hover
834 | .react-datepicker__navigation--years-upcoming {
835 | border-bottom-color: #b3b3b3;
836 | }
837 |
838 | .react-datepicker__year-option:hover
839 | .react-datepicker__navigation--years-previous,
840 | .react-datepicker__month-option:hover
841 | .react-datepicker__navigation--years-previous,
842 | .react-datepicker__month-year-option:hover
843 | .react-datepicker__navigation--years-previous {
844 | border-top-color: #b3b3b3;
845 | }
846 |
847 | .react-datepicker__year-option--selected,
848 | .react-datepicker__month-option--selected,
849 | .react-datepicker__month-year-option--selected {
850 | position: absolute;
851 | left: 15px;
852 | }
853 |
854 | .react-datepicker__close-icon {
855 | cursor: pointer;
856 | background-color: transparent;
857 | border: 0;
858 | outline: 0;
859 | padding: 0px 6px 0px 0px;
860 | position: absolute;
861 | top: 0;
862 | right: 0;
863 | height: 100%;
864 | display: table-cell;
865 | vertical-align: middle;
866 | }
867 |
868 | .react-datepicker__close-icon::after {
869 | cursor: pointer;
870 | background-color: #216ba5;
871 | color: #fff;
872 | border-radius: 50%;
873 | height: 16px;
874 | width: 16px;
875 | padding: 2px;
876 | font-size: 12px;
877 | line-height: 1;
878 | text-align: center;
879 | display: table-cell;
880 | vertical-align: middle;
881 | content: "\00d7";
882 | }
883 |
884 | .react-datepicker__today-button {
885 | background: #f0f0f0;
886 | border-top: 1px solid #aeaeae;
887 | cursor: pointer;
888 | text-align: center;
889 | font-weight: 500;
890 | padding: 5px 0;
891 | clear: left;
892 | }
893 |
894 | .react-datepicker__portal {
895 | position: fixed;
896 | width: 100vw;
897 | height: 100vh;
898 | background-color: rgba(0, 0, 0, 0.8);
899 | left: 0;
900 | top: 0;
901 | justify-content: center;
902 | align-items: center;
903 | display: flex;
904 | z-index: 2147483647;
905 | }
906 |
907 | .react-datepicker__portal .react-datepicker__day-name,
908 | .react-datepicker__portal .react-datepicker__day,
909 | .react-datepicker__portal .react-datepicker__time-name {
910 | width: 3rem;
911 | line-height: 3rem;
912 | }
913 |
914 | @media (max-width: 400px), (max-height: 550px) {
915 | .react-datepicker__portal .react-datepicker__day-name,
916 | .react-datepicker__portal .react-datepicker__day,
917 | .react-datepicker__portal .react-datepicker__time-name {
918 | width: 2rem;
919 | line-height: 2rem;
920 | }
921 | }
922 |
923 | .react-datepicker__portal .react-datepicker__current-month,
924 | .react-datepicker__portal .react-datepicker-time__header {
925 | font-size: 1.44rem;
926 | }
927 |
928 | .react-datepicker__portal .react-datepicker__navigation {
929 | border: 0.81rem solid transparent;
930 | }
931 |
932 | .react-datepicker__portal .react-datepicker__navigation--previous {
933 | border-right-color: #ccc;
934 | }
935 |
936 | .react-datepicker__portal .react-datepicker__navigation--previous:hover {
937 | border-right-color: #b3b3b3;
938 | }
939 |
940 | .react-datepicker__portal .react-datepicker__navigation--previous--disabled,
941 | .react-datepicker__portal
942 | .react-datepicker__navigation--previous--disabled:hover {
943 | border-right-color: #e6e6e6;
944 | cursor: default;
945 | }
946 |
947 | .react-datepicker__portal .react-datepicker__navigation--next {
948 | border-left-color: #ccc;
949 | }
950 |
951 | .react-datepicker__portal .react-datepicker__navigation--next:hover {
952 | border-left-color: #b3b3b3;
953 | }
954 |
955 | .react-datepicker__portal .react-datepicker__navigation--next--disabled,
956 | .react-datepicker__portal .react-datepicker__navigation--next--disabled:hover {
957 | border-left-color: #e6e6e6;
958 | cursor: default;
959 | }
960 |
--------------------------------------------------------------------------------