diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..90f072a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,38 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:jest/recommended" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false, + "babelOptions": { + "presets": ["@babel/preset-react"] + }, + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + }, + "sourceType": "module" + }, + "plugins": ["react", "react-hooks", "jest"], + "ignorePatterns": ["**/public"], + "rules": { + "indent": 0, + "linebreak-style": 0, + "quotes": 0, + "semi": 0, + "no-console": 0, + "no-debugger": "warn", + "react/display-name": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "no-unused-vars": [1, { "args": "after-used", "argsIgnorePattern": "^_" }] + } +} diff --git a/package-lock.json b/package-lock.json index 17c4552..51203e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,8 @@ "@adobe/css-tools": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", - "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==" + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true }, "@ampproject/remapping": { "version": "2.2.0", @@ -72,6 +73,25 @@ } } }, + "@babel/eslint-parser": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", + "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", + "dev": true, + "requires": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@babel/generator": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", @@ -1327,6 +1347,11 @@ } } }, + "@flare/js-utils": { + "version": "1.0.3", + "resolved": "https://lab.code-rove.com/public-node-registry/@flare/js-utils/-/js-utils-1.0.3.tgz", + "integrity": "sha512-VgXQHoQEVZ/71B6YQHQP8/Yd/w1smGD+kCCiNvJKZ1xMD3nkN9mjoHxIqbOJMZ2q5PZlV6gXYT7eVol8Wm+D0A==" + }, "@flare/tuitio-client": { "version": "1.0.4", "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.0.4.tgz", @@ -1336,15 +1361,10 @@ "axios": "^1.3.2" }, "dependencies": { - "@flare/js-utils": { - "version": "1.0.3", - "resolved": "https://lab.code-rove.com/public-node-registry/@flare/js-utils/-/js-utils-1.0.3.tgz", - "integrity": "sha512-VgXQHoQEVZ/71B6YQHQP8/Yd/w1smGD+kCCiNvJKZ1xMD3nkN9mjoHxIqbOJMZ2q5PZlV6gXYT7eVol8Wm+D0A==" - }, "axios": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz", - "integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -1354,9 +1374,9 @@ } }, "@flare/tuitio-client-react": { - "version": "1.0.0", - "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client-react/-/tuitio-client-react-1.0.0.tgz", - "integrity": "sha512-iIsyteZaCLXQCAFAnifYq6Stw7Jg72id7Ky/b3hB8ZGaNkrTDKDsmLTW7a2bHWljcFbCbYuRXrmHgpt8YJ6Hzg==", + "version": "1.0.1", + "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client-react/-/tuitio-client-react-1.0.1.tgz", + "integrity": "sha512-WA+1UzfBgZ4gGdcT0AT6lQ9zOQ3mxc5UxQvxoM1SBR5i5tbIuQQCY6M8dudm1l02fNfwNQqJIcrtuOTTXZqMOA==", "requires": { "@flare/tuitio-client": "^1.0.4" } @@ -1695,6 +1715,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", + "dev": true, "requires": { "jest-get-type": "^29.2.0" } @@ -2012,6 +2033,7 @@ "version": "29.0.0", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "dev": true, "requires": { "@sinclair/typebox": "^0.24.1" } @@ -2162,6 +2184,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", + "dev": true, "requires": { "@jest/schemas": "^29.0.0", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2175,6 +2198,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2309,6 +2333,15 @@ "@types/whatwg-streams": "^0.0.7" } }, + "@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "requires": { + "eslint-scope": "5.1.1" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2441,7 +2474,8 @@ "@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true }, "@sinonjs/commons": { "version": "1.8.6", @@ -2581,6 +2615,7 @@ "version": "7.31.2", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", + "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2596,6 +2631,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -2608,6 +2644,7 @@ "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -2616,6 +2653,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, "requires": { "@babel/runtime": "^7.10.2", "@babel/runtime-corejs3": "^7.10.2" @@ -2625,6 +2663,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2634,6 +2673,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -2647,6 +2687,7 @@ "version": "5.16.5", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, "requires": { "@adobe/css-tools": "^4.0.1", "@babel/runtime": "^7.9.2", @@ -2663,6 +2704,7 @@ "version": "11.2.7", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^7.28.1" @@ -2672,6 +2714,7 @@ "version": "12.8.3", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz", "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==", + "dev": true, "requires": { "@babel/runtime": "^7.12.5" } @@ -2684,7 +2727,8 @@ "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==" + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true }, "@types/babel__core": { "version": "7.1.20", @@ -2784,6 +2828,7 @@ "version": "29.2.4", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz", "integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==", + "dev": true, "requires": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -2891,6 +2936,7 @@ "version": "5.14.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, "requires": { "@types/jest": "*" } @@ -2956,6 +3002,7 @@ "version": "17.0.18", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.18.tgz", "integrity": "sha512-eIJR1UER6ur3EpKM3d+2Pgd+ET+k6Kn9B4ZItX0oPjjVI5PrfaRjKyLT5UYendDpLuoiJMNJvovLQbEXqhsPaw==", + "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -3413,6 +3460,7 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, "requires": { "deep-equal": "^2.0.5" } @@ -3639,7 +3687,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "axe-core": { "version": "4.6.1", @@ -3647,12 +3696,13 @@ "integrity": "sha512-lCZN5XRuOnpG4bpMq8v0khrWtUOn+i8lZSb6wHZH56ZfbIEv6XwJV84AAueh9/zi7qPVJ/E4yz6fmsiyOmXR4w==" }, "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "axobject-query": { @@ -4482,6 +4532,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4526,7 +4577,8 @@ "ci-info": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", - "integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==" + "integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==", + "dev": true }, "cipher-base": { "version": "1.0.4", @@ -5169,7 +5221,8 @@ "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true }, "cssdb": { "version": "4.4.0", @@ -5428,6 +5481,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz", "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "es-get-iterator": "^1.1.2", @@ -5628,7 +5682,8 @@ "diff-sequences": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==" + "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", @@ -5688,7 +5743,8 @@ "dom-accessibility-api": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==" + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true }, "dom-converter": { "version": "0.2.0", @@ -5992,6 +6048,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.0", @@ -6780,6 +6837,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", + "dev": true, "requires": { "@jest/expect-utils": "^29.3.1", "jest-get-type": "^29.2.0", @@ -7143,6 +7201,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -8451,7 +8510,8 @@ "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true }, "is-module": { "version": "1.0.0", @@ -8547,7 +8607,8 @@ "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true }, "is-shared-array-buffer": { "version": "1.0.2", @@ -8582,6 +8643,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -8598,7 +8660,8 @@ "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true }, "is-weakref": { "version": "1.0.2", @@ -8612,6 +8675,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -8633,7 +8697,8 @@ "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "isexe": { "version": "2.0.0", @@ -9156,6 +9221,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^29.3.1", @@ -9167,6 +9233,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -9372,7 +9439,8 @@ "jest-get-type": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==" + "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", + "dev": true }, "jest-haste-map": { "version": "26.6.2", @@ -9640,6 +9708,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", + "dev": true, "requires": { "chalk": "^4.0.0", "jest-diff": "^29.3.1", @@ -9651,6 +9720,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -9662,6 +9732,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", + "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.3.1", @@ -9678,6 +9749,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10240,6 +10312,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", + "dev": true, "requires": { "@jest/types": "^29.3.1", "@types/node": "*", @@ -10253,6 +10326,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10809,7 +10883,8 @@ "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "dev": true }, "magic-string": { "version": "0.25.9", @@ -10961,7 +11036,8 @@ "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true }, "mini-css-extract-plugin": { "version": "0.11.3", @@ -12977,6 +13053,12 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -12995,6 +13077,7 @@ "version": "29.3.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", + "dev": true, "requires": { "@jest/schemas": "^29.0.0", "ansi-styles": "^5.0.0", @@ -13004,12 +13087,14 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, @@ -13740,6 +13825,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, "requires": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -15282,6 +15368,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, "requires": { "min-indent": "^1.0.0" } @@ -17325,6 +17412,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, "requires": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -17341,6 +17429,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", diff --git a/package.json b/package.json index da674e5..a49039d 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,11 @@ }, "private": true, "dependencies": { - "@flare/tuitio-client-react": "^1.0.0", + "@flare/js-utils": "^1.0.3", + "@flare/tuitio-client-react": "^1.0.1", "@material-ui/core": "^4.11.2", "@material-ui/icons": "^4.11.2", - "@testing-library/jest-dom": "^5.11.6", - "@testing-library/react": "^11.2.2", - "@testing-library/user-event": "^12.5.0", - "axios": "^0.27.2", + "axios": "^1.3.4", "i18next": "^19.4.4", "i18next-browser-languagedetector": "^4.1.1", "i18next-http-backend": "^1.4.0", @@ -33,6 +31,13 @@ "react-scripts": "4.0.1", "react-toastify": "^6.2.0" }, + "devDependencies": { + "@babel/eslint-parser": "^7.16.5", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "prettier": "^2.5.1" + }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/private/Notes.txt b/private/Notes.txt index 29e5673..d291f00 100644 --- a/private/Notes.txt +++ b/private/Notes.txt @@ -1,3 +1,6 @@ REACT v4: https://v4.mui.com/getting-started/installation/ -https://v4.mui.com/components/material-icons/ \ No newline at end of file +https://v4.mui.com/components/material-icons/ + +Theming: +https://v4.mui.com/customization/palette/ (Dark theme) \ No newline at end of file diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 6e31cfb..0822ee5 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -12,9 +12,10 @@ "English": "English", "Romanian": "Romanian" }, - "Steps": { - "Login": "Login", - "Network": "Network", + "Menu": { + "Dashboard": "Dashboard", + "Machines": "Machines", + "Trash": "Trash", "Settings": "Settings" }, "Login": { diff --git a/public/locales/ro/translations.json b/public/locales/ro/translations.json index 8b25ec5..51c3bd0 100644 --- a/public/locales/ro/translations.json +++ b/public/locales/ro/translations.json @@ -3,9 +3,10 @@ "English": "Engleză", "Romanian": "Română" }, - "Steps": { - "Login": "Autentificare", - "Network": "Rețea", + "Menu": { + "Dashboard": "Bord", + "Machines": "Mașini", + "Trash": "Gunoi", "Settings": "Setări" }, "Login": { diff --git a/src/components/App.js b/src/components/App.js index 6b1ffa8..dad60bc 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,45 +1,72 @@ -import React, { useReducer, useMemo } from "react"; -import Main from "./layout/Main"; -import { initialState } from "../state/initialState"; -import { - reducer, - dispatchActions as reducerDispatchActions -} from "../state/reducer"; -import { - ApplicationStateContext, - ApplicationDispatchContext -} from "../state/ApplicationContexts"; -import { ToastContainer, Slide } from "react-toastify"; -import "react-toastify/dist/ReactToastify.css"; +import React from "react"; +import PropTypes from "prop-types"; +import AppLayout from "./layout/AppLayout"; +import { BrowserRouter, Switch, Redirect, Route } from "react-router-dom"; +import { useTuitioToken } from "@flare/tuitio-client-react"; +import LoginContainer from "../features/login/components/LoginContainer"; -const App = () => { - const [state, dispatch] = useReducer(reducer, initialState); - const dispatchActions = useMemo( - () => reducerDispatchActions(dispatch), - [dispatch] - ); +const PrivateRoute = ({ component, ...rest }) => { + const { valid } = useTuitioToken(); return ( - <> - - -
- - - - + + valid ? ( + React.createElement(component, props) + ) : ( + + ) + } + /> + ); +}; + +PrivateRoute.propTypes = { + component: PropTypes.func.isRequired, + location: PropTypes.object +}; + +const PublicRoute = ({ component, ...rest }) => { + const { valid } = useTuitioToken(); + return ( + + valid ? ( + + ) : ( + React.createElement(component, props) + ) + } + /> + ); +}; + +PublicRoute.propTypes = { + component: PropTypes.func.isRequired +}; + +const App = () => { + return ( + + + } /> + + + + ); }; diff --git a/src/components/common/PageTitle.js b/src/components/common/PageTitle.js new file mode 100644 index 0000000..474242e --- /dev/null +++ b/src/components/common/PageTitle.js @@ -0,0 +1,32 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { makeStyles } from "@material-ui/core/styles"; +import { Typography } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + box: { + display: "flex", + justifyContent: "space-between", + marginBottom: theme.spacing(2), + marginTop: theme.spacing(0) + }, + title: { textTransform: "uppercase" } +})); + +const PageTitle = ({ text }) => { + const classes = useStyles(); + + return ( +
+ + {text} + +
+ ); +}; + +PageTitle.propTypes = { + text: PropTypes.string.isRequired +}; + +export default PageTitle; diff --git a/src/components/common/inputs/PasswordField.js b/src/components/common/inputs/PasswordField.js index 5ad0449..e94c1b3 100644 --- a/src/components/common/inputs/PasswordField.js +++ b/src/components/common/inputs/PasswordField.js @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import PropTypes from "prop-types"; import { InputAdornment, TextField, @@ -53,4 +54,8 @@ const PasswordField = ({ label, ...rest }) => { ); }; +PasswordField.propTypes = { + label: PropTypes.string +}; + export default PasswordField; diff --git a/src/components/layout/stepper/ApplicationStepper.js b/src/components/common/stepper/ApplicationStepper.js similarity index 97% rename from src/components/layout/stepper/ApplicationStepper.js rename to src/components/common/stepper/ApplicationStepper.js index a1cb099..0faf8e7 100644 --- a/src/components/layout/stepper/ApplicationStepper.js +++ b/src/components/common/stepper/ApplicationStepper.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import steps from "../../../constants/steps"; +import steps from "./steps"; import { Card, Stepper, diff --git a/src/components/layout/stepper/CustomStepConnector.js b/src/components/common/stepper/CustomStepConnector.js similarity index 100% rename from src/components/layout/stepper/CustomStepConnector.js rename to src/components/common/stepper/CustomStepConnector.js diff --git a/src/components/layout/stepper/StepIcon.js b/src/components/common/stepper/StepIcon.js similarity index 96% rename from src/components/layout/stepper/StepIcon.js rename to src/components/common/stepper/StepIcon.js index b3c461b..01628e8 100644 --- a/src/components/layout/stepper/StepIcon.js +++ b/src/components/common/stepper/StepIcon.js @@ -2,7 +2,7 @@ import React from "react"; import PropTypes from "prop-types"; import clsx from "clsx"; import { makeStyles } from "@material-ui/core/styles"; -import steps from "../../../constants/steps"; +import steps from "./steps"; const useStepIconStyles = makeStyles({ root: { diff --git a/src/constants/steps.js b/src/components/common/stepper/steps.js similarity index 94% rename from src/constants/steps.js rename to src/components/common/stepper/steps.js index 7cc158e..f38d65f 100644 --- a/src/constants/steps.js +++ b/src/components/common/stepper/steps.js @@ -11,7 +11,7 @@ const steps = [ { id: 1, title: "Steps.Network", - route: "/network", + route: "/machines", disabled: false, icon: }, diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js new file mode 100644 index 0000000..70092b3 --- /dev/null +++ b/src/components/layout/AppLayout.js @@ -0,0 +1,34 @@ +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import AppRoutes from "./AppRoutes"; +import TopBar from "./TopBar"; +import Sidebar from "./Sidebar"; +import styles from "./styles"; + +const useStyles = makeStyles(styles); + +const AppLayout = () => { + const [open, setOpen] = useState(false); + const classes = useStyles(); + + const handleDrawerOpen = () => { + setOpen(true); + }; + + const handleDrawerClose = () => { + setOpen(false); + }; + + return ( +
+ + +
+
+ +
+
+ ); +}; + +export default AppLayout; diff --git a/src/components/layout/Switcher.js b/src/components/layout/AppRoutes.js similarity index 59% rename from src/components/layout/Switcher.js rename to src/components/layout/AppRoutes.js index 9cb45d5..69e207f 100644 --- a/src/components/layout/Switcher.js +++ b/src/components/layout/AppRoutes.js @@ -4,16 +4,18 @@ import PageNotFound from "./PageNotFound"; import LoginContainer from "../../features/login/components/LoginContainer"; import NetworkContainer from "../../features/network/components/NetworkContainer"; import SettingsContainer from "../../features/settings/components/SettingsContainer"; +import DashboardContainer from "../../features/dashboard/components/DashboardContainer"; -const Switcher = () => { +const AppRoutes = () => { return ( - - + + + ); }; -export default Switcher; +export default AppRoutes; diff --git a/src/components/layout/LightDarkToggle.js b/src/components/layout/LightDarkToggle.js new file mode 100644 index 0000000..cc42262 --- /dev/null +++ b/src/components/layout/LightDarkToggle.js @@ -0,0 +1,25 @@ +import React from "react"; +import { IconButton } from "@material-ui/core"; +import { + Brightness2 as MoonIcon, + WbSunny as SunIcon +} from "@material-ui/icons"; +import { useApplicationTheme } from "../../providers/ThemeProvider"; + +const LightDarkToggle = () => { + const { isDark, onDarkModeChanged } = useApplicationTheme(); + + const handleChange = () => onDarkModeChanged(!isDark); + + return ( + + {isDark ? : } + + ); +}; + +export default LightDarkToggle; diff --git a/src/components/layout/Main.js b/src/components/layout/Main.js deleted file mode 100644 index db0702a..0000000 --- a/src/components/layout/Main.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import ApplicationStepper from "./stepper/ApplicationStepper"; -import Switcher from "./Switcher"; -import { makeStyles } from "@material-ui/core/styles"; -import { useTuitioToken } from "@flare/tuitio-client-react"; -import LoginContainer from "../../features/login/components/LoginContainer"; - -const useStyles = makeStyles(() => ({ - app: { - backgroundColor: "#282c34", - minHeight: "100vh", - display: "flex", - flexDirection: "column" - } -})); - -const Main = () => { - const classes = useStyles(); - const { validate: validateToken } = useTuitioToken(); - const tokenIsValid = validateToken(); - - return ( -
- {tokenIsValid ? ( - <> - - - - ) : ( - - )} -
- ); -}; - -export default Main; diff --git a/src/components/layout/ProfileButton.js b/src/components/layout/ProfileButton.js new file mode 100644 index 0000000..fbf6784 --- /dev/null +++ b/src/components/layout/ProfileButton.js @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { IconButton, Menu, MenuItem } from "@material-ui/core"; +import AccountCircle from "@material-ui/icons/AccountCircle"; +import { useHistory } from "react-router-dom"; +import { useTuitioClient } from "@flare/tuitio-client-react"; + +const ProfileButton = () => { + const history = useHistory(); + const { logout } = useTuitioClient(); + + const [anchorEl, setAnchorEl] = useState(null); + const openUserMenu = Boolean(anchorEl); + + const handleMenu = event => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( +
+ + + + + { + history.push("/user-profile"); + handleClose(); + }} + > + Profile + + logout()}>Logout + +
+ ); +}; + +export default ProfileButton; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js new file mode 100644 index 0000000..4c2aea1 --- /dev/null +++ b/src/components/layout/Sidebar.js @@ -0,0 +1,106 @@ +import React from "react"; +import PropTypes from "prop-types"; +import clsx from "clsx"; +import { makeStyles, useTheme } from "@material-ui/core/styles"; +import { + Drawer, + List, + Divider, + IconButton, + ListItemIcon, + ListItemText +} from "@material-ui/core"; +import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; +import ChevronRightIcon from "@material-ui/icons/ChevronRight"; +import ListItem from "@material-ui/core/ListItem"; +import DeleteIcon from "@material-ui/icons/Delete"; +import DnsIcon from "@material-ui/icons/Dns"; +import SettingsIcon from "@material-ui/icons/Settings"; +import DashboardIcon from "@material-ui/icons/Dashboard"; +import { useHistory } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import styles from "./styles"; + +const useStyles = makeStyles(styles); + +const Sidebar = ({ open, handleDrawerClose }) => { + const classes = useStyles(); + const theme = useTheme(); + const history = useHistory(); + const { t } = useTranslation(); + + return ( + +
+ + {theme.direction === "rtl" ? ( + + ) : ( + + )} + +
+ + + history.push("/dashboard")} + > + + + + + + history.push("/machines")} + > + + + + + + + + + history.push("/trash")}> + + + + + + history.push("/settings")} + > + + + + + + +
+ ); +}; + +Sidebar.propTypes = { + open: PropTypes.bool.isRequired, + handleDrawerClose: PropTypes.func.isRequired +}; + +export default Sidebar; diff --git a/src/components/layout/TopBar.js b/src/components/layout/TopBar.js new file mode 100644 index 0000000..026f3a4 --- /dev/null +++ b/src/components/layout/TopBar.js @@ -0,0 +1,50 @@ +import React from "react"; +import PropTypes from "prop-types"; +import clsx from "clsx"; +import { makeStyles } from "@material-ui/core/styles"; +import { AppBar, Toolbar, Typography, IconButton } from "@material-ui/core"; +import MenuIcon from "@material-ui/icons/Menu"; +import ProfileButton from "./ProfileButton"; +import LightDarkToggle from "./LightDarkToggle"; +import styles from "./styles"; + +const useStyles = makeStyles(styles); + +const TopBar = ({ open, handleDrawerOpen }) => { + const classes = useStyles(); + + return ( + + + + + + + Network resurrector + + + + + + ); +}; + +TopBar.propTypes = { + open: PropTypes.bool.isRequired, + handleDrawerOpen: PropTypes.func.isRequired +}; + +export default TopBar; diff --git a/src/components/layout/styles.js b/src/components/layout/styles.js new file mode 100644 index 0000000..0c06cdf --- /dev/null +++ b/src/components/layout/styles.js @@ -0,0 +1,68 @@ +const drawerWidth = 240; + +const styles = theme => ({ + root: { + display: "flex" + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }) + }, + appBarShift: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen + }) + }, + menuButton: { + marginRight: 36 + }, + hide: { + display: "none" + }, + drawer: { + width: drawerWidth, + flexShrink: 0, + whiteSpace: "nowrap" + }, + drawerOpen: { + width: drawerWidth, + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen + }) + }, + drawerClose: { + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }), + overflowX: "hidden", + width: theme.spacing(7) + 1, + [theme.breakpoints.up("sm")]: { + width: theme.spacing(9) + 1 + } + }, + toolbar: { + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar + }, + title: { + flexGrow: 1 + }, + content: { + flexGrow: 1, + padding: theme.spacing(2) + } +}); + +export default styles; diff --git a/src/features/dashboard/components/DashboardContainer.js b/src/features/dashboard/components/DashboardContainer.js new file mode 100644 index 0000000..88c8ee0 --- /dev/null +++ b/src/features/dashboard/components/DashboardContainer.js @@ -0,0 +1,5 @@ +import React from "react"; + +const DashboardContainer = () =>

