├── .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 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/assets/deleteIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/assets/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/src/assets/checkboxChecked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | {iconAlt} 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 | ![Node.js CI](https://github.com/boldCM/Starter-Template/workflows/Node.js%20CI/badge.svg) 2 | 3 | # AppEatIt ![AppEatIt Favicon](client/src/assets/favicon.png) 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/calendarFilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/assets/recipeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | 3 | 4 | 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 |
    40 | 41 | 42 | Zur Wochenübersicht 43 | 44 | 45 | Zur Einkaufsliste 46 | 47 | 48 | Zur Rezeptübersicht 49 | 50 | 51 |
    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 | Zur Rezeptübersicht 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 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 |
    80 | 88 | 89 | 97 | 98 | 106 | 107 | 108 |
    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 | --------------------------------------------------------------------------------