diff --git a/package-lock.json b/package-lock.json index 8d9da51..79a42bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,11 @@ "name": "freethyme", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.8.2", + "@emotion/styled": "^11.8.1", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", + "@mui/material": "^5.5.2", "@reduxjs/toolkit": "^1.8.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", @@ -30,9 +33,12 @@ "react-schedule-selector": "^3.0.0", "react-scripts": "5.0.0", "sass": "^1.49.9", - "styled-components": "^5.3.3", + "styled-components": "^5.3.5", "typescript": "~4.1.5", "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/react-copy-to-clipboard": "^5.0.2" } }, "node_modules/@ampproject/remapping": { @@ -1988,6 +1994,86 @@ "postcss": "^8.3" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz", + "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -2043,6 +2129,66 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, + "node_modules/@emotion/react": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.2.tgz", + "integrity": "sha512-+1bcHBaNJv5nkIIgnGKVsie3otS0wF9f1T1hteF3WeVvMNQEtfZ4YyFpnphGoot3ilU/wWMgP2SgIDuHLE/wAA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/react/node_modules/@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/@emotion/react/node_modules/@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/react/node_modules/@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "node_modules/@emotion/react/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, "node_modules/@emotion/serialize": { "version": "0.11.16", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", @@ -2066,16 +2212,28 @@ "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" }, "node_modules/@emotion/styled": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz", - "integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==", + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz", + "integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==", "dependencies": { - "@emotion/styled-base": "^10.3.0", - "babel-plugin-emotion": "^10.0.27" + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/is-prop-valid": "^1.1.2", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0" }, "peerDependencies": { - "@emotion/core": "^10.0.27", - "react": ">=16.3.0" + "@babel/core": "^7.0.0", + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, "node_modules/@emotion/styled-base": { @@ -2093,6 +2251,31 @@ "react": ">=16.3.0" } }, + "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "dependencies": { + "@emotion/memoize": "^0.7.4" + } + }, + "node_modules/@emotion/styled/node_modules/@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/styled/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, "node_modules/@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -3043,6 +3226,241 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, + "node_modules/@mui/base": { + "version": "5.0.0-alpha.73", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.73.tgz", + "integrity": "sha512-TEUCIIEAWrngAqpIa+dY3nofGSNj70LC3KC9WcCzyXPK3M4AG2GNi7ndd/g/0DtC55kbxrudzlV8TG3vrB2Vjw==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/utils": "^5.4.4", + "@popperjs/core": "^2.11.4", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^17.0.0", + "react-dom": "^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "dependencies": { + "@emotion/memoize": "^0.7.4" + } + }, + "node_modules/@mui/material": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.5.2.tgz", + "integrity": "sha512-r4p1u9eDlSqW3TS/Iq9yolifWHpuW6e0BSeqEJW3EEIcKfPVVk4WNUNJ+s8DtN7dBoDcveXxcQVVjYXTIv1d9g==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.73", + "@mui/system": "^5.5.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^17.0.0", + "react-dom": "^17.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.4.tgz", + "integrity": "sha512-V/gxttr6736yJoU9q+4xxXsa0K/w9Hn9pg99zsOHt7i/O904w2CX5NHh5WqDXtoUzVcayLF0RB17yr6l79CE+A==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.4.4", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.5.2.tgz", + "integrity": "sha512-jkz5AHHbA43akBo5L3y1X1/X0f+RvXvCp3eXKt+iOf3qnKSAausbtlVz7gBbC4xIWDnP1Jb/6T+t/0/7gObRYA==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@emotion/cache": "^11.7.1", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine/node_modules/@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/@mui/styled-engine/node_modules/@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "node_modules/@mui/styled-engine/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@mui/system": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.5.2.tgz", + "integrity": "sha512-OATYFI36nliud8xh0u+ZNqDo0jWjxpO0vZLlzqNB+ZtkR5Q/+1X3GgboA9ruiB8Rq+udnJlMBQNGW0qqjvAOHQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.4.4", + "@mui/styled-engine": "^5.5.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^17.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz", + "integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@types/prop-types": "^15.7.4", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0" + } + }, "node_modules/@node-redis/bloom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", @@ -4360,6 +4778,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "16.9.14", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", @@ -4368,6 +4795,14 @@ "@types/react": "^16" } }, + "node_modules/@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.23", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.23.tgz", @@ -6915,9 +7350,9 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, "node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -14515,6 +14950,19 @@ "styled-components": ">=5.0" } }, + "node_modules/react-schedule-selector/node_modules/@emotion/styled": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz", + "integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==", + "dependencies": { + "@emotion/styled-base": "^10.3.0", + "babel-plugin-emotion": "^10.0.27" + }, + "peerDependencies": { + "@emotion/core": "^10.0.27", + "react": ">=16.3.0" + } + }, "node_modules/react-schedule-selector/node_modules/@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -15913,13 +16361,14 @@ } }, "node_modules/styled-components": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", - "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", + "hasInstallScript": true, "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", + "@emotion/is-prop-valid": "^1.1.0", "@emotion/stylis": "^0.8.4", "@emotion/unitless": "^0.7.4", "babel-plugin-styled-components": ">= 1.12.0", @@ -15941,6 +16390,14 @@ "react-is": ">= 16.8.0" } }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "dependencies": { + "@emotion/memoize": "^0.7.4" + } + }, "node_modules/styled-components/node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -15961,6 +16418,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -19010,6 +19472,76 @@ "postcss-value-parser": "^4.2.0" } }, + "@emotion/babel-plugin": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz", + "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + } + } + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -19062,6 +19594,56 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, + "@emotion/react": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.2.tgz", + "integrity": "sha512-+1bcHBaNJv5nkIIgnGKVsie3otS0wF9f1T1hteF3WeVvMNQEtfZ4YyFpnphGoot3ilU/wWMgP2SgIDuHLE/wAA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "dependencies": { + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + } + } + }, "@emotion/serialize": { "version": "0.11.16", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", @@ -19087,12 +19669,42 @@ "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" }, "@emotion/styled": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz", - "integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==", + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz", + "integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==", "requires": { - "@emotion/styled-base": "^10.3.0", - "babel-plugin-emotion": "^10.0.27" + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/is-prop-valid": "^1.1.2", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, + "@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + } } }, "@emotion/styled-base": { @@ -19779,6 +20391,126 @@ "react-is": "^16.8.0 || ^17.0.0" } }, + "@mui/base": { + "version": "5.0.0-alpha.73", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.73.tgz", + "integrity": "sha512-TEUCIIEAWrngAqpIa+dY3nofGSNj70LC3KC9WcCzyXPK3M4AG2GNi7ndd/g/0DtC55kbxrudzlV8TG3vrB2Vjw==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/utils": "^5.4.4", + "@popperjs/core": "^2.11.4", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + } + } + }, + "@mui/material": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.5.2.tgz", + "integrity": "sha512-r4p1u9eDlSqW3TS/Iq9yolifWHpuW6e0BSeqEJW3EEIcKfPVVk4WNUNJ+s8DtN7dBoDcveXxcQVVjYXTIv1d9g==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.73", + "@mui/system": "^5.5.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + } + }, + "@mui/private-theming": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.4.tgz", + "integrity": "sha512-V/gxttr6736yJoU9q+4xxXsa0K/w9Hn9pg99zsOHt7i/O904w2CX5NHh5WqDXtoUzVcayLF0RB17yr6l79CE+A==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.4.4", + "prop-types": "^15.7.2" + } + }, + "@mui/styled-engine": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.5.2.tgz", + "integrity": "sha512-jkz5AHHbA43akBo5L3y1X1/X0f+RvXvCp3eXKt+iOf3qnKSAausbtlVz7gBbC4xIWDnP1Jb/6T+t/0/7gObRYA==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/cache": "^11.7.1", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + } + } + }, + "@mui/system": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.5.2.tgz", + "integrity": "sha512-OATYFI36nliud8xh0u+ZNqDo0jWjxpO0vZLlzqNB+ZtkR5Q/+1X3GgboA9ruiB8Rq+udnJlMBQNGW0qqjvAOHQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.4.4", + "@mui/styled-engine": "^5.5.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "prop-types": "^15.7.2" + } + }, + "@mui/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz", + "integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==", + "requires": {} + }, + "@mui/utils": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@types/prop-types": "^15.7.4", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + } + }, "@node-redis/bloom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", @@ -20777,6 +21509,15 @@ "csstype": "^3.0.2" } }, + "@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.14", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", @@ -20785,6 +21526,14 @@ "@types/react": "^16" } }, + "@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "requires": { + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.23", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.23.tgz", @@ -21943,7 +22692,6 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, -<<<<<<< HEAD "boostrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/boostrap/-/boostrap-2.0.0.tgz", @@ -21955,8 +22703,6 @@ "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", "requires": {} }, -======= ->>>>>>> d33b06cdc4352969a07e70b0790904f0794343af "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -22178,7 +22924,6 @@ "wrap-ansi": "^7.0.0" } }, -<<<<<<< HEAD "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -22189,8 +22934,6 @@ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" }, -======= ->>>>>>> d33b06cdc4352969a07e70b0790904f0794343af "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -22707,9 +23450,9 @@ } }, "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "damerau-levenshtein": { "version": "1.0.8", @@ -28118,6 +28861,15 @@ "src": "^1.1.2" }, "dependencies": { + "@emotion/styled": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz", + "integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==", + "requires": { + "@emotion/styled-base": "^10.3.0", + "babel-plugin-emotion": "^10.0.27" + } + }, "@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -28279,7 +29031,6 @@ "workbox-webpack-plugin": "^6.4.1" } }, -<<<<<<< HEAD "react-transition-group": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", @@ -28291,8 +29042,6 @@ "prop-types": "^15.6.2" } }, -======= ->>>>>>> d33b06cdc4352969a07e70b0790904f0794343af "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -29173,13 +29922,13 @@ "requires": {} }, "styled-components": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", - "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz", + "integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", + "@emotion/is-prop-valid": "^1.1.0", "@emotion/stylis": "^0.8.4", "@emotion/unitless": "^0.7.4", "babel-plugin-styled-components": ">= 1.12.0", @@ -29189,6 +29938,14 @@ "supports-color": "^5.5.0" }, "dependencies": { + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -29205,6 +29962,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index e8cdc83..f4781cf 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.8.2", + "@emotion/styled": "^11.8.1", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", + "@mui/material": "^5.5.2", "@reduxjs/toolkit": "^1.8.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", @@ -25,7 +28,7 @@ "react-schedule-selector": "^3.0.0", "react-scripts": "5.0.0", "sass": "^1.49.9", - "styled-components": "^5.3.3", + "styled-components": "^5.3.5", "typescript": "~4.1.5", "uuid": "^8.3.2" }, @@ -49,5 +52,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/react-copy-to-clipboard": "^5.0.2" } } diff --git a/src/App.tsx b/src/App.tsx index dc4149d..8b6f2f6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,20 @@ import Landing from './pages/Landing'; -import Schedule from './pages/Schedule'; +import Meeting from './pages/Meeting'; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; function App() { + + return ( <> - } /> - } /> + } /> + }> + + diff --git a/src/api/api.js b/src/api/api.js new file mode 100644 index 0000000..c6eb5fa --- /dev/null +++ b/src/api/api.js @@ -0,0 +1,107 @@ +import axios from "axios"; +import { store } from "../app/store"; +import { setCurMemberSlots, updateMemberSlots } from "../pages/meetingSlice"; + +const axiosInstance = axios.create({ + baseURL: "http://localhost:4000", +}); + +async function createMeeting(meetingName) { + try { + const response = await axiosInstance.post(`/api/calendars`, {name: meetingName}); + return response.data; + // { + // "id": integer, + // "name": string, + // "members": [] + // } + } catch(err) { + console.log(err); + } +} + +async function getMeeting(meetingId) { + try{ + const response = await axiosInstance.get(`/api/calendars/${meetingId}`); + return response.data; + // { + // "id": integer, (meetingId) + // "name": string, + // "members": list[Member] + // } + } catch(err){ + console.log(err) + } +} + +async function renameMeeting(meetingId, newName) { + try{ + const response = await axiosInstance.patch(`/api/calendars/${meetingId}`, {name:newName}); + return response.data; + // { + // "id": integer, (meetingId) + // "name": string, + // "members": list[Member] + // } + } catch(err){ + console.log(err) + } +} + +async function addNewMember(meetingId, memberName) { + try { + const response = await axiosInstance.post(`/api/calendars/${meetingId}/members/`, {name: memberName}); + return response.data; + // { + // "id": integer, + // "name": string, + // "timeSlots": [] + // } + } catch(err) { + console.log(err); + } +} + +async function setAvail(meetingId, memberId, timeSlots) { + try { + store.dispatch(setCurMemberSlots(timeSlots)) + const response = await axiosInstance.put(`/api/calendars/${meetingId}/members/${memberId}`, { timeSlots }); + store.dispatch(updateMemberSlots({id: memberId, name: response.data.name, timeSlots: timeSlots })) + return response.data; + // { + // "id": integer + // "name": string + // "timeSlots": list[TimeSlot] + // } + } catch(err) { + console.log(err); + } +} + +// don't use, member name unique +async function renameMember(meetingId, memberId, newName) { + try{ + const response = await axiosInstance.patch(`/api/calendars/${meetingId}/members/${memberId}`, {name:newName}); + return response.data; + // { + // "id": integer, + // "name": string, + // "timeSlots": list[TimeSlot] + // } + } catch(err){ + console.log(err) + } +} + + +async function removeMember(meetingId, memberId) { + try{ + await axiosInstance.delete(`/api/calendars/${meetingId}/members/${memberId}`); + } catch(err) { + console.log(err); + } + +} + +export { createMeeting, getMeeting, removeMember, renameMeeting, renameMember, addNewMember, setAvail }; + diff --git a/src/api/dateFormateConverter.ts b/src/api/dateFormateConverter.ts new file mode 100644 index 0000000..3f07da6 --- /dev/null +++ b/src/api/dateFormateConverter.ts @@ -0,0 +1,44 @@ +import { TimeSlotType } from "../components/TimeSlot" + +export function convertTStoDate(slots: TimeSlotType[]){ + const dayMap : any = {"Sunday":0, "Monday":1, "Tuesday":2, "Wednesday":3, "Thursday":4, "Friday":5, "Saturday":6} as Object + const present = new Date(); + const currYear = present.getFullYear(); + let currDayOfMonth = present.getDate(); + let currWeekday = present.getDay(); + const currMonth = present.getMonth() + let converted = [] as Date[] + // need to extract hours from no of mins + slots.forEach(slot => { + let daysToAdd; + let slotDay = dayMap[slot.day] as number + daysToAdd = slotDay - currWeekday + // 1410 becomes 23.5 + let hourFloat = slot.timeStart/60 + let hour = Math.floor(hourFloat) + let minutes = 60 * (hourFloat % 1) // 30 or 0 + converted.push(new Date(currYear, currMonth, currDayOfMonth + daysToAdd, hour, minutes)) + }); + return converted +} + +export function convertDatetoTS(dates: Date[], meetingId:number, memberId:number ) { + const timeData = dates.map((date: Date)=>{ + const formattedDate = new Intl.DateTimeFormat('en-GB', { weekday: 'long', hour: 'numeric', minute: 'numeric' }).format(date); + // "Saturday 23:30" + const [day, time] = formattedDate.split(" "); + const [hour, minute] = time.split(":"); + const timeStart = parseInt(hour) * 60 + parseInt(minute); + const timeEnd = timeStart + 30; + const data = { + id : meetingId, + memberId, + day, + timeStart, + timeEnd + }; + return data; + }); + return timeData +} + diff --git a/src/app/store.ts b/src/app/store.ts index 5207999..fa72714 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -1,10 +1,10 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; -import meetingNameReducer from '../pages/meetingNameSlice'; +import meetingReducer from '../pages/meetingSlice'; export const store = configureStore({ reducer: { - meetingName: meetingNameReducer + meeting: meetingReducer }, }); diff --git a/src/components/Add.tsx b/src/components/Add.tsx new file mode 100644 index 0000000..09dacb9 --- /dev/null +++ b/src/components/Add.tsx @@ -0,0 +1,80 @@ +import { addMember, selectMembers, setCurMemberId, setCurMemberName, setCurMemberSlots } from "../pages/meetingSlice"; +import { useAppDispatch, useAppSelector } from "../app/hooks"; +import * as API from "../api/api"; +import { useState } from "react"; +import { TextField } from '@material-ui/core'; +import { TimeSlotType } from "./TimeSlot"; + +const Add = () => { + const dispatch = useAppDispatch(); + const [memberName, setMemberName] = useState(""); + const meetingId = useAppSelector(state => state.meeting.id); + const members = useAppSelector(state => state.meeting.members); + const memberSlots = useAppSelector(state => state.meeting.curMemberSlots); + const selectedMembers = useAppSelector(state => state.meeting.selectedMembers); + + + const addMemberName = (event: any) => { + const element = event.currentTarget as HTMLInputElement; + setMemberName(element.value); + }; + + const handleSubmit = async (event: any) => { + event.preventDefault(); + // treat "A" the same as "A " + const trimmedName = memberName.trim() + if (trimmedName === "") { + alert("Name can't be blank!"); + return; + } + if (selectedMembers.length !== 0) { + alert("Unselect all members to add new availability!") + return; + } + for (let i = 0; i < members.length; i++) { + if (members[i].name === trimmedName) { + alert("Member with this name already exists!"); + return; + } + } + + // new member + + let data = await API.addNewMember(meetingId, trimmedName); + console.log("create new member") + dispatch(setCurMemberId(data.id)); + dispatch(setCurMemberName(data.name)); + dispatch(addMember({ id: data.id, name: data.name, timeSlots: data.timeSlots })); + + // memberId is undefined in slots at this point + const memberSlotsWithId = memberSlots.map((slot: TimeSlotType) => { + return {...slot, memberId: data.id} + }) + + API.setAvail(meetingId, data.id, memberSlotsWithId) + + }; + + return ( +
+
+ +
+
+ +
+
+ ) +} + +export default Add; diff --git a/src/components/AddAvail.tsx b/src/components/AddAvail.tsx new file mode 100644 index 0000000..9bdae39 --- /dev/null +++ b/src/components/AddAvail.tsx @@ -0,0 +1,29 @@ +import * as API from "../api/api"; + + +async function addAvail(meetingId: number, memberId: number, timeSlots: Array) { + + if (meetingId && memberId && timeSlots.length !== 0) { + const timeData = timeSlots.map((date: Date)=>{ + const formattedDate = new Intl.DateTimeFormat('en-GB', { weekday: 'long', hour: 'numeric', minute: 'numeric' }).format(date); + // "Saturday 23:30" + const [day, time] = formattedDate.split(" "); + const [hour, minute] = time.split(":"); + const timeStart = parseInt(hour) * 60 + parseInt(minute); + const timeEnd = timeStart + 30; + const data = { + id : meetingId, + memberId, + day, + timeStart, + timeEnd + }; + return data; + }); + await API.setAvail(meetingId, memberId, timeData); + console.log("set member availability succeeded"); + } + +} + +export { addAvail }; \ No newline at end of file diff --git a/src/components/Available.tsx b/src/components/Available.tsx new file mode 100644 index 0000000..c37984d --- /dev/null +++ b/src/components/Available.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useAppSelector } from '../app/hooks'; +const Available = () => { + const members = useAppSelector(state => state.meeting.hoveredMembers) + + return ( +
+ Available members: {members.join(", ")} +
+ ) +} + +export default Available; \ No newline at end of file diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx new file mode 100644 index 0000000..f5316d3 --- /dev/null +++ b/src/components/Calendar.tsx @@ -0,0 +1,138 @@ +import { useEffect, useState } from "react"; +import { setCurMemberId, setCurMemberSlots, updateHoveredMembers } from "../pages/meetingSlice"; +import { addAvail } from "./AddAvail"; +import { useAppSelector, useAppDispatch } from "../app/hooks"; +// @ts-ignore +import ScheduleSelector from "react-schedule-selector"; +import { convertTStoDate, convertDatetoTS } from "../api/dateFormateConverter"; +import { updateChangedAlready } from "../pages/meetingSlice"; + +const Calendar = () => { + const dispatch = useAppDispatch(); + const meetingId = useAppSelector(state => state.meeting.id); + const memberId = useAppSelector(state => state.meeting.curMemberId); + const members = useAppSelector(state => state.meeting.members); + const curMemberSlots = useAppSelector(state => state.meeting.curMemberSlots); + const selectedMembers = useAppSelector(state => state.meeting.selectedMembers); + const changedAlready = useAppSelector(state => state.meeting.changedAlready); + + const [timeSlots, setTimeSlots] = useState([]); + + const handleChange = (event:any) => { + + if (selectedMembers.length === 0) { + setTimeSlots(event) + dispatch(setCurMemberSlots(convertDatetoTS(timeSlots, meetingId, selectedMembers[0]))) + return; + } + // let user select slots only after their previous slots have been fetched + if (changedAlready === true) { + + setTimeSlots(event) + dispatch(setCurMemberId(selectedMembers[0])) + dispatch(setCurMemberSlots(convertDatetoTS(timeSlots, meetingId, selectedMembers[0]))) + } + } + + if (selectedMembers.length === 1 && changedAlready === false) { + for (let i = 0; i < members.length; i++) { + if (members[i].id === selectedMembers[0]) { + let convertedDates = convertTStoDate(members[i].timeSlots) + dispatch(updateChangedAlready(true)) + setTimeSlots(convertedDates) + break; + } + } + } + + // make sure that changedAlready is false when we go back to 1 selected member + if (selectedMembers.length > 1) { + dispatch(updateChangedAlready(false)) + } + // changedAlready is always true when we first reach 0 selected members + else if (selectedMembers.length === 0 && changedAlready === true) { + dispatch(updateChangedAlready(false)) + setTimeSlots([]) + } + + + + const renderDateCell = (date: Date, selected: boolean, refSetter: (dateCell: HTMLElement | null) => void) => { + const formattedDate = new Intl.DateTimeFormat('en-GB', { weekday: 'long', hour: 'numeric', minute: 'numeric' }).format(date); + // "Saturday 23:30" + const [day, time] = formattedDate.split(" "); + const [hour, minute] = time.split(":"); + const timeStart = parseInt(hour) * 60 + parseInt(minute); + const timeEnd = timeStart + 30; + + // default cell color, unselected + let backColor = "gray"; + let opacity = 1; + + let membersInSlot = [] as string[]; + let numMembersInSlot = 0; + + members.forEach((member)=>{ + if (selectedMembers.includes(member.id)) { + const slots = member.timeSlots; + slots.forEach((slot)=>{ + if (slot.day === day + && slot.timeStart === timeStart + && slot.timeEnd === timeEnd) { + numMembersInSlot++; + membersInSlot.push(member.name); + } + }); + } + }); + + if (selectedMembers.length <= 1) { + if (selected) { + backColor = "green" + } + } + else { + if (numMembersInSlot !== 0) { + backColor = "blue" + opacity = numMembersInSlot/members.length + } + } + + return ( + + ); + + } + + + return ( +
+ + +
+ ) +} + +export default Calendar; \ No newline at end of file diff --git a/src/components/Confirm.tsx b/src/components/Confirm.tsx new file mode 100644 index 0000000..8949d0f --- /dev/null +++ b/src/components/Confirm.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import * as API from "../api/api"; +import { useAppSelector } from '../app/hooks'; + +const Confirm = () => { + const meetingId = useAppSelector(state => state.meeting.id); + const memberId = useAppSelector(state => state.meeting.curMemberId); + const curMemberSlots = useAppSelector(state => state.meeting.curMemberSlots); + const selectedMembers = useAppSelector(state => state.meeting.selectedMembers) + + const handleClick = () => { + if (selectedMembers.length !== 1) { + alert("Select one member to update their availability!"); + return; + } + API.setAvail(meetingId, memberId, curMemberSlots) + } + return ( +
+ + +
+ ) +} + +export default Confirm; diff --git a/src/components/CreateMeeting.tsx b/src/components/CreateMeeting.tsx index 5e92ba8..3c33849 100644 --- a/src/components/CreateMeeting.tsx +++ b/src/components/CreateMeeting.tsx @@ -1,66 +1,56 @@ -import React, { useState, useEffect } from "react"; -import { Grid, FormControl, TextField} from '@material-ui/core' +import React, { useState } from "react"; +import { FormControl, TextField} from '@material-ui/core' import { useAppDispatch } from '../app/hooks' -import { createMeeting } from "../pages/meetingNameSlice"; -import { useNavigate } from 'react-router-dom' -import '../styles/button.css' -import axios from 'axios' - -const styles = { - display: "flex", - justifyContent: "center", - alignItems: "center", -} +import { createMeeting } from "../pages/meetingSlice"; +import { useNavigate } from 'react-router-dom'; +import '../styles/button.css'; +import '../styles/landingPage.css'; +import * as API from "../api/api"; const CreateMeeting = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); - const [name, setName] = useState(""); + const [name, setName] = useState("Unnamed Meeting"); - const handleSubmit = (event) => { + const handleSubmit = async (event: any) => { event.preventDefault(); - let id: number; - axios.post("http://localhost:4000/api/calendars/", { name }) - .then((response)=>{ - id = response.data.id; - dispatch(createMeeting({ id, name })); - navigate("/create"); - }) - .catch((error)=>{ - console.log(error); - }); + const data = await API.createMeeting(name); + const id = data.id; + dispatch(createMeeting({ id, name })); + navigate("/" + id); }; // Update name every time user changes text in text field - const updateName = (event) => { - setName(event.target.value); + const updateName = (event: any) => { + const element = event.currentTarget as HTMLInputElement; + setName(element.value); }; return ( - - - - + <> +
+
+ + + +
+
- +
+
+ + ) } diff --git a/src/components/Delete.tsx b/src/components/Delete.tsx new file mode 100644 index 0000000..c15f004 --- /dev/null +++ b/src/components/Delete.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useAppDispatch, useAppSelector } from '../app/hooks'; +import { removeMember } from '../pages/meetingSlice'; +import * as API from "../api/api"; +const Delete = () => { + const dispatch = useAppDispatch(); + const selectedMembers = useAppSelector(state => state.meeting.selectedMembers); + const meetingId = useAppSelector(state => state.meeting.id); + const handleClick = () => { + if (selectedMembers.length === 0) { + alert("No members selected!"); + return; + } + selectedMembers.forEach((memberId) => { + API.removeMember(meetingId, memberId); + dispatch(removeMember(memberId)); + }) + + } + return ( +
+ +
+ ) +} + +export default Delete; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index c8a8322..6d98d36 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,30 +1,35 @@ -import React, { useState } from "react" import Share from "./Share"; -import Edit from "./Edit"; import Add from "./Add"; +import Delete from "./Delete"; +import Confirm from "./Confirm"; import { useAppSelector } from "../app/hooks"; -const Header = ({name}) => { - const meetingName = useAppSelector(state=> state.meetingName.meetingName); - - const [Header, setHeader] = useState({meetingName:meetingName, userName:"", selectedTimes: false}); +const Header = () => { + const meetingName = useAppSelector(state=> state.meeting.name); return (
-
-

{meetingName}

+
+

{meetingName}

-
-

{name}

+
+
+ + +
+ +
+ +
+
-
- {/* if user has already selected time then let them edit*/} - {Header.selectedTimes? : } +
+
-
+
diff --git a/src/components/Member.tsx b/src/components/Member.tsx new file mode 100644 index 0000000..6d3fd4f --- /dev/null +++ b/src/components/Member.tsx @@ -0,0 +1,21 @@ +import React, { useState } from "react" +import { TimeSlotType } from "./TimeSlot"; + +const Member = () => { + const [name, setName] = useState(""); + const [timeSlots, setTimeSlots] = useState([]); + + return ( +
+

Participants

+
+ ) +} + +export default Member; + +export interface MemberType { + id: number; + name: string; + timeSlots: TimeSlotType[]; +}; \ No newline at end of file diff --git a/src/components/MemberList.tsx b/src/components/MemberList.tsx new file mode 100644 index 0000000..3292356 --- /dev/null +++ b/src/components/MemberList.tsx @@ -0,0 +1,72 @@ +import { Box, List, ListItem, ListItemIcon, ListItemText, Checkbox, Divider } from '@material-ui/core'; +import { AccountCircle } from '@material-ui/icons'; +import { useState } from 'react'; +import { useAppSelector, useAppDispatch} from '../app/hooks'; +import { selectMembers } from '../pages/meetingSlice'; + + +function MemberList() { + const dispatch = useAppDispatch(); + const members = useAppSelector(state => state.meeting.members); + + const [selected, setSelected] = useState([]); + + const handleSelection = (id: number) => () => { + const current = selected.indexOf(id); + const newSelected = [...selected]; + + if (current === -1) { + newSelected.push(id); + } else { + newSelected.splice(current, 1); + } + setSelected(newSelected); + dispatch(selectMembers(newSelected)); + }; + + const handleClick = () => { + const allSelected = members.map(member => member.id) + setSelected(allSelected) + dispatch(selectMembers(allSelected)) + } + + return ( + + +
+
+ +
+
+ +
+
+ + + {members.map((member)=>{ + return ( + + + + + + + + ) + })} + +
+
+ + ); + +} + + +export default MemberList; \ No newline at end of file diff --git a/src/components/Share.tsx b/src/components/Share.tsx new file mode 100644 index 0000000..02aaab7 --- /dev/null +++ b/src/components/Share.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +const Share = () => { + + const handleClick = () => { + navigator.clipboard.writeText(window.location.href); + + let prompt = document.getElementById("1") as HTMLElement + prompt.style.display = ""; + + const hidePrompt = () => { + prompt.style.display = "none" + } + setTimeout(hidePrompt, 1500) + } + return ( +
+ + Link copied. +
+ ) +} + +export default Share; diff --git a/src/components/TimeSlot.tsx b/src/components/TimeSlot.tsx new file mode 100644 index 0000000..25b2318 --- /dev/null +++ b/src/components/TimeSlot.tsx @@ -0,0 +1,7 @@ +export interface TimeSlotType { + id: number; + memberId: number; + day: string; + timeStart: number; + timeEnd: number; +} diff --git a/src/features/counter/Counter.module.css b/src/features/counter/Counter.module.css deleted file mode 100644 index 025bb72..0000000 --- a/src/features/counter/Counter.module.css +++ /dev/null @@ -1,79 +0,0 @@ -.row { - display: flex; - align-items: center; - justify-content: center; -} - -.row > button { - margin-left: 4px; - margin-right: 8px; -} - -.row:not(:last-child) { - margin-bottom: 16px; -} - -.value { - font-size: 78px; - padding-left: 16px; - padding-right: 16px; - margin-top: 2px; - font-family: 'Courier New', Courier, monospace; -} - -.button { - appearance: none; - background: none; - font-size: 32px; - padding-left: 12px; - padding-right: 12px; - outline: none; - border: 2px solid transparent; - color: rgb(112, 76, 182); - padding-bottom: 4px; - cursor: pointer; - background-color: rgba(112, 76, 182, 0.1); - border-radius: 2px; - transition: all 0.15s; -} - -.textbox { - font-size: 32px; - padding: 2px; - width: 64px; - text-align: center; - margin-right: 4px; -} - -.button:hover, -.button:focus { - border: 2px solid rgba(112, 76, 182, 0.4); -} - -.button:active { - background-color: rgba(112, 76, 182, 0.2); -} - -.asyncButton { - composes: button; - position: relative; -} - -.asyncButton:after { - content: ''; - background-color: rgba(112, 76, 182, 0.15); - display: block; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - opacity: 0; - transition: width 1s linear, opacity 0.5s ease 1s; -} - -.asyncButton:active:after { - width: 0%; - opacity: 1; - transition: 0s; -} diff --git a/src/features/counter/Counter.tsx b/src/features/counter/Counter.tsx deleted file mode 100644 index ece5191..0000000 --- a/src/features/counter/Counter.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useState } from 'react'; - -import { useAppSelector, useAppDispatch } from '../../app/hooks'; -import { - decrement, - increment, - incrementByAmount, - incrementAsync, - incrementIfOdd, - selectCount, -} from './counterSlice'; -import styles from './Counter.module.css'; - -export function Counter() { - const count = useAppSelector(selectCount); - const dispatch = useAppDispatch(); - const [incrementAmount, setIncrementAmount] = useState('2'); - - const incrementValue = Number(incrementAmount) || 0; - - return ( -
-
- - {count} - -
-
- setIncrementAmount(e.target.value)} - /> - - - -
-
- ); -} diff --git a/src/features/counter/counterAPI.ts b/src/features/counter/counterAPI.ts deleted file mode 100644 index 0a9cdd3..0000000 --- a/src/features/counter/counterAPI.ts +++ /dev/null @@ -1,6 +0,0 @@ -// A mock function to mimic making an async request for data -export function fetchCount(amount = 1) { - return new Promise<{ data: number }>((resolve) => - setTimeout(() => resolve({ data: amount }), 500) - ); -} diff --git a/src/features/counter/counterSlice.spec.ts b/src/features/counter/counterSlice.spec.ts deleted file mode 100644 index 098163b..0000000 --- a/src/features/counter/counterSlice.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import counterReducer, { - CounterState, - increment, - decrement, - incrementByAmount, -} from './counterSlice'; - -describe('counter reducer', () => { - const initialState: CounterState = { - value: 3, - status: 'idle', - }; - it('should handle initial state', () => { - expect(counterReducer(undefined, { type: 'unknown' })).toEqual({ - value: 0, - status: 'idle', - }); - }); - - it('should handle increment', () => { - const actual = counterReducer(initialState, increment()); - expect(actual.value).toEqual(4); - }); - - it('should handle decrement', () => { - const actual = counterReducer(initialState, decrement()); - expect(actual.value).toEqual(2); - }); - - it('should handle incrementByAmount', () => { - const actual = counterReducer(initialState, incrementByAmount(2)); - expect(actual.value).toEqual(5); - }); -}); diff --git a/src/features/counter/counterSlice.ts b/src/features/counter/counterSlice.ts deleted file mode 100644 index 0010332..0000000 --- a/src/features/counter/counterSlice.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { RootState, AppThunk } from '../../app/store'; -import { fetchCount } from './counterAPI'; - -export interface CounterState { - value: number; - status: 'idle' | 'loading' | 'failed'; -} - -const initialState: CounterState = { - value: 0, - status: 'idle', -}; - -// The function below is called a thunk and allows us to perform async logic. It -// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This -// will call the thunk with the `dispatch` function as the first argument. Async -// code can then be executed and other actions can be dispatched. Thunks are -// typically used to make async requests. -export const incrementAsync = createAsyncThunk( - 'counter/fetchCount', - async (amount: number) => { - const response = await fetchCount(amount); - // The value we return becomes the `fulfilled` action payload - return response.data; - } -); - -export const counterSlice = createSlice({ - name: 'counter', - initialState, - // The `reducers` field lets us define reducers and generate associated actions - reducers: { - increment: (state) => { - // Redux Toolkit allows us to write "mutating" logic in reducers. It - // doesn't actually mutate the state because it uses the Immer library, - // which detects changes to a "draft state" and produces a brand new - // immutable state based off those changes - state.value += 1; - }, - decrement: (state) => { - state.value -= 1; - }, - // Use the PayloadAction type to declare the contents of `action.payload` - incrementByAmount: (state, action: PayloadAction) => { - state.value += action.payload; - }, - }, - // The `extraReducers` field lets the slice handle actions defined elsewhere, - // including actions generated by createAsyncThunk or in other slices. - extraReducers: (builder) => { - builder - .addCase(incrementAsync.pending, (state) => { - state.status = 'loading'; - }) - .addCase(incrementAsync.fulfilled, (state, action) => { - state.status = 'idle'; - state.value += action.payload; - }); - }, -}); - -export const { increment, decrement, incrementByAmount } = counterSlice.actions; - -// The function below is called a selector and allows us to select a value from -// the state. Selectors can also be defined inline where they're used instead of -// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)` -export const selectCount = (state: RootState) => state.counter.value; - -// We can also write thunks by hand, which may contain both sync and async logic. -// Here's an example of conditionally dispatching actions based on current state. -export const incrementIfOdd = (amount: number): AppThunk => ( - dispatch, - getState -) => { - const currentValue = selectCount(getState()); - if (currentValue % 2 === 1) { - dispatch(incrementByAmount(amount)); - } -}; - -export default counterSlice.reducer; diff --git a/src/pages/Meeting.tsx b/src/pages/Meeting.tsx new file mode 100644 index 0000000..5813a1c --- /dev/null +++ b/src/pages/Meeting.tsx @@ -0,0 +1,53 @@ +import Calendar from '../components/Calendar'; +import Header from '../components/Header'; +import MemberList from '../components/MemberList'; +import Available from "../components/Available"; +import * as API from "../api/api"; +import { useEffect } from "react"; +import '../styles/meeting.css'; +import { updateMeetingId, updateMembers, updateMeetingName } from './meetingSlice'; +import { useAppDispatch, useAppSelector } from '../app/hooks'; +import { useParams } from 'react-router-dom'; + +function Meeting() { + let id = useParams().id as string; + const dispatch = useAppDispatch(); + dispatch(updateMeetingId(parseInt(id))) + let meetingId = useAppSelector(state=>state.meeting.id); + + + useEffect(() => { + const loadData = async () =>{ + const data = await API.getMeeting(meetingId); + dispatch(updateMeetingId(data.id)); + dispatch(updateMeetingName(data.name)); + dispatch(updateMembers(data.members)); + } + if (meetingId) { + loadData().catch(err=>console.log(err)); + } + + }, [meetingId, dispatch]); + + + return( + +
+
+
+
+
+ +
+
+ +
+ +
+ +
+ + ); +} + +export default Meeting; \ No newline at end of file diff --git a/src/pages/Schedule.tsx b/src/pages/Schedule.tsx deleted file mode 100644 index ca68156..0000000 --- a/src/pages/Schedule.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// figma second & third pages -// where users create/edit the schedule - -import Calendar from '../components/Calendar'; -import Participants from '../components/Participants'; -import Header from '../components/Header'; - -function Schedule() { - return( - <> -
- - - - ); -} - -export default Schedule; \ No newline at end of file diff --git a/src/pages/meetingNameSlice.tsx b/src/pages/meetingNameSlice.tsx deleted file mode 100644 index 2905ef0..0000000 --- a/src/pages/meetingNameSlice.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import type { RootState } from '../app/store' -interface MeetingNameState { - id: number - meetingName: string -} - -const initialState: MeetingNameState = { - id: NaN, - meetingName: "" -} - -export const meetingNameSlice = createSlice({ - name: "meeting", - initialState, - reducers: { - createMeeting: (state, action: PayloadAction)=>{ - state.id = action.payload["id"]; - state.meetingName = action.payload["name"]; - console.log(state.id, state.meetingName); - } - } -}) - -export const { createMeeting } = meetingNameSlice.actions; -export const selectMeetingName = (state: RootState) => state.meetingName.meetingName -export default meetingNameSlice.reducer diff --git a/src/pages/meetingSlice.tsx b/src/pages/meetingSlice.tsx new file mode 100644 index 0000000..25c099d --- /dev/null +++ b/src/pages/meetingSlice.tsx @@ -0,0 +1,98 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import type { RootState } from '../app/store' +import type { MemberType } from '../components/Member' +import { TimeSlotType } from '../components/TimeSlot' + +interface MeetingState { + id: number + name: string + members: MemberType[] + curMemberName: string + curMemberId: number, + curMemberSlots: TimeSlotType[], + selectedMembers: number[], + hoveredMembers: string[], + changedAlready: boolean +} + +const initialState: MeetingState = { + id: NaN, + name: "", + members: [], + curMemberName: "", + curMemberId: NaN, + curMemberSlots: [], + selectedMembers: [], + hoveredMembers: [], + changedAlready: false +} + +export const meetingSlice = createSlice({ + name: "meeting", + initialState, + reducers: { + createMeeting: (state, action)=>{ + state.id = action.payload["id"]; + state.name = action.payload["name"]; + }, + updateMeetingId: (state, action: PayloadAction)=>{ + state.id = action.payload; + }, + updateMeetingName: (state, action: PayloadAction)=>{ + state.name = action.payload; + }, + updateMembers: (state, action: PayloadAction)=>{ + state.members = [...action.payload]; + }, + addMember: (state, action: PayloadAction)=>{ + state.members = [action.payload, ...state.members]; + }, + updateMemberSlots: (state, action: PayloadAction)=>{ + for (let i = 0; i < state.members.length; i++) { + if (state.members[i].id === action.payload.id) { + state.members[i].timeSlots = action.payload.timeSlots + } + } + }, + removeMember: (state, action: PayloadAction)=>{ + state.members = [...state.members.filter(member => member.id !== action.payload)]; + state.selectedMembers = [...state.selectedMembers.filter(memberId => memberId !== action.payload)] + }, + setCurMemberName: (state, action: PayloadAction)=>{ + state.curMemberName = action.payload; + }, + setCurMemberId: (state, action: PayloadAction)=>{ + state.curMemberId = action.payload; + }, + setCurMemberSlots: (state, action: PayloadAction)=>{ // date string + state.curMemberSlots = action.payload; + }, + // store member id's + selectMembers: (state, action: PayloadAction )=>{ // for display chosen members' times + state.selectedMembers = action.payload; + }, + updateHoveredMembers: (state, action: PayloadAction)=>{ + state.hoveredMembers = [...action.payload]; + }, + updateChangedAlready: (state, action: PayloadAction)=>{ + state.changedAlready = action.payload; + }, + } +}) + +export const { createMeeting, + updateMeetingId, + updateMeetingName, + updateMembers, + addMember, + removeMember, + setCurMemberName, + setCurMemberId, + setCurMemberSlots, + selectMembers, +updateHoveredMembers, +updateChangedAlready, +updateMemberSlots } = meetingSlice.actions; + +export const selectMeetingName = (state: RootState) => state.meeting.name +export default meetingSlice.reducer diff --git a/src/schedule-selector.d.ts b/src/schedule-selector.d.ts new file mode 100644 index 0000000..76c44c9 --- /dev/null +++ b/src/schedule-selector.d.ts @@ -0,0 +1 @@ +declare module 'react-schedule-selector'; \ No newline at end of file diff --git a/src/styles/button.css b/src/styles/button.css index 77ea99e..0ea9ae8 100644 --- a/src/styles/button.css +++ b/src/styles/button.css @@ -18,11 +18,26 @@ body { box-shadow: 0px 2px 3px 2px rgba(0, 0, 0, 0.2); } -.btn--add { +.btn--create { border: 1px solid #fd7473; background-color: #fd7473; } +.btn--add { + border: 1px solid #35e46f; + background-color: #35e46f; +} + +.btn--delete { + border: 1px solid #e43535; + background-color: #e43535; +} + +.btn--confirm { + border: 1px solid #e4d235; + background-color: #e4d235; +} + .btn--edit { border: 1px solid #d29842; background-color: #d29842; diff --git a/src/styles/landingPage.css b/src/styles/landingPage.css new file mode 100644 index 0000000..4d37dd1 --- /dev/null +++ b/src/styles/landingPage.css @@ -0,0 +1,18 @@ +.flexbox-container { + height: 700px; + width: 100vw; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 30px; +} + +.flexbox-item-1 { + min-width: 80%; +} + +.flexbox-item-2 { + width: 80%; +} + diff --git a/src/styles/meeting.css b/src/styles/meeting.css new file mode 100644 index 0000000..eb0698f --- /dev/null +++ b/src/styles/meeting.css @@ -0,0 +1,25 @@ +.item-header { + grid-area: header; + grid-column: 1.5 / span 2.5; +} +.item-calendar { + grid-area: calendar; +} +.item-list { + grid-area: list; +} +.item-footer { + grid-area: footer; +} + +.meeting-container { + display: grid; + grid-template-columns: repeat(4, 25%); + grid-template-rows: 2fr 7fr 3fr; + grid-template-areas: + "header header header header" + "calendar calendar calendar list" + "footer footer footer footer"; + column-gap:20px; + row-gap: 20px; +} \ No newline at end of file