In development...

; + +export default DashboardContainer; diff --git a/src/features/login/components/LoginContainer.js b/src/features/login/components/LoginContainer.js index 5909658..d14e5c7 100644 --- a/src/features/login/components/LoginContainer.js +++ b/src/features/login/components/LoginContainer.js @@ -14,7 +14,7 @@ const LoginContainer = () => { const { error } = useToast(); const { t } = useTranslation(); const { login, logout } = useTuitioClient({ - onLoginFailed: response => error(t("Login.IncorrectCredentials")), + onLoginFailed: () => error(t("Login.IncorrectCredentials")), onLoginError: err => error(err.message) }); const { valid: tokenIsValid } = useTuitioToken(); diff --git a/src/features/machines/components/Machine.js b/src/features/machines/components/Machine.js index 946adac..629c8a1 100644 --- a/src/features/machines/components/Machine.js +++ b/src/features/machines/components/Machine.js @@ -41,6 +41,18 @@ const ActionButton = React.forwardRef((props, _ref) => { ); }); +ActionButton.propTypes = { + machine: PropTypes.shape({ + machineId: PropTypes.number.isRequired + }).isRequired, + action: PropTypes.shape({ + code: PropTypes.string.isRequired, + tooltip: PropTypes.string.isRequired, + system: PropTypes.bool, + effect: PropTypes.func.isRequired + }).isRequired +}; + const Machine = ({ machine, actions, diff --git a/src/features/machines/components/MachinesContainer.js b/src/features/machines/components/MachinesContainer.js index 7c59cbf..20cf0d6 100644 --- a/src/features/machines/components/MachinesContainer.js +++ b/src/features/machines/components/MachinesContainer.js @@ -2,9 +2,10 @@ import React, { useContext, useEffect, useCallback } from "react"; import { ApplicationStateContext, ApplicationDispatchContext -} from "../../../state/ApplicationContexts"; +} from "../../../state/contexts"; import useApi from "../../../api"; import MachinesList from "./MachinesList"; +import PageTitle from "../../../components/common/PageTitle"; const MachinesContainer = () => { const state = useContext(ApplicationStateContext); @@ -27,7 +28,12 @@ const MachinesContainer = () => { } }, [handleReadMachines, state.network.machines.loaded]); - return ; + return ( + <> + + + + ); }; export default MachinesContainer; diff --git a/src/features/settings/components/SettingsContainer.js b/src/features/settings/components/SettingsContainer.js index 8eebf7e..bace773 100644 --- a/src/features/settings/components/SettingsContainer.js +++ b/src/features/settings/components/SettingsContainer.js @@ -1,8 +1,14 @@ import React from "react"; import LanguageContainer from "./language/LanguageContainer"; +import ThemeSettings from "./ThemeSettings"; const SettingsContainer = () => { - return ; + return ( + <> + + + + ); }; export default SettingsContainer; diff --git a/src/features/settings/components/ThemeSettings.js b/src/features/settings/components/ThemeSettings.js new file mode 100644 index 0000000..cf522d3 --- /dev/null +++ b/src/features/settings/components/ThemeSettings.js @@ -0,0 +1,23 @@ +import React from "react"; +import { useApplicationTheme } from "../../../providers/ThemeProvider"; +import { Switch } from "@material-ui/core"; + +const ThemeSettings = () => { + const { isDark, onDarkModeChanged } = useApplicationTheme(); + + const handleChange = event => { + const { checked } = event.target; + onDarkModeChanged(checked); + }; + + return ( + + ); +}; + +export default ThemeSettings; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 4a1df4d..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 b9d2971..2dc491a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,25 @@ import React, { Suspense } from "react"; import ReactDOM from "react-dom"; -import "./index.css"; -import "./utils/i18n"; +import ThemeProvider from "./providers/ThemeProvider"; +import CssBaseline from "@material-ui/core/CssBaseline"; import App from "./components/App"; -import { BrowserRouter as Router } from "react-router-dom"; import { TuitioProvider } from "@flare/tuitio-client-react"; +import ApplicationStateProvider from "./providers/ApplicationStateProvider"; +import ToastProvider from "./providers/ToastProvider"; +import "./utils/i18n"; ReactDOM.render( - - Loading...}> - - - - - , + + + + + Loading...}> + + + + + + + , document.getElementById("root") ); diff --git a/src/providers/ApplicationStateProvider.js b/src/providers/ApplicationStateProvider.js new file mode 100644 index 0000000..071541f --- /dev/null +++ b/src/providers/ApplicationStateProvider.js @@ -0,0 +1,33 @@ +import React, { useReducer, useMemo } from "react"; +import PropTypes from "prop-types"; +import { + ApplicationStateContext, + ApplicationDispatchContext +} from "../state/contexts"; +import { + reducer, + dispatchActions as reducerDispatchActions +} from "../state/reducer"; +import { initialState } from "../state/initialState"; + +const ApplicationStateProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + const dispatchActions = useMemo( + () => reducerDispatchActions(dispatch), + [dispatch] + ); + + return ( + + + {children} + + + ); +}; + +ApplicationStateProvider.propTypes = { + children: PropTypes.node.isRequired +}; + +export default ApplicationStateProvider; diff --git a/src/providers/ThemeProvider.js b/src/providers/ThemeProvider.js new file mode 100644 index 0000000..bcbef66 --- /dev/null +++ b/src/providers/ThemeProvider.js @@ -0,0 +1,82 @@ +import React, { useReducer, useMemo, useContext } from "react"; +import PropTypes from "prop-types"; +import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles"; +import { localStorage } from "@flare/js-utils"; +import { getThemes } from "../themes"; + +const ApplicationThemeContext = React.createContext(); +const LOCAL_STORAGE_COLOR_SCHEME_KEY = "network-resurrector-color-scheme"; + +const COLOR_SCHEME = { + LIGHT: "light", + DARK: "dark" +}; + +const colorScheme = localStorage.getItem(LOCAL_STORAGE_COLOR_SCHEME_KEY); +const prefersDarkMode = window.matchMedia( + "(prefers-color-scheme: dark)" +).matches; + +const initialState = { + scheme: + colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT) +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case "onColorSchemeChanged": { + return { + ...state, + scheme: action.scheme + }; + } + default: { + return state; + } + } +}; + +const dispatchActions = dispatch => ({ + onColorSchemeChanged: scheme => { + dispatch({ type: "onColorSchemeChanged", scheme }); + localStorage.setItem(LOCAL_STORAGE_COLOR_SCHEME_KEY, scheme); + } +}); + +const useApplicationTheme = () => { + const { state, actions } = useContext(ApplicationThemeContext); + const { onColorSchemeChanged } = actions; + + const { scheme } = state; + const onDarkModeChanged = active => + onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT); + + return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged }; +}; + +const ThemeProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + const actions = useMemo(() => dispatchActions(dispatch), [dispatch]); + const themes = useMemo( + () => getThemes(state.scheme === COLOR_SCHEME.DARK), + [state.scheme] + ); + + return ( + + {children} + + ); +}; + +ThemeProvider.propTypes = { + children: PropTypes.node.isRequired +}; + +export { ThemeProvider, ApplicationThemeContext, useApplicationTheme }; +export default ThemeProvider; diff --git a/src/providers/ToastProvider.js b/src/providers/ToastProvider.js new file mode 100644 index 0000000..9213665 --- /dev/null +++ b/src/providers/ToastProvider.js @@ -0,0 +1,31 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { ToastContainer, Slide } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; + +const ToastProvider = ({ children }) => { + return ( + <> + {children} + + + ); +}; + +ToastProvider.propTypes = { + children: PropTypes.node.isRequired +}; + +export default ToastProvider; diff --git a/src/state/ApplicationContexts.js b/src/state/contexts.js similarity index 100% rename from src/state/ApplicationContexts.js rename to src/state/contexts.js diff --git a/src/themes/default.js b/src/themes/default.js new file mode 100644 index 0000000..eb6dad5 --- /dev/null +++ b/src/themes/default.js @@ -0,0 +1,35 @@ +const primary = "#00695C"; +const secondary = "#FF5C93"; +const warning = "#ff9800"; +const success = "#4caf50"; +const info = "#2196f3"; + +const defaultTheme = { + palette: { + primary: { + main: primary + }, + secondary: { + main: secondary, + contrastText: "#ffcc00" + }, + warning: { + main: warning + }, + success: { + main: success + }, + info: { + main: info + } + }, + overrides: { + MuiBackdrop: { + root: { + backgroundColor: "#4A4A4A1A" + } + } + } +}; + +export default defaultTheme; diff --git a/src/themes/index.js b/src/themes/index.js new file mode 100644 index 0000000..07216c2 --- /dev/null +++ b/src/themes/index.js @@ -0,0 +1,39 @@ +import defaultTheme from "./default"; +import { createTheme } from "@material-ui/core/styles"; + +const overrides = { + typography: { + h1: { + fontSize: "3rem" + }, + h2: { + fontSize: "2rem" + }, + h3: { + fontSize: "1.64rem" + }, + h4: { + fontSize: "1.5rem" + }, + h5: { + fontSize: "1.285rem" + }, + h6: { + fontSize: "1.142rem" + } + } +}; + +const getThemes = darkMode => { + const type = darkMode ? "dark" : "light"; + + return { + default: createTheme({ + ...defaultTheme, + ...overrides, + palette: { ...defaultTheme.palette, type } + }) + }; +}; + +export { getThemes };