diff --git a/.gitignore b/.gitignore index 4d29575..a420806 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,9 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies /node_modules -/.pnp -.pnp.js - -# testing -/coverage - + # production /build -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - npm-debug.log* yarn-debug.log* yarn-error.log* diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..b9e8bcc --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": "./src" + }, + "include": [ + "src", + "src/components/shared/.js" + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 618c52f..c2c8954 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,19 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", + "clsx": "^2.0.0", + "date-fns": "^3.0.0", + "env-cmd": "^10.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.12", + "react-router-dom": "^6.21.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "react-toastify": "^9.1.3", + "sass": "^1.69.5", + "web-vitals": "^2.1.4", + "yup": "^1.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3263,6 +3272,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -5324,6 +5341,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -6023,6 +6063,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6697,6 +6745,15 @@ "node": ">=10" } }, + "node_modules/date-fns": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz", + "integrity": "sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7133,6 +7190,29 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "dependencies": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "bin": { + "env-cmd": "bin/env-cmd.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/env-cmd/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9244,6 +9324,11 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -14436,6 +14521,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14456,6 +14546,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14734,6 +14829,17 @@ "react": "^18.2.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.12.tgz", + "integrity": "sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -14752,6 +14858,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "dependencies": { + "@remix-run/router": "1.14.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.0.tgz", + "integrity": "sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==", + "dependencies": { + "@remix-run/router": "1.14.0", + "react-router": "6.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14824,6 +14960,26 @@ } } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -15309,6 +15465,22 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" }, + "node_modules/sass": { + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -16498,6 +16670,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -16530,6 +16707,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -17932,6 +18114,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.3.3.tgz", + "integrity": "sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -20088,6 +20292,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -21640,6 +21849,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==" }, + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -22156,6 +22387,11 @@ "wrap-ansi": "^7.0.0" } }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -22634,6 +22870,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz", + "integrity": "sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -22955,6 +23196,22 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, + "env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "requires": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + } + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -24480,6 +24737,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" }, + "immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -28037,6 +28299,11 @@ } } }, + "property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -28053,6 +28320,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -28256,6 +28528,14 @@ "scheduler": "^0.23.0" } }, + "react-error-boundary": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.12.tgz", + "integrity": "sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -28271,6 +28551,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "requires": { + "@remix-run/router": "1.14.0" + } + }, + "react-router-dom": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.0.tgz", + "integrity": "sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==", + "requires": { + "@remix-run/router": "1.14.0", + "react-router": "6.21.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -28326,6 +28623,21 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "requires": { + "clsx": "^1.1.1" + }, + "dependencies": { + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + } + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -28663,6 +28975,16 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" }, + "sass": { + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, "sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -29557,6 +29879,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -29580,6 +29907,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -30648,6 +30980,24 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "yup": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.3.3.tgz", + "integrity": "sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw==", + "requires": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } } } } diff --git a/package.json b/package.json index 71b5218..734f49a 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,19 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", + "clsx": "^2.0.0", + "date-fns": "^3.0.0", + "env-cmd": "^10.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.12", + "react-router-dom": "^6.21.0", "react-scripts": "5.0.1", - "web-vitals": "^2.1.4" + "react-toastify": "^9.1.3", + "sass": "^1.69.5", + "web-vitals": "^2.1.4", + "yup": "^1.3.3" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/api/_setup.js b/src/api/_setup.js new file mode 100644 index 0000000..0f2b7f4 --- /dev/null +++ b/src/api/_setup.js @@ -0,0 +1,108 @@ +import axios from "axios"; + +import config from "../config/config.js"; + +const BASE_URL = config.apiBaseUrl; + +/* Adjust authorization according to API */ + +function getAuthToken() { + return localStorage.getItem("authToken"); +} + +const Client = axios.create({ + baseURL: BASE_URL, + headers: { + Authorization: `Bearer ${getAuthToken()}`, + }, +}); + +axios.interceptors.response.use( + (response) => { + return Promise.resolve(response); + }, + (error) => { + if (error.response && error.response.status === 403) { + localStorage.clear(); + return (window.location.href = "/"); + } + + return Promise.reject(error); + } +); + +export const getRequest = (path, query, responseType = "json") => { + return Client.get(`${BASE_URL}${path}`, { + params: query, + responseType: responseType, + headers: { + "Content-Type": "application/json", + }, + }); +}; + +export const postRequestMultiPart = (path, data) => { + return Client.post(`${BASE_URL}${path}`, data, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); +}; + +export const postRequest = (path, data) => { + return Client.post(`${BASE_URL}${path}`, data, { + headers: { + "Content-Type": "application/json", + }, + }); +}; + +export const patchRequest = (path, data) => { + return Client.patch(`${BASE_URL}${path}`, data, { + headers: { + "Content-Type": "application/json", + }, + }); +}; + +export const multiGetRequest = async (paths) => { + let arr = []; + + paths.forEach((item) => { + arr.push(Client.get(`${BASE_URL}${item}`, {})); + }); + return axios.all(arr); +}; + +export const multiPostRequest = async (configs) => { + let arr = []; + configs.forEach((item) => { + arr.push(Client.post(`${BASE_URL}${item.url}`, item.data, {})); + }); + return axios.all(arr); +}; + +export const putRequest = (path, data) => { + return Client.put(`${BASE_URL}${path}`, data, { + headers: { + "Content-Type": "application/json", + }, + }); +}; + +export const headRequest = (path) => { + return Client.head(`${BASE_URL}${path}`, { + headers: { + "Content-Type": "application/json", + }, + }); +}; + +export const deleteRequest = (path, body) => { + return Client.delete(`${BASE_URL}${path}`, { + headers: { + "Content-Type": "application/json", + }, + data: body, + }); +}; diff --git a/src/api/auth.js b/src/api/auth.js new file mode 100644 index 0000000..712221a --- /dev/null +++ b/src/api/auth.js @@ -0,0 +1,5 @@ +import { postRequest } from "./_setup"; + +export function postLoginGoogle(body) { + return postRequest(`/auth/login`, body); +} diff --git a/src/components-layout/Footer/index.js b/src/components-layout/Footer/index.js new file mode 100644 index 0000000..3e65109 --- /dev/null +++ b/src/components-layout/Footer/index.js @@ -0,0 +1,2 @@ +export * from "./Footer"; +export { default } from "./Footer"; diff --git a/src/components-layout/Navbar/Navbar.jsx b/src/components-layout/Navbar/Navbar.jsx new file mode 100644 index 0000000..a501ccf --- /dev/null +++ b/src/components-layout/Navbar/Navbar.jsx @@ -0,0 +1,9 @@ +import style from "./Navbar.module.scss"; + +export default function Navbar() { + return ( +
+ +
+ ); +} diff --git a/src/components-layout/Navbar/Navbar.module.scss b/src/components-layout/Navbar/Navbar.module.scss new file mode 100644 index 0000000..0f81718 --- /dev/null +++ b/src/components-layout/Navbar/Navbar.module.scss @@ -0,0 +1,4 @@ +.wrapper { + nav { + } +} diff --git a/src/components-layout/Navbar/index.js b/src/components-layout/Navbar/index.js new file mode 100644 index 0000000..c8abe6d --- /dev/null +++ b/src/components-layout/Navbar/index.js @@ -0,0 +1,2 @@ +export * from "./Navbar"; +export { default } from "./Navbar"; diff --git a/src/components-shared/Preloader/Preloader.jsx b/src/components-shared/Preloader/Preloader.jsx new file mode 100644 index 0000000..417eac5 --- /dev/null +++ b/src/components-shared/Preloader/Preloader.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import style from "./Preloader.module.scss"; + +export default function Preloader() { + return
; +} diff --git a/src/components-shared/Preloader/Preloader.module.scss b/src/components-shared/Preloader/Preloader.module.scss new file mode 100644 index 0000000..ec81ef8 --- /dev/null +++ b/src/components-shared/Preloader/Preloader.module.scss @@ -0,0 +1,2 @@ +.wrapper { +} diff --git a/src/components-shared/Preloader/index.js b/src/components-shared/Preloader/index.js new file mode 100644 index 0000000..7f8f70d --- /dev/null +++ b/src/components-shared/Preloader/index.js @@ -0,0 +1,2 @@ +export * from "./Preloader"; +export { default } from "./Preloader"; diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx new file mode 100644 index 0000000..172c0ef --- /dev/null +++ b/src/components/Login/Login.jsx @@ -0,0 +1,7 @@ +import React from "react"; +import style from "./Login.module.scss"; +import clsx from "clsx"; + +export default function Login() { + return
Login Page
; +} diff --git a/src/components/Login/Login.module.scss b/src/components/Login/Login.module.scss new file mode 100644 index 0000000..ec81ef8 --- /dev/null +++ b/src/components/Login/Login.module.scss @@ -0,0 +1,2 @@ +.wrapper { +} diff --git a/src/components/Login/index.js b/src/components/Login/index.js new file mode 100644 index 0000000..fd8e77b --- /dev/null +++ b/src/components/Login/index.js @@ -0,0 +1,2 @@ +export * from "./Login"; +export { default } from "./Login"; diff --git a/src/config/config.js b/src/config/config.js new file mode 100644 index 0000000..acab5f6 --- /dev/null +++ b/src/config/config.js @@ -0,0 +1,14 @@ +const env = process.env.REACT_APP_ENV; + +const apiBaseUrlMap = { + dev: "dev-url", + prod: "prod-url", +}; + +const config = { + env, + apiBaseUrl: apiBaseUrlMap[env], + // ... Add all other configs here +}; + +export default config; diff --git a/src/contexts/AppContext.js b/src/contexts/AppContext.js new file mode 100644 index 0000000..6d6aeb1 --- /dev/null +++ b/src/contexts/AppContext.js @@ -0,0 +1,36 @@ +import { useReducer, createContext, useContext } from "react"; +export const AppContext = createContext(); + +const initialState = { + isLoading: true, +}; + +function reducer(state, action) { + const { type, payload } = action; + + switch (type) { + case "SET_LOADING": + return { ...state, isLoading: payload }; + default: + return state; + } +} + +export function AppContextProvider({ children }) { + const [state, dispatch] = useReducer(reducer, initialState); + + function setLoading(isLoading) { + dispatch({ type: "SET_LOADING", payload: isLoading }); + } + + const value = { + state, + setLoading, + }; + + return {children}; +} + +export function useAppContext() { + return useContext(AppContext); +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index d563c0f..9c9f9bb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; -const root = ReactDOM.createRoot(document.getElementById('root')); +import App from "./index/App"; +import reportWebVitals from "./reportWebVitals"; + +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( diff --git a/src/index/App.js b/src/index/App.js new file mode 100644 index 0000000..3e1efba --- /dev/null +++ b/src/index/App.js @@ -0,0 +1,58 @@ +import "./index.scss"; +import "react-toastify/dist/ReactToastify.min.css"; + +import { BrowserRouter } from "react-router-dom"; +import { ToastContainer } from "react-toastify"; +import { ErrorBoundary } from "react-error-boundary"; + +import AppRouter from "./AppRouter"; +import Navbar from "components-layout/Navbar"; + +import { AppContextProvider } from "contexts/AppContext"; + +function AppWrapper() { + return ( + <> + + + + + +
+ +
+
+ + ); +} + +function App() { + return ( + +
Fatal Error
+ + } + > + + + + + +
+ ); +} + +export default App; diff --git a/src/index/AppRouter.js b/src/index/AppRouter.js new file mode 100644 index 0000000..ecca2bf --- /dev/null +++ b/src/index/AppRouter.js @@ -0,0 +1,19 @@ +import { Suspense, lazy } from "react"; +import { Routes, Route, Navigate } from "react-router-dom"; +import { routes } from "./routes"; + +import Preloader from "components-shared/Preloader/Preloader"; + +const Login = lazy(() => import("components/Login")); + +export default function AppRouter() { + return ( + }> + + } /> + + } /> + + + ); +} diff --git a/src/index/index.scss b/src/index/index.scss new file mode 100644 index 0000000..ab5bcd0 --- /dev/null +++ b/src/index/index.scss @@ -0,0 +1,40 @@ +@import "~utils/vars"; + +body, +html { + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +* { + box-sizing: border-box; + font-family: "Manrope", sans-serif; +} + +img { + user-select: none !important; + --webkit-user-drag: none; +} + +a { + text-decoration: none; + color: #555; +} + +button { + border: none; + cursor: pointer; +} +h2 { + color: #616161; + font-weight: 500; + font-size: 1.5rem; +} + +.withPadding { + padding-inline: $inlinePadding; +} diff --git a/src/index/routes.js b/src/index/routes.js new file mode 100644 index 0000000..379caec --- /dev/null +++ b/src/index/routes.js @@ -0,0 +1,3 @@ +export const routes = { + Login: "/login", +}; diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/utils/consts.js b/src/utils/consts.js new file mode 100644 index 0000000..8e0331b --- /dev/null +++ b/src/utils/consts.js @@ -0,0 +1,12 @@ +import { AiOutlineLogout as LogOut } from "react-icons/ai"; +import { FiUser } from "react-icons/fi"; + +export const Roles = { + User: "user", + Admin: "admin", +}; + +export const NavLinks = [ + { name: "My Account", link: "/profile/student/account", icon: }, + { name: "Logout", link: "", icon: }, +]; diff --git a/src/utils/helpers.js b/src/utils/helpers.js new file mode 100644 index 0000000..8e0a28c --- /dev/null +++ b/src/utils/helpers.js @@ -0,0 +1,62 @@ +/* For small common helper utility functions */ + +export const titleCase = (str) => { + var splitStr = str.toLowerCase().split(" "); + for (var i = 0; i < splitStr.length; i++) { + splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1); + } + return splitStr.join(" "); +}; + +export const findIndexInObjArray = (myArray, searchTerm, property) => { + for (var i = 0, len = myArray.length; i < len; i++) { + if (myArray[i][property] === searchTerm) return i; + } + return -1; +}; + +export function objectToQueryString(obj) { + return Object.keys(obj) + .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) + .join("&"); +} + +export const getRandomNumber = (start, end) => { + return Math.floor(Math.random() * end) + start; +}; + +export function capitalizeFirstLetter(word) { + if (typeof word !== "string") return ""; + return word.charAt(0).toUpperCase() + word.slice(1); +} + +export function sortArrOfObjects(arr, param, type) { + if (type === "desc") + return arr.sort((a, b) => (a[param] > b[param] ? -1 : b[param] > a[param] ? 1 : 0)); + else if (type === "asc") + return arr.sort((a, b) => (a[param] > b[param] ? 1 : b[param] > a[param] ? -1 : 0)); +} + +export function arrayRandomShuffle(arr) { + return arr + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); +} + +export function splitArrChunks(arr, noOfChunks) { + if (!arr || !noOfChunks) throw new Error("Params missing"); + + return arr.reduce((accum, curr, index, array) => { + if (index % noOfChunks === 0) accum.push(array.slice(index, index + noOfChunks)); + return accum; + }, []); +} + +export function convertArrToObj(arr, key) { + const obj = {}; + arr.forEach((item) => { + obj[item[key]] = item; + }); + return obj; +} diff --git a/src/utils/vars.scss b/src/utils/vars.scss new file mode 100644 index 0000000..6e3d2d7 --- /dev/null +++ b/src/utils/vars.scss @@ -0,0 +1,5 @@ +$inlinePadding: 4em; +$navbarHeight: 3.8em; +$primaryColor: #4280e3; + +/* You can add scss mixins here & other common variables*/