diff --git a/package-lock.json b/package-lock.json index 7ee0760..f465d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,15 @@ "name": "wf-fe-task", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.6", + "@mui/material": "^6.1.6", + "@reduxjs/toolkit": "^2.3.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-redux": "^9.1.2", + "redux": "^5.0.1" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -42,7 +49,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -95,7 +101,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dev": true, "dependencies": { "@babel/parser": "^7.26.2", "@babel/types": "^7.26.0", @@ -127,7 +132,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -166,7 +170,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -175,7 +178,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -206,7 +208,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "dev": true, "dependencies": { "@babel/types": "^7.26.0" }, @@ -247,11 +248,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", @@ -265,7 +276,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/generator": "^7.25.9", @@ -283,7 +293,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -292,7 +301,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -301,6 +309,144 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -861,7 +1007,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -875,7 +1020,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -884,7 +1028,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -892,19 +1035,239 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz", + "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz", + "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.1.6", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", + "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.6", + "@mui/system": "^6.1.6", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.6", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.6", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz", + "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz", + "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz", + "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.6", + "@mui/styled-engine": "^6.1.6", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.6", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz", + "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -940,6 +1303,38 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", + "integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz", @@ -1227,17 +1622,20 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dev": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1252,6 +1650,19 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz", @@ -1559,6 +1970,20 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1623,7 +2048,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1664,6 +2088,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1694,6 +2126,21 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -1711,14 +2158,12 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1737,12 +2182,29 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.55", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", "integrity": "sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1794,7 +2256,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -2049,6 +2510,11 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2098,6 +2564,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2146,6 +2620,30 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2155,11 +2653,19 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2180,6 +2686,25 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2237,7 +2762,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -2251,6 +2775,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2297,6 +2826,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2375,8 +2909,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -2408,6 +2941,14 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2459,7 +3000,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2467,6 +3007,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2485,11 +3042,23 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2540,6 +3109,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2592,6 +3176,33 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -2601,11 +3212,64 @@ "node": ">=0.10.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -2718,6 +3382,14 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2739,6 +3411,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2751,6 +3428,17 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2868,6 +3556,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.4.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", @@ -2957,6 +3653,14 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 0e4268b..916a448 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.6", + "@mui/material": "^6.1.6", + "@reduxjs/toolkit": "^2.3.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-redux": "^9.1.2", + "redux": "^5.0.1" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.styles.tsx b/src/App.styles.tsx new file mode 100644 index 0000000..1a0d101 --- /dev/null +++ b/src/App.styles.tsx @@ -0,0 +1,7 @@ +import { Box, styled } from "@mui/material"; + +export const AppWrapper = styled(Box)(({ theme }) => ({ + maxWidth: theme.breakpoints.values.sm, + margin: "auto", + padding: theme.spacing(5), +})); diff --git a/src/App.tsx b/src/App.tsx index 3d7ded3..ef3f56c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,16 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import { Provider } from "react-redux"; +import { AppWrapper } from "./App.styles"; +import store from "./redux/store"; +import { Home } from "./pages"; function App() { - const [count, setCount] = useState(0) - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) + + + + + + ); } -export default App +export default App; diff --git a/src/components/Wizard/Wizard.styles.tsx b/src/components/Wizard/Wizard.styles.tsx new file mode 100644 index 0000000..47ad3a0 --- /dev/null +++ b/src/components/Wizard/Wizard.styles.tsx @@ -0,0 +1,7 @@ +import { Box, styled } from "@mui/material"; + +export const ContentWrapper = styled(Box)(({ theme }) => ({ + minHeight: "300px", + margin: "auto", + paddingTop: theme.spacing(7), +})); diff --git a/src/components/Wizard/Wizard.tsx b/src/components/Wizard/Wizard.tsx new file mode 100644 index 0000000..106da09 --- /dev/null +++ b/src/components/Wizard/Wizard.tsx @@ -0,0 +1,34 @@ +import { useState } from "react"; +import { WizardProps } from "./Wizard.types"; +import { WizardHeader } from "./components"; +import { WizardButtons } from "./components/WizardButtons"; +import { ContentWrapper } from "./Wizard.styles"; + +export const Wizard: React.FC = ({ steps, onSubmit }) => { + const [step, setStep] = useState(0); + const { + content, + canProceedFurther, + canProceedBack = true, + isLoading, + } = steps[step]; + const headerSteps: string[] = steps.map(({ label }) => { + return label; + }); + + return ( +
+ + {content} + +
+ ); +}; diff --git a/src/components/Wizard/Wizard.types.ts b/src/components/Wizard/Wizard.types.ts new file mode 100644 index 0000000..9960601 --- /dev/null +++ b/src/components/Wizard/Wizard.types.ts @@ -0,0 +1,12 @@ +export type Step = { + label: string; + content: React.ReactNode; + canProceedFurther: boolean; + canProceedBack?: boolean; + isLoading?: boolean; +}; + +export type WizardProps = { + onSubmit: () => Promise; + steps: Step[]; +}; diff --git a/src/components/Wizard/components/WizardButtons/WizardButtons.styles.tsx b/src/components/Wizard/components/WizardButtons/WizardButtons.styles.tsx new file mode 100644 index 0000000..723e570 --- /dev/null +++ b/src/components/Wizard/components/WizardButtons/WizardButtons.styles.tsx @@ -0,0 +1,7 @@ +import { Box, styled } from "@mui/material"; + +export const ButtonWrapper = styled(Box)(({ theme }) => ({ + display: "flex", + justifyContent: "space-between", + paddingTop: theme.spacing(2), +})); diff --git a/src/components/Wizard/components/WizardButtons/WizardButtons.tsx b/src/components/Wizard/components/WizardButtons/WizardButtons.tsx new file mode 100644 index 0000000..23ae595 --- /dev/null +++ b/src/components/Wizard/components/WizardButtons/WizardButtons.tsx @@ -0,0 +1,43 @@ +import { Button } from "@mui/material"; +import { WizardButtonsProps } from "./WizardButtons.types"; +import { ButtonWrapper } from "./WizardButtons.styles"; + +export const WizardButtons: React.FC = ({ + currentStep, + setStep, + isFirstStep, + isLastStep, + canProceedFurther, + canProceedBack, + onSubmit, +}) => { + const onPreviousStepChange = () => { + setStep(currentStep - 1); + }; + + const onNextStepChange = () => { + if (isLastStep) { + onSubmit(); + } else { + setStep(currentStep + 1); + } + }; + + return ( + + + + + ); +}; diff --git a/src/components/Wizard/components/WizardButtons/WizardButtons.types.ts b/src/components/Wizard/components/WizardButtons/WizardButtons.types.ts new file mode 100644 index 0000000..ec259d3 --- /dev/null +++ b/src/components/Wizard/components/WizardButtons/WizardButtons.types.ts @@ -0,0 +1,10 @@ +import { WizardProps } from "../../Wizard.types"; + +export type WizardButtonsProps = { + setStep: (step: number) => void; + currentStep: number; + isFirstStep: boolean; + isLastStep: boolean; + canProceedFurther: boolean; + canProceedBack: boolean; +} & Pick; diff --git a/src/components/Wizard/components/WizardButtons/index.ts b/src/components/Wizard/components/WizardButtons/index.ts new file mode 100644 index 0000000..5fd20ac --- /dev/null +++ b/src/components/Wizard/components/WizardButtons/index.ts @@ -0,0 +1 @@ +export * from "./WizardButtons"; diff --git a/src/components/Wizard/components/WizardHeader/WizardHeader.tsx b/src/components/Wizard/components/WizardHeader/WizardHeader.tsx new file mode 100644 index 0000000..5c0c01b --- /dev/null +++ b/src/components/Wizard/components/WizardHeader/WizardHeader.tsx @@ -0,0 +1,17 @@ +import { Stepper, Step, StepLabel } from "@mui/material"; +import { WizardHeaderProps } from "./WizardHeader.types"; + +export const WizardHeader: React.FC = ({ + currentStep, + steps, +}) => { + return ( + + {steps.map((label) => ( + + {label} + + ))} + + ); +}; diff --git a/src/components/Wizard/components/WizardHeader/WizardHeader.types.ts b/src/components/Wizard/components/WizardHeader/WizardHeader.types.ts new file mode 100644 index 0000000..1135cc0 --- /dev/null +++ b/src/components/Wizard/components/WizardHeader/WizardHeader.types.ts @@ -0,0 +1,4 @@ +export type WizardHeaderProps = { + steps: string[]; + currentStep: number; +}; diff --git a/src/components/Wizard/components/WizardHeader/index.ts b/src/components/Wizard/components/WizardHeader/index.ts new file mode 100644 index 0000000..fdfa018 --- /dev/null +++ b/src/components/Wizard/components/WizardHeader/index.ts @@ -0,0 +1 @@ +export * from "./WizardHeader"; diff --git a/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.constant.ts b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.constant.ts new file mode 100644 index 0000000..b61d08f --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.constant.ts @@ -0,0 +1,7 @@ +import { Feature } from "./FeatureSelectList.types"; + +export const FEATURE_OPTIONS: Feature[] = [ + { id: "1", name: "Feature A", description: "Description for Feature A" }, + { id: "2", name: "Feature B", description: "Description for Feature B" }, + { id: "3", name: "Feature C", description: "Description for Feature C" }, +]; diff --git a/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.tsx b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.tsx new file mode 100644 index 0000000..7a50ccb --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.tsx @@ -0,0 +1,36 @@ +import { + Checkbox, + List, + ListItem, + ListItemIcon, + ListItemText, +} from "@mui/material"; +import { SelectFormProps } from "./FeatureSelectList.types"; +import { useSelectedFeatures } from "../../../../../hooks/useSelectedFeatures"; + +export const FeatureSelectList: React.FC = ({ options }) => { + const { selectedFeatureIds, updateSelectedFeatures } = useSelectedFeatures(); + + const handleToggle = (featureId: string) => { + if (selectedFeatureIds.includes(featureId)) { + updateSelectedFeatures( + selectedFeatureIds.filter((id) => id !== featureId) + ); + } else { + updateSelectedFeatures([...selectedFeatureIds, featureId]); + } + }; + + return ( + + {options.map(({ id, name, description }) => ( + handleToggle(id)}> + + + + + + ))} + + ); +}; diff --git a/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.types.ts b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.types.ts new file mode 100644 index 0000000..a53ada4 --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.types.ts @@ -0,0 +1,9 @@ +export type Feature = { + id: string; + name: string; + description: string; +}; + +export type SelectFormProps = { + options: Feature[]; +}; diff --git a/src/components/Wizard/components/WizardSteps/FeatureSelectList/index.ts b/src/components/Wizard/components/WizardSteps/FeatureSelectList/index.ts new file mode 100644 index 0000000..118a3dc --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/FeatureSelectList/index.ts @@ -0,0 +1 @@ +export * from "./FeatureSelectList"; diff --git a/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.styles.tsx b/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.styles.tsx new file mode 100644 index 0000000..860d9d1 --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.styles.tsx @@ -0,0 +1,5 @@ +import { styled, Typography } from "@mui/material"; + +export const CenteredTypography = styled(Typography)(() => ({ + textAlign: 'center' +})) \ No newline at end of file diff --git a/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.tsx b/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.tsx new file mode 100644 index 0000000..13d52bc --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/ReviewScreen/ReviewScreen.tsx @@ -0,0 +1,47 @@ +import { Alert, Box, Grid, Typography } from "@mui/material"; +import { FEATURE_OPTIONS } from "../FeatureSelectList/FeatureSelectList.constant"; +import { Feature } from "../FeatureSelectList/FeatureSelectList.types"; +import { + useUserInfo, + useSelectedFeatures, + useInfoSubmitted, +} from "../../../../../hooks"; + +export const ReviewScreen = () => { + const { userName, userDescription } = useUserInfo(); + const { selectedFeatureIds } = useSelectedFeatures(); + const { isSuccess } = useInfoSubmitted(); + + const selectedFeatures: Feature[] = FEATURE_OPTIONS.filter((feature) => + selectedFeatureIds.includes(feature.id) + ); + + return ( + <> + {isSuccess && ( + + Your account has been successfully created! + + )} + + + Name + {userName} + + + Description + {userDescription} + + + Selected features + {selectedFeatures.map(({ id, name, description }) => ( + + {name} + {description} + + ))} + + + + ); +}; diff --git a/src/components/Wizard/components/WizardSteps/ReviewScreen/index.ts b/src/components/Wizard/components/WizardSteps/ReviewScreen/index.ts new file mode 100644 index 0000000..de82fbe --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/ReviewScreen/index.ts @@ -0,0 +1 @@ +export * from "./ReviewScreen"; diff --git a/src/components/Wizard/components/WizardSteps/UserInfoForm/UserInfoForm.tsx b/src/components/Wizard/components/WizardSteps/UserInfoForm/UserInfoForm.tsx new file mode 100644 index 0000000..10d54a9 --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/UserInfoForm/UserInfoForm.tsx @@ -0,0 +1,65 @@ +import { FormEvent, useState } from "react"; +import { useUserInfo } from "../../../../../hooks/useUserInfo"; +import { TextField, Button, Alert, CircularProgress } from "@mui/material"; + +export const UserInfoForm: React.FC = () => { + const { + userName, + userDescription, + onNameSubmit, + hasError, + isSuccess, + isLoading, + errorDetails, + } = useUserInfo(); + const [name, setName] = useState(userName); + const [description, setDescription] = useState(userDescription); + + const onSubmit = async (e: FormEvent) => { + e.preventDefault(); + await onNameSubmit(name, description); + }; + + return ( +
+ {hasError && {errorDetails}} + {isSuccess && Welcome {userName}!} + + setName(e.target.value)} + fullWidth + variant="outlined" + margin="normal" + slotProps={{ htmlInput: { maxLength: 63 } }} + autoFocus + /> + + setDescription(e.target.value)} + fullWidth + variant="outlined" + margin="normal" + multiline + rows={4} + placeholder="Enter a brief description of yourself" + /> + + + + ); +}; diff --git a/src/components/Wizard/components/WizardSteps/UserInfoForm/index.ts b/src/components/Wizard/components/WizardSteps/UserInfoForm/index.ts new file mode 100644 index 0000000..63451f8 --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/UserInfoForm/index.ts @@ -0,0 +1 @@ +export * from "./UserInfoForm"; diff --git a/src/components/Wizard/components/WizardSteps/index.ts b/src/components/Wizard/components/WizardSteps/index.ts new file mode 100644 index 0000000..8871c69 --- /dev/null +++ b/src/components/Wizard/components/WizardSteps/index.ts @@ -0,0 +1,3 @@ +export * from "./UserInfoForm"; +export * from "./FeatureSelectList"; +export * from "./ReviewScreen"; diff --git a/src/components/Wizard/components/index.ts b/src/components/Wizard/components/index.ts new file mode 100644 index 0000000..2222eb3 --- /dev/null +++ b/src/components/Wizard/components/index.ts @@ -0,0 +1,2 @@ +export * from "./WizardButtons"; +export * from "./WizardHeader"; diff --git a/src/components/Wizard/index.ts b/src/components/Wizard/index.ts new file mode 100644 index 0000000..063f0a9 --- /dev/null +++ b/src/components/Wizard/index.ts @@ -0,0 +1 @@ +export * from "./Wizard"; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..063f0a9 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export * from "./Wizard"; diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..fe8e6b1 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,3 @@ +export * from "./useUserInfo"; +export * from "./useSelectedFeatures"; +export * from "./useInfoSummited"; diff --git a/src/hooks/useInfoSummited.ts b/src/hooks/useInfoSummited.ts new file mode 100644 index 0000000..c1b6fe4 --- /dev/null +++ b/src/hooks/useInfoSummited.ts @@ -0,0 +1,94 @@ +import { useDispatch, useSelector } from "react-redux"; +import { + setIsSuccess, + setHasError, + setIsLoading, + onReset, + setErrorDetails, +} from "../slices/submitSlice"; +import { useCallback } from "react"; +import { RootState } from "../redux/store"; +import { DummyApi } from "../api/dummy-api"; +import { useUserInfo } from "./useUserInfo"; +import { useSelectedFeatures } from "./useSelectedFeatures"; + +export const useInfoSubmitted = () => { + const dispatch = useDispatch(); + const { submit } = DummyApi; + const { userName, userDescription } = useUserInfo(); + const { selectedFeatureIds } = useSelectedFeatures(); + + const isSuccess = useSelector( + (state: RootState) => state.infoSubmitted.isSuccess + ); + const hasError = useSelector( + (state: RootState) => state.infoSubmitted.hasError + ); + const isLoading = useSelector( + (state: RootState) => state.infoSubmitted.isLoading + ); + const errorDetails = useSelector( + (state: RootState) => state.infoSubmitted.errorDetails + ); + + const updateIsSuccess = useCallback( + (success: boolean) => dispatch(setIsSuccess(success)), + [dispatch] + ); + + const updateIsLoading = useCallback( + (loading: boolean) => dispatch(setIsLoading(loading)), + [dispatch] + ); + + const updateHasError = useCallback( + (error: boolean) => dispatch(setHasError(error)), + [dispatch] + ); + + const updateErrorDetails = useCallback( + (error: string) => dispatch(setErrorDetails(error)), + [dispatch] + ); + + const updateOnReset = useCallback(() => dispatch(onReset()), [dispatch]); + + const onFinalSubmit = useCallback(async () => { + updateOnReset(); + try { + updateIsLoading(true); + await submit({ + name: userName, + features: selectedFeatureIds, + description: userDescription, + }); + updateIsLoading(false); + updateIsSuccess(true); + } catch (e) { + updateErrorDetails(e as string); + updateIsLoading(false); + updateHasError(true); + } + }, [ + updateOnReset, + updateIsLoading, + submit, + userName, + selectedFeatureIds, + userDescription, + updateIsSuccess, + updateErrorDetails, + updateHasError, + ]); + + return { + isLoading, + isSuccess, + hasError, + errorDetails, + updateIsLoading, + updateIsSuccess, + updateHasError, + onFinalSubmit, + }; +}; diff --git a/src/hooks/useSelectedFeatures.ts b/src/hooks/useSelectedFeatures.ts new file mode 100644 index 0000000..f818308 --- /dev/null +++ b/src/hooks/useSelectedFeatures.ts @@ -0,0 +1,23 @@ +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../redux/store"; +import { useCallback } from "react"; +import { setSelectedFeatures } from "../slices/featureSlice"; + +export const useSelectedFeatures = () => { + const dispatch = useDispatch(); + + const selectedFeatureIds = useSelector( + (state: RootState) => state.selectedFeatures.selectedFeatureIds + ); + + const updateSelectedFeatures = useCallback( + (selectedFeatureIds: string[]) => + dispatch(setSelectedFeatures(selectedFeatureIds)), + [dispatch] + ); + + return { + selectedFeatureIds, + updateSelectedFeatures, + }; +}; diff --git a/src/hooks/useUserInfo.ts b/src/hooks/useUserInfo.ts new file mode 100644 index 0000000..c5a5c11 --- /dev/null +++ b/src/hooks/useUserInfo.ts @@ -0,0 +1,104 @@ +import { useDispatch, useSelector } from "react-redux"; +import { + setUserName, + setUserDescription, + setIsSuccess, + setHasError, + setIsLoading, + onReset, + setErrorDetails, +} from "../slices/userInfoSlice"; +import { useCallback } from "react"; +import { RootState } from "../redux/store"; +import { DummyApi } from "../api/dummy-api"; + +export const useUserInfo = () => { + const dispatch = useDispatch(); + const { checkName } = DummyApi; + + const userName = useSelector((state: RootState) => state.userInfo.name); + const userDescription = useSelector( + (state: RootState) => state.userInfo.description + ); + const isSuccess = useSelector((state: RootState) => state.userInfo.isSuccess); + const hasError = useSelector((state: RootState) => state.userInfo.hasError); + const isLoading = useSelector((state: RootState) => state.userInfo.isLoading); + const errorDetails = useSelector( + (state: RootState) => state.userInfo.errorDetails + ); + + const updateUserName = useCallback( + (newName: string) => dispatch(setUserName(newName)), + [dispatch] + ); + + const updateUserDescription = useCallback( + (newDescription: string) => dispatch(setUserDescription(newDescription)), + [dispatch] + ); + + const updateIsSuccess = useCallback( + (success: boolean) => dispatch(setIsSuccess(success)), + [dispatch] + ); + + const updateIsLoading = useCallback( + (loading: boolean) => dispatch(setIsLoading(loading)), + [dispatch] + ); + + const updateHasError = useCallback( + (error: boolean) => dispatch(setHasError(error)), + [dispatch] + ); + + const updateErrorDetails = useCallback( + (error: string) => dispatch(setErrorDetails(error)), + [dispatch] + ); + + const updateOnReset = useCallback(() => dispatch(onReset()), [dispatch]); + + const onNameSubmit = useCallback( + async (name: string, description: string) => { + updateOnReset(); + try { + updateIsLoading(true); + await checkName({ name }); + updateIsLoading(false); + updateIsSuccess(true); + updateUserDescription(description); + updateUserName(name); + } catch (e) { + updateErrorDetails(e as string); + updateIsLoading(false); + updateHasError(true); + } + }, + [ + updateOnReset, + updateIsLoading, + checkName, + updateIsSuccess, + updateUserDescription, + updateUserName, + updateErrorDetails, + updateHasError, + ] + ); + + return { + userName, + userDescription, + isLoading, + isSuccess, + hasError, + errorDetails, + updateUserName, + updateUserDescription, + updateIsLoading, + updateIsSuccess, + updateHasError, + onNameSubmit, + }; +}; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/main.tsx b/src/main.tsx index bef5202..4aff025 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,5 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx new file mode 100644 index 0000000..9f99739 --- /dev/null +++ b/src/pages/Home/Home.tsx @@ -0,0 +1,47 @@ +import { useMemo } from "react"; +import { Wizard } from "../../components"; +import { + UserInfoForm, + FeatureSelectList, + ReviewScreen, +} from "../../components/Wizard/components/WizardSteps"; +import { Step } from "../../components/Wizard/Wizard.types"; +import { + useUserInfo, + useSelectedFeatures, + useInfoSubmitted, +} from "../../hooks"; +import { FEATURE_OPTIONS } from "../../components/Wizard/components/WizardSteps/FeatureSelectList/FeatureSelectList.constant"; + +export const Home: React.FC = () => { + const { isSuccess: isNameValid } = useUserInfo(); + const { selectedFeatureIds } = useSelectedFeatures(); + const { + onFinalSubmit, + isSuccess: isFinalSubmitSuccessful, + isLoading, + } = useInfoSubmitted(); + const STEPS: Step[] = useMemo( + () => [ + { + label: "Insert your name", + content: , + canProceedFurther: isNameValid, + }, + { + label: "Choose your features", + content: , + canProceedFurther: selectedFeatureIds.length > 0, + }, + { + label: "Create your account", + content: , + canProceedFurther: !isFinalSubmitSuccessful, + canProceedBack: !isFinalSubmitSuccessful, + isLoading, + }, + ], + [isNameValid, selectedFeatureIds.length, isFinalSubmitSuccessful, isLoading] + ); + return onFinalSubmit()} />; +}; diff --git a/src/pages/Home/index.ts b/src/pages/Home/index.ts new file mode 100644 index 0000000..5d79295 --- /dev/null +++ b/src/pages/Home/index.ts @@ -0,0 +1 @@ +export * from "./Home"; diff --git a/src/pages/index.ts b/src/pages/index.ts new file mode 100644 index 0000000..5d79295 --- /dev/null +++ b/src/pages/index.ts @@ -0,0 +1 @@ +export * from "./Home"; diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..56a77dd --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,17 @@ +import { configureStore } from "@reduxjs/toolkit"; +import userInfoReducer from "../slices/userInfoSlice"; +import featureSliceReducer from "../slices/featureSlice"; +import submitSliceReducer from "../slices/submitSlice"; + +const store = configureStore({ + reducer: { + userInfo: userInfoReducer, + selectedFeatures: featureSliceReducer, + infoSubmitted: submitSliceReducer, + }, +}); + +export default store; + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/src/slices/featureSlice.ts b/src/slices/featureSlice.ts new file mode 100644 index 0000000..e55fa19 --- /dev/null +++ b/src/slices/featureSlice.ts @@ -0,0 +1,22 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; + +interface FeatureState { + selectedFeatureIds: string[]; +} + +const initialState: FeatureState = { + selectedFeatureIds: [], +}; + +export const featureSlice = createSlice({ + name: "feature", + initialState, + reducers: { + setSelectedFeatures: (state, action: PayloadAction) => { + state.selectedFeatureIds = action.payload; + }, + }, +}); + +export const { setSelectedFeatures } = featureSlice.actions; +export default featureSlice.reducer; diff --git a/src/slices/submitSlice.ts b/src/slices/submitSlice.ts new file mode 100644 index 0000000..f3c6604 --- /dev/null +++ b/src/slices/submitSlice.ts @@ -0,0 +1,49 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; + +interface UserNameState { + isSuccess: boolean; + hasError: boolean; + errorDetails: string; + isLoading: boolean; +} + +const initialState: UserNameState = { + isSuccess: false, + hasError: false, + errorDetails: "", + isLoading: false, +}; + +export const submitSlice = createSlice({ + name: "infoSubmitted", + initialState, + reducers: { + setIsSuccess: (state, action: PayloadAction) => { + state.isSuccess = action.payload; + }, + setHasError: (state, action: PayloadAction) => { + state.hasError = action.payload; + }, + setErrorDetails: (state, action: PayloadAction) => { + state.errorDetails = action.payload; + }, + setIsLoading: (state, action: PayloadAction) => { + state.isLoading = action.payload; + }, + onReset: (state) => { + state.isLoading = false; + state.hasError = false; + state.errorDetails = ""; + state.isSuccess = false; + }, + }, +}); + +export const { + setIsSuccess, + setHasError, + setErrorDetails, + setIsLoading, + onReset, +} = submitSlice.actions; +export default submitSlice.reducer; diff --git a/src/slices/userInfoSlice.ts b/src/slices/userInfoSlice.ts new file mode 100644 index 0000000..0ada4df --- /dev/null +++ b/src/slices/userInfoSlice.ts @@ -0,0 +1,62 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; + +interface UserNameState { + name: string; + description: string; + isSuccess: boolean; + hasError: boolean; + errorDetails: string; + isLoading: boolean; +} + +const initialState: UserNameState = { + name: "", + description: "", + isSuccess: false, + hasError: false, + errorDetails: "", + isLoading: false, +}; + +export const userInfoSlice = createSlice({ + name: "userInfo", + initialState, + reducers: { + setUserName: (state, action: PayloadAction) => { + state.name = action.payload; + }, + setUserDescription: (state, action: PayloadAction) => { + state.description = action.payload; + }, + setIsSuccess: (state, action: PayloadAction) => { + state.isSuccess = action.payload; + }, + setHasError: (state, action: PayloadAction) => { + state.hasError = action.payload; + }, + setErrorDetails: (state, action: PayloadAction) => { + state.errorDetails = action.payload; + }, + setIsLoading: (state, action: PayloadAction) => { + state.isLoading = action.payload; + }, + onReset: (state) => { + state.name = ""; + state.isLoading = false; + state.hasError = false; + state.errorDetails = ""; + state.isSuccess = false; + }, + }, +}); + +export const { + setUserName, + setUserDescription, + setIsSuccess, + setHasError, + setErrorDetails, + setIsLoading, + onReset, +} = userInfoSlice.actions; +export default userInfoSlice.reducer;