commit 98c5e7d37d7d00d1bf3290d24afe5f20388e1885
Author: Justin Ferrari <‘justinwesleyferrari@gmail.com’>
Date: Fri Dec 8 13:27:19 2023 -0500
Initial Q-Shop Commit in its own repo
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bfe8e32
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+*.zip
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..0a4d4fc
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,10 @@
+{
+ "printWidth": 80,
+ "singleQuote": false,
+ "trailingComma": "es5",
+ "bracketSpacing": true,
+ "jsxBracketSameLine": false,
+ "arrowParens": "avoid",
+ "tabWidth": 2,
+ "semi": true
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..717953c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Q-Shop
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..d64b961
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6895 @@
+{
+ "name": "q-blog",
+ "version": "0.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "q-blog",
+ "version": "0.0.0",
+ "dependencies": {
+ "@emotion/react": "^11.10.6",
+ "@emotion/styled": "^11.10.6",
+ "@mui/icons-material": "^5.11.11",
+ "@mui/material": "^5.11.13",
+ "@reduxjs/toolkit": "^1.9.3",
+ "@types/react-grid-layout": "^1.3.2",
+ "axios": "^1.3.4",
+ "compressorjs": "^1.2.1",
+ "localforage": "^1.10.0",
+ "moment": "^2.29.4",
+ "philliplm-react-modern-audio-player": "^1.4.6",
+ "react": "^18.2.0",
+ "react-copy-to-clipboard": "^5.1.0",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
+ "react-dom": "^18.2.0",
+ "react-dropzone": "^14.2.3",
+ "react-grid-layout": "^1.3.4",
+ "react-intersection-observer": "^9.4.3",
+ "react-joyride": "^2.5.4",
+ "react-masonry-css": "^1.0.16",
+ "react-redux": "^8.0.5",
+ "react-resize-detector": "^8.0.4",
+ "react-router-dom": "^6.9.0",
+ "react-toastify": "^9.1.2",
+ "react-virtuoso": "^4.3.3",
+ "short-unique-id": "^4.4.4",
+ "slate": "^0.91.4",
+ "slate-history": "^0.86.0",
+ "slate-react": "^0.91.11"
+ },
+ "devDependencies": {
+ "@mui/types": "^7.2.3",
+ "@types/react": "^18.0.28",
+ "@types/react-copy-to-clipboard": "^5.0.4",
+ "@types/react-dom": "^18.0.11",
+ "@vitejs/plugin-react-swc": "^3.2.0",
+ "prettier": "^2.8.6",
+ "typescript": "^4.9.3",
+ "vite": "^4.2.0",
+ "worker-loader": "^3.0.8"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz",
+ "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
+ "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
+ "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
+ "dependencies": {
+ "@babel/template": "^7.20.7",
+ "@babel/types": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz",
+ "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
+ "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.11"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
+ "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz",
+ "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==",
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.21.3",
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-function-name": "^7.21.0",
+ "@babel/helper-hoist-variables": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/parser": "^7.21.3",
+ "@babel/types": "^7.21.3",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz",
+ "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
+ "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.0",
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/serialize": "^1.1.1",
+ "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.1.3"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.10.5",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz",
+ "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/sheet": "^1.2.1",
+ "@emotion/utils": "^1.2.0",
+ "@emotion/weak-memoize": "^0.3.0",
+ "stylis": "4.1.3"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
+ "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz",
+ "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
+ "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz",
+ "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.10.6",
+ "@emotion/cache": "^11.10.5",
+ "@emotion/serialize": "^1.1.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0",
+ "@emotion/utils": "^1.2.0",
+ "@emotion/weak-memoize": "^0.3.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==",
+ "dependencies": {
+ "@emotion/hash": "^0.9.0",
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/unitless": "^0.8.0",
+ "@emotion/utils": "^1.2.0",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz",
+ "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA=="
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz",
+ "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.10.6",
+ "@emotion/is-prop-valid": "^1.2.0",
+ "@emotion/serialize": "^1.1.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0",
+ "@emotion/utils": "^1.2.0"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
+ "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz",
+ "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz",
+ "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw=="
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
+ "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz",
+ "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz",
+ "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz",
+ "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz",
+ "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz",
+ "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz",
+ "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz",
+ "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz",
+ "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz",
+ "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz",
+ "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz",
+ "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz",
+ "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz",
+ "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz",
+ "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz",
+ "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz",
+ "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz",
+ "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz",
+ "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz",
+ "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz",
+ "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz",
+ "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz",
+ "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@formatjs/ecma402-abstract": {
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
+ "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "0.2.32",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz",
+ "integrity": "sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz",
+ "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "@formatjs/icu-skeleton-parser": "1.3.18",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "1.3.18",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
+ "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.2.32",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
+ "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@gilbarbara/deep-equal": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz",
+ "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA=="
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz",
+ "integrity": "sha512-wjeur7K4AecT+YwoBmBXQ/+n5lP69tuZc34hw09s44EozZK7FZHSyfPvRp5/xEb2D6abLboskDY4jG+Nt0TNUQ==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "node_modules/@internationalized/message": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.0.tgz",
+ "integrity": "sha512-Oo5m70FcBdADf7G8NkUffVSfuCdeAYVfsvNjZDi9ELpjvkc4YNJVTHt/NyTI9K7FgAVoELxiP9YmN0sJ+HNHYQ==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14",
+ "intl-messageformat": "^10.1.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.2.0.tgz",
+ "integrity": "sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "node_modules/@internationalized/string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.1.0.tgz",
+ "integrity": "sha512-TJQKiyUb+wyAfKF59UNeZ/kELMnkxyecnyPCnBI1ma4NaXReJW+7Cc2mObXAqraIBJUVv7rgI46RLKrLgi35ng==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+ "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@juggle/resize-observer": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
+ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
+ },
+ "node_modules/@mui/base": {
+ "version": "5.0.0-alpha.121",
+ "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz",
+ "integrity": "sha512-8nJRY76UqlJV+q/Yzo0tgGfPWEOa+4N9rjO81fMmcJqP0I6m54hLDXsjvMg4tvelY5eKHXUK6Tb7en+GHfTqZA==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@emotion/is-prop-valid": "^1.2.0",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "@popperjs/core": "^2.11.6",
+ "clsx": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.13.tgz",
+ "integrity": "sha512-lx+GXBR9h/ApZsEP728tl0pyZyuajto+VnBgsoAzw1d5+CbmOo8ZWieKwVUGxZlPT1wMYNUYS5NtKzCli0xYjw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "5.11.11",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz",
+ "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@mui/material": "^5.0.0",
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.13.tgz",
+ "integrity": "sha512-2CnSj43F+159LbGmTLLQs5xbGYMiYlpTByQhP7c7cMX6opbScctBFE1PuyElpAmwW8Ag9ysfZH1d1MFAmJQkjg==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/base": "5.0.0-alpha.121",
+ "@mui/core-downloads-tracker": "^5.11.13",
+ "@mui/system": "^5.11.13",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "@types/react-transition-group": "^4.4.5",
+ "clsx": "^1.2.1",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz",
+ "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/utils": "^5.11.13",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "5.11.11",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz",
+ "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@emotion/cache": "^11.10.5",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.13.tgz",
+ "integrity": "sha512-OWP0Alp6C8ufnGm9+CZcl3d+OoRXL2PnrRT5ohaMLxvGL9OfNcL2t4JOjMmA0k1UAGd6E/Ygbu5lEPrZSDlvCg==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/private-theming": "^5.11.13",
+ "@mui/styled-engine": "^5.11.11",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "clsx": "^1.2.1",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz",
+ "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==",
+ "peerDependencies": {
+ "@types/react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz",
+ "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@types/prop-types": "^15.7.5",
+ "@types/react-is": "^16.7.1 || ^17.0.0",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.6",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
+ "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@react-aria/focus": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.11.0.tgz",
+ "integrity": "sha512-yPuWs9bAR9CMfIwyOPm2oXLPF19gNkUqW+ozSPhWbLMTEa8Ma09eHW1br4xbN+4ONOm/dCJsIkxTNPUkiLdQoA==",
+ "dependencies": {
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/i18n": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.7.0.tgz",
+ "integrity": "sha512-PZCWmhO9mJvelwiYlsXLY6W4L2o+oza3xnDx0cZDVqp/Hf+OwMAPHWtZsFRTKdjk4TaOPB/ISc9HknWn6UpY4w==",
+ "dependencies": {
+ "@internationalized/date": "^3.1.0",
+ "@internationalized/message": "^3.1.0",
+ "@internationalized/number": "^3.2.0",
+ "@internationalized/string": "^3.1.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.14.0.tgz",
+ "integrity": "sha512-e1Tkr0UTuYFpV21PJrXy7jEY542Vl+C2Fo70oukZ1fN+wtfQkzodsTIzyepXb7kVMGmC34wDisMJUrksVkfY2w==",
+ "dependencies": {
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/overlays": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.13.0.tgz",
+ "integrity": "sha512-hRZyhAYzrlCcEWQ2k2jP24b0wc5/355Xl5w5FZHRmPeU1U4XlFwKX/eFlBs/li9Sprm1bTDXrCY480Kl6UsKDA==",
+ "dependencies": {
+ "@react-aria/focus": "^3.11.0",
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-aria/visually-hidden": "^3.7.0",
+ "@react-stately/overlays": "^3.5.0",
+ "@react-types/button": "^3.7.1",
+ "@react-types/overlays": "^3.7.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.5.0.tgz",
+ "integrity": "sha512-h0MJdSWOd1qObLnJ8mprU31wI8tmKFJMuwT22MpWq6psisOOZaga6Ml4u6Ee6M6duWWISjXvqO4Sb/J0PBA+nQ==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/utils": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.15.0.tgz",
+ "integrity": "sha512-aJZBG++iz1UwTW5gXFaHicKju4p0MPhAyBTcf2awHYWeTUUslDjJcEnNg7kjBYZBOrOSlA2rAt7/7C5CCURQPg==",
+ "dependencies": {
+ "@react-aria/ssr": "^3.5.0",
+ "@react-stately/utils": "^3.6.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-aria/visually-hidden": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.7.0.tgz",
+ "integrity": "sha512-v/0ujJ67H6LjwY8J7mIGPVB1K8suBArLV+w8UGdX/wFXRL7H4r2fiqlrwAElWSmNbhDQl5BDm/Zh/ub9jB9yzA==",
+ "dependencies": {
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-dnd/asap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+ "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+ },
+ "node_modules/@react-dnd/invariant": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+ "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+ },
+ "node_modules/@react-dnd/shallowequal": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+ },
+ "node_modules/@react-spectrum/layout": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.5.0.tgz",
+ "integrity": "sha512-Yjy0uvGUHjczl44PQlMqOEBoJIe4coYwDQCoVl20YrTzGiMdwLV24Hs9uqK3I6yKmAoNxTehe0CdOVgwuVRF9g==",
+ "dependencies": {
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/layout": "^3.3.6",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "@react-spectrum/provider": "^3.0.0",
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spectrum/provider": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.7.0.tgz",
+ "integrity": "sha512-6Uch60R5PKJC+ZY3VweVadaCoaIj6Nd85TKfFH4jf8NEzXv5CqjwUzwV5aQ6u3k1K24Ves7RpLlK53HW1wUm6w==",
+ "dependencies": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/overlays": "^3.13.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/provider": "^3.6.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spectrum/theme-default": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.0.tgz",
+ "integrity": "sha512-DagwngJ9T7yGgtdTwKj9LaHbg3AWLW2/ILJK92xbPj/yAAEVXJ8vhiP9Tg3W3mZMsxBCNwTVLyOLheu7d+D8og==",
+ "dependencies": {
+ "@react-types/provider": "^3.6.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spectrum/utils": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.9.0.tgz",
+ "integrity": "sha512-I1JKwiyLE54mAT7Z2wadVKoai1Q60QFg4XNuYUwk8TwWAEz7DUUHucntHAND6dqFpVIMK1Rk9lcZBft43FF2BQ==",
+ "dependencies": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spectrum/view": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.5.0.tgz",
+ "integrity": "sha512-Zzsy2S6gT14vtli52tjM5VP1EdMOP+bppNCmwX+saUJuxaEdj6oYjP89quE4HwALzYceOfmXIuGDyEVeDcuSsQ==",
+ "dependencies": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/shared": "^3.17.0",
+ "@react-types/view": "^3.4.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "@react-spectrum/provider": "^3.0.0",
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-stately/overlays": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.5.0.tgz",
+ "integrity": "sha512-r+U/G0Y4tCfI5wyBeIu+hmcZVRN8ChoK2zM1srPH9nDKsijQard2goX+9YmKng2LJ01Re/P6F8S8jYbpfEdLfQ==",
+ "dependencies": {
+ "@react-stately/utils": "^3.6.0",
+ "@react-types/overlays": "^3.7.0",
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-stately/utils": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.6.0.tgz",
+ "integrity": "sha512-rptF7iUWDrquaYvBAS4QQhOBQyLBncDeHF03WnHXAxnuPJXNcr9cXJtjJPGCs036ZB8Q2hc9BGG5wNyMkF5v+Q==",
+ "dependencies": {
+ "@swc/helpers": "^0.4.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/button": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.7.1.tgz",
+ "integrity": "sha512-c+8xjmqWSjI5/mEHVLbVSp0eh/z2UU8Ga+wqjbEUZUjm8uopYj1PaCAwZ7YgcAebyQrL/21GyjK6tFHKzuUdJQ==",
+ "dependencies": {
+ "@react-types/shared": "^3.17.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/layout": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.6.tgz",
+ "integrity": "sha512-8YF7nb+0AtSDRj+rejMRS5djWpqAf5Q6sJzgdccn42JQxW7jvxoKqN0pCRPqpMzQqae5hOsYYpVNfuIHwUUkUA==",
+ "dependencies": {
+ "@react-types/shared": "^3.17.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/overlays": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.7.0.tgz",
+ "integrity": "sha512-LstucncZ8dM+xJYEijI1V6jGH20w5XO/T60r7JTrgQElMC86phPeoWkMTN4c2lsRikybolDbvXL6XsF76YO56A==",
+ "dependencies": {
+ "@react-types/shared": "^3.17.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/provider": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.6.0.tgz",
+ "integrity": "sha512-29+X5yMr1uHUc/BmFoyBqfWyMT3YGZJffaprtGGRQkCuwMbqoA8GI+rGJzNAMAD/1cPNo237PAGsi4NAup5tAQ==",
+ "dependencies": {
+ "@react-types/shared": "^3.17.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.17.0.tgz",
+ "integrity": "sha512-1SNZ/RhVrrQ1e6yE0bPV7d5Sfp+Uv0dfUEhwF9MAu2v5msu7AMewnsiojKNA0QA6Ing1gpDLjHCxtayQfuxqcg==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-types/view": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.0.tgz",
+ "integrity": "sha512-lXDLbOYRFwo8hxrSDIYUmnxOGZlHBDVEFe1ZKAXJ+qPwntzxhzUeAuddTiMDhEETKgTclmlIsJnwz+HNv6dhSQ==",
+ "dependencies": {
+ "@react-types/shared": "^3.17.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
+ "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==",
+ "dependencies": {
+ "immer": "^9.0.16",
+ "redux": "^4.2.0",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.7"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.0.2"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz",
+ "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@swc/core": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz",
+ "integrity": "sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.3.41",
+ "@swc/core-darwin-x64": "1.3.41",
+ "@swc/core-linux-arm-gnueabihf": "1.3.41",
+ "@swc/core-linux-arm64-gnu": "1.3.41",
+ "@swc/core-linux-arm64-musl": "1.3.41",
+ "@swc/core-linux-x64-gnu": "1.3.41",
+ "@swc/core-linux-x64-musl": "1.3.41",
+ "@swc/core-win32-arm64-msvc": "1.3.41",
+ "@swc/core-win32-ia32-msvc": "1.3.41",
+ "@swc/core-win32-x64-msvc": "1.3.41"
+ }
+ },
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.41.tgz",
+ "integrity": "sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.41.tgz",
+ "integrity": "sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.41.tgz",
+ "integrity": "sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.41.tgz",
+ "integrity": "sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.41.tgz",
+ "integrity": "sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.41.tgz",
+ "integrity": "sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.41.tgz",
+ "integrity": "sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.41.tgz",
+ "integrity": "sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.41.tgz",
+ "integrity": "sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.41.tgz",
+ "integrity": "sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
+ "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/eslint": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz",
+ "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
+ "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "0.0.51",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
+ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/is-hotkey": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz",
+ "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ=="
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "dev": true
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.14.191",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
+ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.15.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz",
+ "integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==",
+ "devOptional": true,
+ "peer": true
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.0.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
+ "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-copy-to-clipboard": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
+ "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.0.11",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz",
+ "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==",
+ "devOptional": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-grid-layout": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz",
+ "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-is": {
+ "version": "17.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
+ "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
+ },
+ "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/@vitejs/plugin-react-swc": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
+ "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==",
+ "dev": true,
+ "dependencies": {
+ "@swc/core": "^1.3.35"
+ },
+ "peerDependencies": {
+ "vite": "^4"
+ }
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
+ "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
+ "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
+ "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
+ "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
+ "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.11.1",
+ "@webassemblyjs/helper-api-error": "1.11.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
+ "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
+ "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
+ "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
+ "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
+ "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
+ "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/helper-wasm-section": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1",
+ "@webassemblyjs/wasm-opt": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1",
+ "@webassemblyjs/wast-printer": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
+ "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/ieee754": "1.11.1",
+ "@webassemblyjs/leb128": "1.11.1",
+ "@webassemblyjs/utf8": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
+ "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
+ "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-api-error": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/ieee754": "1.11.1",
+ "@webassemblyjs/leb128": "1.11.1",
+ "@webassemblyjs/utf8": "1.11.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
+ "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-import-assertions": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
+ "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+ "dev": true,
+ "peer": true,
+ "peerDependencies": {
+ "acorn": "^8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
+ "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "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/babel-plugin-styled-components": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
+ "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.16.0",
+ "@babel/helper-module-imports": "^7.16.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11",
+ "picomatch": "^2.3.0"
+ },
+ "peerDependencies": {
+ "styled-components": ">= 2"
+ }
+ },
+ "node_modules/babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw=="
+ },
+ "node_modules/big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/blueimp-canvas-to-blob": {
+ "version": "3.29.0",
+ "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
+ "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg=="
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
+ "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001449",
+ "electron-to-chromium": "^1.4.284",
+ "node-releases": "^2.0.8",
+ "update-browserslist-db": "^1.0.10"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001469",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz",
+ "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ],
+ "peer": true
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
+ "node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/compressorjs": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz",
+ "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
+ "dependencies": {
+ "blueimp-canvas-to-blob": "^3.29.0",
+ "is-blob": "^2.1.0"
+ }
+ },
+ "node_modules/compute-scroll-into-view": {
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
+ "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="
+ },
+ "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/copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "dependencies": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "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/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/direction": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
+ "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
+ "bin": {
+ "direction": "cli.js"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dnd-core": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+ "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+ "dependencies": {
+ "@react-dnd/asap": "^5.0.1",
+ "@react-dnd/invariant": "^4.0.1",
+ "redux": "^4.2.0"
+ }
+ },
+ "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.4.334",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz",
+ "integrity": "sha512-laZ1odk+TRen6q0GeyQx/JEkpD3iSZT7ewopCpKqg9bTjP1l8XRfU3Bg20CFjNPZkp5+NDBl3iqd4o/kPO+Vew==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
+ "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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/es-module-lexer": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
+ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz",
+ "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.11",
+ "@esbuild/android-arm64": "0.17.11",
+ "@esbuild/android-x64": "0.17.11",
+ "@esbuild/darwin-arm64": "0.17.11",
+ "@esbuild/darwin-x64": "0.17.11",
+ "@esbuild/freebsd-arm64": "0.17.11",
+ "@esbuild/freebsd-x64": "0.17.11",
+ "@esbuild/linux-arm": "0.17.11",
+ "@esbuild/linux-arm64": "0.17.11",
+ "@esbuild/linux-ia32": "0.17.11",
+ "@esbuild/linux-loong64": "0.17.11",
+ "@esbuild/linux-mips64el": "0.17.11",
+ "@esbuild/linux-ppc64": "0.17.11",
+ "@esbuild/linux-riscv64": "0.17.11",
+ "@esbuild/linux-s390x": "0.17.11",
+ "@esbuild/linux-x64": "0.17.11",
+ "@esbuild/netbsd-x64": "0.17.11",
+ "@esbuild/openbsd-x64": "0.17.11",
+ "@esbuild/sunos-x64": "0.17.11",
+ "@esbuild/win32-arm64": "0.17.11",
+ "@esbuild/win32-ia32": "0.17.11",
+ "@esbuild/win32-x64": "0.17.11"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/exenv": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+ "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/file-selector": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+ "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "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/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=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/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "node_modules/immer": {
+ "version": "9.0.19",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz",
+ "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==",
+ "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==",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/intl-messageformat": {
+ "version": "10.3.3",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz",
+ "integrity": "sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "@formatjs/fast-memoize": "2.0.1",
+ "@formatjs/icu-messageformat-parser": "2.3.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "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-blob": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz",
+ "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-hotkey": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
+ "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ=="
+ },
+ "node_modules/is-lite": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.9.2.tgz",
+ "integrity": "sha512-qZuxbaEiKLOKhX4sbHLfhFN9iA3YciuZLb37/DfXCpWnz8p7qNL2lwkpxYMXfjlS8eEEjpULPZxAUI8N6FYvYQ=="
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "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/loader-runner": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6.11.5"
+ }
+ },
+ "node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/localforage": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+ "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+ "dependencies": {
+ "lie": "3.1.1"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
+ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
+ "dev": true,
+ "peer": 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/parent-module": {
+ "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==",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "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-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/philliplm-react-modern-audio-player": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz",
+ "integrity": "sha512-2C/1lpQJmD0gWJMVt6k/QNZoakxHwxGhRFyw17OVAwbpXmptisr9aHDzT/VPFKoIr5qj+y1WajJ4lnghbfzx9Q==",
+ "dependencies": {
+ "@react-spectrum/layout": "^3.3.1",
+ "@react-spectrum/provider": "^3.4.1",
+ "@react-spectrum/theme-default": "^3.3.1",
+ "@react-spectrum/view": "^3.2.1",
+ "classnames": "^2.3.1",
+ "react-icons": "^4.4.0",
+ "styled-components": "^5.3.5",
+ "wavesurfer.js": "^6.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
+ "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "node_modules/prettier": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz",
+ "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "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/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-copy-to-clipboard": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
+ "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
+ "dependencies": {
+ "copy-to-clipboard": "^3.3.1",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": "^15.3.0 || 16 || 17 || 18"
+ }
+ },
+ "node_modules/react-dnd": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+ "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+ "dependencies": {
+ "@react-dnd/invariant": "^4.0.1",
+ "@react-dnd/shallowequal": "^4.0.1",
+ "dnd-core": "^16.0.1",
+ "fast-deep-equal": "^3.1.3",
+ "hoist-non-react-statics": "^3.3.2"
+ },
+ "peerDependencies": {
+ "@types/hoist-non-react-statics": ">= 3.3.1",
+ "@types/node": ">= 12",
+ "@types/react": ">= 16",
+ "react": ">= 16.14"
+ },
+ "peerDependenciesMeta": {
+ "@types/hoist-non-react-statics": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-dnd-html5-backend": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+ "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+ "dependencies": {
+ "dnd-core": "^16.0.1"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-draggable": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz",
+ "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==",
+ "dependencies": {
+ "clsx": "^1.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0",
+ "react-dom": ">= 16.3.0"
+ }
+ },
+ "node_modules/react-dropzone": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+ "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+ "dependencies": {
+ "attr-accept": "^2.2.2",
+ "file-selector": "^0.6.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
+ "node_modules/react-floater": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.6.tgz",
+ "integrity": "sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg==",
+ "dependencies": {
+ "deepmerge": "^4.2.2",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.8.2",
+ "popper.js": "^1.16.0",
+ "prop-types": "^15.8.1",
+ "react-proptype-conditional-require": "^1.0.4",
+ "tree-changes": "^0.9.1"
+ },
+ "peerDependencies": {
+ "react": "15 - 18",
+ "react-dom": "15 - 18"
+ }
+ },
+ "node_modules/react-floater/node_modules/is-lite": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
+ "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
+ },
+ "node_modules/react-grid-layout": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz",
+ "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==",
+ "dependencies": {
+ "clsx": "^1.1.1",
+ "lodash.isequal": "^4.0.0",
+ "prop-types": "^15.8.1",
+ "react-draggable": "^4.0.0",
+ "react-resizable": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0",
+ "react-dom": ">= 16.3.0"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
+ "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/react-intersection-observer": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz",
+ "integrity": "sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==",
+ "peerDependencies": {
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "node_modules/react-joyride": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.5.4.tgz",
+ "integrity": "sha512-CLV1Ju79iRKIP/KqsEX05nSxMBzT41BhPAyNrTdBQWEYAkpyo6iiCPelk6No2W8iPgw373JKk2cK7AQ5Q3lFew==",
+ "dependencies": {
+ "deepmerge": "^4.3.1",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.9.2",
+ "prop-types": "^15.8.1",
+ "react-floater": "^0.7.6",
+ "react-is": "^16.13.1",
+ "scroll": "^3.0.1",
+ "scrollparent": "^2.0.1",
+ "tree-changes": "^0.9.2"
+ },
+ "peerDependencies": {
+ "react": "15 - 18",
+ "react-dom": "15 - 18"
+ }
+ },
+ "node_modules/react-joyride/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/react-masonry-css": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz",
+ "integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==",
+ "peerDependencies": {
+ "react": ">=16.0.0"
+ }
+ },
+ "node_modules/react-proptype-conditional-require": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz",
+ "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw=="
+ },
+ "node_modules/react-redux": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
+ "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8 || ^17.0 || ^18.0",
+ "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react-native": ">=0.59",
+ "redux": "^4"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-resizable": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
+ "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
+ "dependencies": {
+ "prop-types": "15.x",
+ "react-draggable": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3"
+ }
+ },
+ "node_modules/react-resize-detector": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.0.4.tgz",
+ "integrity": "sha512-ln9pMAob8y8mc9UI4aZuuWFiyMqBjnTs/sxe9Vs9dPXUjwCTeKK1FP8I75ufnb/2mEEZXG6wOo/fjMcBRRuAXw==",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz",
+ "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==",
+ "dependencies": {
+ "@remix-run/router": "1.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz",
+ "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==",
+ "dependencies": {
+ "@remix-run/router": "1.4.0",
+ "react-router": "6.9.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-toastify": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.2.tgz",
+ "integrity": "sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==",
+ "dependencies": {
+ "clsx": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
+ "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/react-virtuoso": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.3.3.tgz",
+ "integrity": "sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16 || >=17 || >= 18",
+ "react-dom": ">=16 || >=17 || >= 18"
+ }
+ },
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "peerDependencies": {
+ "redux": "^4"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "node_modules/reselect": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
+ "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dependencies": {
+ "is-core-module": "^2.9.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==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz",
+ "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "peer": true
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/scroll": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz",
+ "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg=="
+ },
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "2.2.31",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
+ "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
+ "dependencies": {
+ "compute-scroll-into-view": "^1.0.20"
+ }
+ },
+ "node_modules/scrollparent": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz",
+ "integrity": "sha512-HSdN78VMvFCSGCkh0oYX/tY4R3P1DW61f8+TeZZ4j2VLgfwvw0bpRSOv4PCVKisktIwbzHCfZsx+rLbbDBqIBA=="
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+ "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
+ "node_modules/short-unique-id": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz",
+ "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==",
+ "bin": {
+ "short-unique-id": "bin/short-unique-id",
+ "suid": "bin/short-unique-id"
+ }
+ },
+ "node_modules/slate": {
+ "version": "0.91.4",
+ "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz",
+ "integrity": "sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==",
+ "dependencies": {
+ "immer": "^9.0.6",
+ "is-plain-object": "^5.0.0",
+ "tiny-warning": "^1.0.3"
+ }
+ },
+ "node_modules/slate-history": {
+ "version": "0.86.0",
+ "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.86.0.tgz",
+ "integrity": "sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og==",
+ "dependencies": {
+ "is-plain-object": "^5.0.0"
+ },
+ "peerDependencies": {
+ "slate": ">=0.65.3"
+ }
+ },
+ "node_modules/slate-react": {
+ "version": "0.91.11",
+ "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.91.11.tgz",
+ "integrity": "sha512-2nS29rc2kuTTJrEUOXGyTkFATmTEw/R9KuUXadUYiz+UVwuFOUMnBKuwJWyuIBOsFipS+06SkIayEf5CKdARRQ==",
+ "dependencies": {
+ "@juggle/resize-observer": "^3.4.0",
+ "@types/is-hotkey": "^0.1.1",
+ "@types/lodash": "^4.14.149",
+ "direction": "^1.0.3",
+ "is-hotkey": "^0.1.6",
+ "is-plain-object": "^5.0.0",
+ "lodash": "^4.17.4",
+ "scroll-into-view-if-needed": "^2.2.20",
+ "tiny-invariant": "1.0.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0",
+ "slate": ">=0.65.3"
+ }
+ },
+ "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.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/styled-components": {
+ "version": "5.3.9",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz",
+ "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^1.1.0",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0",
+ "react-is": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
+ "node_modules/stylis": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
+ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.16.6",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz",
+ "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.2",
+ "acorn": "^8.5.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
+ "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^3.1.1",
+ "serialize-javascript": "^6.0.1",
+ "terser": "^5.16.5"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
+ "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+ },
+ "node_modules/tree-changes": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz",
+ "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==",
+ "dependencies": {
+ "@gilbarbara/deep-equal": "^0.1.1",
+ "is-lite": "^0.8.2"
+ }
+ },
+ "node_modules/tree-changes/node_modules/is-lite": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
+ "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
+ },
+ "node_modules/tslib": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.0.tgz",
+ "integrity": "sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.17.5",
+ "postcss": "^8.4.21",
+ "resolve": "^1.22.1",
+ "rollup": "^3.18.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/watchpack": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
+ "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/wavesurfer.js": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz",
+ "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw=="
+ },
+ "node_modules/webpack": {
+ "version": "5.76.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz",
+ "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.3",
+ "@types/estree": "^0.0.51",
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/wasm-edit": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1",
+ "acorn": "^8.7.1",
+ "acorn-import-assertions": "^1.7.6",
+ "browserslist": "^4.14.5",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.10.0",
+ "es-module-lexer": "^0.9.0",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.9",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^3.1.0",
+ "tapable": "^2.1.1",
+ "terser-webpack-plugin": "^5.1.3",
+ "watchpack": "^2.4.0",
+ "webpack-sources": "^3.2.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-sources": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/worker-loader": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
+ "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "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"
+ }
+ }
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "requires": {
+ "@babel/highlight": "^7.18.6"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz",
+ "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==",
+ "requires": {
+ "@babel/types": "^7.21.3",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
+ "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg=="
+ },
+ "@babel/helper-function-name": {
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
+ "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
+ "requires": {
+ "@babel/template": "^7.20.7",
+ "@babel/types": "^7.21.0"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw=="
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
+ },
+ "@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz",
+ "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ=="
+ },
+ "@babel/runtime": {
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
+ "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.11"
+ }
+ },
+ "@babel/template": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
+ "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz",
+ "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==",
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.21.3",
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-function-name": "^7.21.0",
+ "@babel/helper-hoist-variables": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/parser": "^7.21.3",
+ "@babel/types": "^7.21.3",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz",
+ "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@emotion/babel-plugin": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
+ "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.0",
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/serialize": "^1.1.1",
+ "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.1.3"
+ }
+ },
+ "@emotion/cache": {
+ "version": "11.10.5",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz",
+ "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==",
+ "requires": {
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/sheet": "^1.2.1",
+ "@emotion/utils": "^1.2.0",
+ "@emotion/weak-memoize": "^0.3.0",
+ "stylis": "4.1.3"
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
+ "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
+ },
+ "@emotion/is-prop-valid": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz",
+ "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==",
+ "requires": {
+ "@emotion/memoize": "^0.8.0"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
+ "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
+ },
+ "@emotion/react": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz",
+ "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.10.6",
+ "@emotion/cache": "^11.10.5",
+ "@emotion/serialize": "^1.1.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0",
+ "@emotion/utils": "^1.2.0",
+ "@emotion/weak-memoize": "^0.3.0",
+ "hoist-non-react-statics": "^3.3.1"
+ }
+ },
+ "@emotion/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==",
+ "requires": {
+ "@emotion/hash": "^0.9.0",
+ "@emotion/memoize": "^0.8.0",
+ "@emotion/unitless": "^0.8.0",
+ "@emotion/utils": "^1.2.0",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@emotion/sheet": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz",
+ "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA=="
+ },
+ "@emotion/styled": {
+ "version": "11.10.6",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz",
+ "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.10.6",
+ "@emotion/is-prop-valid": "^1.2.0",
+ "@emotion/serialize": "^1.1.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0",
+ "@emotion/utils": "^1.2.0"
+ }
+ },
+ "@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
+ "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
+ },
+ "@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz",
+ "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==",
+ "requires": {}
+ },
+ "@emotion/utils": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz",
+ "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
+ "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
+ },
+ "@esbuild/android-arm": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz",
+ "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz",
+ "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz",
+ "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz",
+ "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz",
+ "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz",
+ "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz",
+ "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz",
+ "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz",
+ "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ia32": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz",
+ "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz",
+ "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-mips64el": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz",
+ "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ppc64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz",
+ "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-riscv64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz",
+ "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-s390x": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz",
+ "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz",
+ "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/netbsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz",
+ "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/openbsd-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz",
+ "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/sunos-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz",
+ "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-arm64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz",
+ "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-ia32": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz",
+ "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-x64": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz",
+ "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@formatjs/ecma402-abstract": {
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
+ "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
+ "requires": {
+ "@formatjs/intl-localematcher": "0.2.32",
+ "tslib": "^2.4.0"
+ }
+ },
+ "@formatjs/fast-memoize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz",
+ "integrity": "sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "@formatjs/icu-messageformat-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz",
+ "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==",
+ "requires": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "@formatjs/icu-skeleton-parser": "1.3.18",
+ "tslib": "^2.4.0"
+ }
+ },
+ "@formatjs/icu-skeleton-parser": {
+ "version": "1.3.18",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
+ "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
+ "requires": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "@formatjs/intl-localematcher": {
+ "version": "0.2.32",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
+ "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "@gilbarbara/deep-equal": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz",
+ "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA=="
+ },
+ "@internationalized/date": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz",
+ "integrity": "sha512-wjeur7K4AecT+YwoBmBXQ/+n5lP69tuZc34hw09s44EozZK7FZHSyfPvRp5/xEb2D6abLboskDY4jG+Nt0TNUQ==",
+ "requires": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@internationalized/message": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.0.tgz",
+ "integrity": "sha512-Oo5m70FcBdADf7G8NkUffVSfuCdeAYVfsvNjZDi9ELpjvkc4YNJVTHt/NyTI9K7FgAVoELxiP9YmN0sJ+HNHYQ==",
+ "requires": {
+ "@swc/helpers": "^0.4.14",
+ "intl-messageformat": "^10.1.0"
+ }
+ },
+ "@internationalized/number": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.2.0.tgz",
+ "integrity": "sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==",
+ "requires": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@internationalized/string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.1.0.tgz",
+ "integrity": "sha512-TJQKiyUb+wyAfKF59UNeZ/kELMnkxyecnyPCnBI1ma4NaXReJW+7Cc2mObXAqraIBJUVv7rgI46RLKrLgi35ng==",
+ "requires": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "requires": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+ },
+ "@jridgewell/source-map": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+ "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "requires": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "@juggle/resize-observer": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
+ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
+ },
+ "@mui/base": {
+ "version": "5.0.0-alpha.121",
+ "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz",
+ "integrity": "sha512-8nJRY76UqlJV+q/Yzo0tgGfPWEOa+4N9rjO81fMmcJqP0I6m54hLDXsjvMg4tvelY5eKHXUK6Tb7en+GHfTqZA==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@emotion/is-prop-valid": "^1.2.0",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "@popperjs/core": "^2.11.6",
+ "clsx": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ }
+ },
+ "@mui/core-downloads-tracker": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.13.tgz",
+ "integrity": "sha512-lx+GXBR9h/ApZsEP728tl0pyZyuajto+VnBgsoAzw1d5+CbmOo8ZWieKwVUGxZlPT1wMYNUYS5NtKzCli0xYjw=="
+ },
+ "@mui/icons-material": {
+ "version": "5.11.11",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz",
+ "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==",
+ "requires": {
+ "@babel/runtime": "^7.21.0"
+ }
+ },
+ "@mui/material": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.13.tgz",
+ "integrity": "sha512-2CnSj43F+159LbGmTLLQs5xbGYMiYlpTByQhP7c7cMX6opbScctBFE1PuyElpAmwW8Ag9ysfZH1d1MFAmJQkjg==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/base": "5.0.0-alpha.121",
+ "@mui/core-downloads-tracker": "^5.11.13",
+ "@mui/system": "^5.11.13",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "@types/react-transition-group": "^4.4.5",
+ "clsx": "^1.2.1",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0",
+ "react-transition-group": "^4.4.5"
+ }
+ },
+ "@mui/private-theming": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz",
+ "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/utils": "^5.11.13",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "@mui/styled-engine": {
+ "version": "5.11.11",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz",
+ "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@emotion/cache": "^11.10.5",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "@mui/system": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.13.tgz",
+ "integrity": "sha512-OWP0Alp6C8ufnGm9+CZcl3d+OoRXL2PnrRT5ohaMLxvGL9OfNcL2t4JOjMmA0k1UAGd6E/Ygbu5lEPrZSDlvCg==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@mui/private-theming": "^5.11.13",
+ "@mui/styled-engine": "^5.11.11",
+ "@mui/types": "^7.2.3",
+ "@mui/utils": "^5.11.13",
+ "clsx": "^1.2.1",
+ "csstype": "^3.1.1",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "@mui/types": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz",
+ "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==",
+ "requires": {}
+ },
+ "@mui/utils": {
+ "version": "5.11.13",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz",
+ "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "@types/prop-types": "^15.7.5",
+ "@types/react-is": "^16.7.1 || ^17.0.0",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.2.0"
+ }
+ },
+ "@popperjs/core": {
+ "version": "2.11.6",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
+ "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
+ },
+ "@react-aria/focus": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.11.0.tgz",
+ "integrity": "sha512-yPuWs9bAR9CMfIwyOPm2oXLPF19gNkUqW+ozSPhWbLMTEa8Ma09eHW1br4xbN+4ONOm/dCJsIkxTNPUkiLdQoA==",
+ "requires": {
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-aria/i18n": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.7.0.tgz",
+ "integrity": "sha512-PZCWmhO9mJvelwiYlsXLY6W4L2o+oza3xnDx0cZDVqp/Hf+OwMAPHWtZsFRTKdjk4TaOPB/ISc9HknWn6UpY4w==",
+ "requires": {
+ "@internationalized/date": "^3.1.0",
+ "@internationalized/message": "^3.1.0",
+ "@internationalized/number": "^3.2.0",
+ "@internationalized/string": "^3.1.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-aria/interactions": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.14.0.tgz",
+ "integrity": "sha512-e1Tkr0UTuYFpV21PJrXy7jEY542Vl+C2Fo70oukZ1fN+wtfQkzodsTIzyepXb7kVMGmC34wDisMJUrksVkfY2w==",
+ "requires": {
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-aria/overlays": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.13.0.tgz",
+ "integrity": "sha512-hRZyhAYzrlCcEWQ2k2jP24b0wc5/355Xl5w5FZHRmPeU1U4XlFwKX/eFlBs/li9Sprm1bTDXrCY480Kl6UsKDA==",
+ "requires": {
+ "@react-aria/focus": "^3.11.0",
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-aria/visually-hidden": "^3.7.0",
+ "@react-stately/overlays": "^3.5.0",
+ "@react-types/button": "^3.7.1",
+ "@react-types/overlays": "^3.7.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-aria/ssr": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.5.0.tgz",
+ "integrity": "sha512-h0MJdSWOd1qObLnJ8mprU31wI8tmKFJMuwT22MpWq6psisOOZaga6Ml4u6Ee6M6duWWISjXvqO4Sb/J0PBA+nQ==",
+ "requires": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-aria/utils": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.15.0.tgz",
+ "integrity": "sha512-aJZBG++iz1UwTW5gXFaHicKju4p0MPhAyBTcf2awHYWeTUUslDjJcEnNg7kjBYZBOrOSlA2rAt7/7C5CCURQPg==",
+ "requires": {
+ "@react-aria/ssr": "^3.5.0",
+ "@react-stately/utils": "^3.6.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-aria/visually-hidden": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.7.0.tgz",
+ "integrity": "sha512-v/0ujJ67H6LjwY8J7mIGPVB1K8suBArLV+w8UGdX/wFXRL7H4r2fiqlrwAElWSmNbhDQl5BDm/Zh/ub9jB9yzA==",
+ "requires": {
+ "@react-aria/interactions": "^3.14.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-dnd/asap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+ "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+ },
+ "@react-dnd/invariant": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+ "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+ },
+ "@react-dnd/shallowequal": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+ },
+ "@react-spectrum/layout": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.5.0.tgz",
+ "integrity": "sha512-Yjy0uvGUHjczl44PQlMqOEBoJIe4coYwDQCoVl20YrTzGiMdwLV24Hs9uqK3I6yKmAoNxTehe0CdOVgwuVRF9g==",
+ "requires": {
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/layout": "^3.3.6",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-spectrum/provider": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.7.0.tgz",
+ "integrity": "sha512-6Uch60R5PKJC+ZY3VweVadaCoaIj6Nd85TKfFH4jf8NEzXv5CqjwUzwV5aQ6u3k1K24Ves7RpLlK53HW1wUm6w==",
+ "requires": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/overlays": "^3.13.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/provider": "^3.6.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-spectrum/theme-default": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.0.tgz",
+ "integrity": "sha512-DagwngJ9T7yGgtdTwKj9LaHbg3AWLW2/ILJK92xbPj/yAAEVXJ8vhiP9Tg3W3mZMsxBCNwTVLyOLheu7d+D8og==",
+ "requires": {
+ "@react-types/provider": "^3.6.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-spectrum/utils": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.9.0.tgz",
+ "integrity": "sha512-I1JKwiyLE54mAT7Z2wadVKoai1Q60QFg4XNuYUwk8TwWAEz7DUUHucntHAND6dqFpVIMK1Rk9lcZBft43FF2BQ==",
+ "requires": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/ssr": "^3.5.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-types/shared": "^3.17.0",
+ "@swc/helpers": "^0.4.14",
+ "clsx": "^1.1.1"
+ }
+ },
+ "@react-spectrum/view": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.5.0.tgz",
+ "integrity": "sha512-Zzsy2S6gT14vtli52tjM5VP1EdMOP+bppNCmwX+saUJuxaEdj6oYjP89quE4HwALzYceOfmXIuGDyEVeDcuSsQ==",
+ "requires": {
+ "@react-aria/i18n": "^3.7.0",
+ "@react-aria/utils": "^3.15.0",
+ "@react-spectrum/utils": "^3.9.0",
+ "@react-types/shared": "^3.17.0",
+ "@react-types/view": "^3.4.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-stately/overlays": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.5.0.tgz",
+ "integrity": "sha512-r+U/G0Y4tCfI5wyBeIu+hmcZVRN8ChoK2zM1srPH9nDKsijQard2goX+9YmKng2LJ01Re/P6F8S8jYbpfEdLfQ==",
+ "requires": {
+ "@react-stately/utils": "^3.6.0",
+ "@react-types/overlays": "^3.7.0",
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-stately/utils": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.6.0.tgz",
+ "integrity": "sha512-rptF7iUWDrquaYvBAS4QQhOBQyLBncDeHF03WnHXAxnuPJXNcr9cXJtjJPGCs036ZB8Q2hc9BGG5wNyMkF5v+Q==",
+ "requires": {
+ "@swc/helpers": "^0.4.14"
+ }
+ },
+ "@react-types/button": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.7.1.tgz",
+ "integrity": "sha512-c+8xjmqWSjI5/mEHVLbVSp0eh/z2UU8Ga+wqjbEUZUjm8uopYj1PaCAwZ7YgcAebyQrL/21GyjK6tFHKzuUdJQ==",
+ "requires": {
+ "@react-types/shared": "^3.17.0"
+ }
+ },
+ "@react-types/layout": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.6.tgz",
+ "integrity": "sha512-8YF7nb+0AtSDRj+rejMRS5djWpqAf5Q6sJzgdccn42JQxW7jvxoKqN0pCRPqpMzQqae5hOsYYpVNfuIHwUUkUA==",
+ "requires": {
+ "@react-types/shared": "^3.17.0"
+ }
+ },
+ "@react-types/overlays": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.7.0.tgz",
+ "integrity": "sha512-LstucncZ8dM+xJYEijI1V6jGH20w5XO/T60r7JTrgQElMC86phPeoWkMTN4c2lsRikybolDbvXL6XsF76YO56A==",
+ "requires": {
+ "@react-types/shared": "^3.17.0"
+ }
+ },
+ "@react-types/provider": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.6.0.tgz",
+ "integrity": "sha512-29+X5yMr1uHUc/BmFoyBqfWyMT3YGZJffaprtGGRQkCuwMbqoA8GI+rGJzNAMAD/1cPNo237PAGsi4NAup5tAQ==",
+ "requires": {
+ "@react-types/shared": "^3.17.0"
+ }
+ },
+ "@react-types/shared": {
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.17.0.tgz",
+ "integrity": "sha512-1SNZ/RhVrrQ1e6yE0bPV7d5Sfp+Uv0dfUEhwF9MAu2v5msu7AMewnsiojKNA0QA6Ing1gpDLjHCxtayQfuxqcg==",
+ "requires": {}
+ },
+ "@react-types/view": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.0.tgz",
+ "integrity": "sha512-lXDLbOYRFwo8hxrSDIYUmnxOGZlHBDVEFe1ZKAXJ+qPwntzxhzUeAuddTiMDhEETKgTclmlIsJnwz+HNv6dhSQ==",
+ "requires": {
+ "@react-types/shared": "^3.17.0"
+ }
+ },
+ "@reduxjs/toolkit": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
+ "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==",
+ "requires": {
+ "immer": "^9.0.16",
+ "redux": "^4.2.0",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.7"
+ }
+ },
+ "@remix-run/router": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz",
+ "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q=="
+ },
+ "@swc/core": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz",
+ "integrity": "sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==",
+ "dev": true,
+ "requires": {
+ "@swc/core-darwin-arm64": "1.3.41",
+ "@swc/core-darwin-x64": "1.3.41",
+ "@swc/core-linux-arm-gnueabihf": "1.3.41",
+ "@swc/core-linux-arm64-gnu": "1.3.41",
+ "@swc/core-linux-arm64-musl": "1.3.41",
+ "@swc/core-linux-x64-gnu": "1.3.41",
+ "@swc/core-linux-x64-musl": "1.3.41",
+ "@swc/core-win32-arm64-msvc": "1.3.41",
+ "@swc/core-win32-ia32-msvc": "1.3.41",
+ "@swc/core-win32-x64-msvc": "1.3.41"
+ }
+ },
+ "@swc/core-darwin-arm64": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.41.tgz",
+ "integrity": "sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-darwin-x64": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.41.tgz",
+ "integrity": "sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-arm-gnueabihf": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.41.tgz",
+ "integrity": "sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-arm64-gnu": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.41.tgz",
+ "integrity": "sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-arm64-musl": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.41.tgz",
+ "integrity": "sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-x64-gnu": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.41.tgz",
+ "integrity": "sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-x64-musl": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.41.tgz",
+ "integrity": "sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-win32-arm64-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.41.tgz",
+ "integrity": "sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-win32-ia32-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.41.tgz",
+ "integrity": "sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-win32-x64-msvc": {
+ "version": "1.3.41",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.41.tgz",
+ "integrity": "sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/helpers": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
+ "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "@types/eslint": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz",
+ "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "@types/eslint-scope": {
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
+ "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.51",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
+ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+ "dev": true,
+ "peer": true
+ },
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "@types/is-hotkey": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz",
+ "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ=="
+ },
+ "@types/json-schema": {
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "dev": true
+ },
+ "@types/lodash": {
+ "version": "4.14.191",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
+ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ=="
+ },
+ "@types/node": {
+ "version": "18.15.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz",
+ "integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==",
+ "devOptional": true,
+ "peer": true
+ },
+ "@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
+ },
+ "@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ },
+ "@types/react": {
+ "version": "18.0.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
+ "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-copy-to-clipboard": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
+ "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-dom": {
+ "version": "18.0.11",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz",
+ "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==",
+ "devOptional": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-grid-layout": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz",
+ "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-is": {
+ "version": "17.0.3",
+ "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
+ "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
+ },
+ "@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=="
+ },
+ "@vitejs/plugin-react-swc": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
+ "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==",
+ "dev": true,
+ "requires": {
+ "@swc/core": "^1.3.35"
+ }
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
+ "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/helper-numbers": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
+ "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+ "dev": true,
+ "peer": true
+ },
+ "@webassemblyjs/helper-api-error": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
+ "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+ "dev": true,
+ "peer": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
+ "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+ "dev": true,
+ "peer": true
+ },
+ "@webassemblyjs/helper-numbers": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
+ "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/floating-point-hex-parser": "1.11.1",
+ "@webassemblyjs/helper-api-error": "1.11.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
+ "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+ "dev": true,
+ "peer": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
+ "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1"
+ }
+ },
+ "@webassemblyjs/ieee754": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
+ "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
+ "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/utf8": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
+ "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+ "dev": true,
+ "peer": true
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
+ "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/helper-wasm-section": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1",
+ "@webassemblyjs/wasm-opt": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1",
+ "@webassemblyjs/wast-printer": "1.11.1"
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
+ "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/ieee754": "1.11.1",
+ "@webassemblyjs/leb128": "1.11.1",
+ "@webassemblyjs/utf8": "1.11.1"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
+ "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-buffer": "1.11.1",
+ "@webassemblyjs/wasm-gen": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1"
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
+ "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/helper-api-error": "1.11.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
+ "@webassemblyjs/ieee754": "1.11.1",
+ "@webassemblyjs/leb128": "1.11.1",
+ "@webassemblyjs/utf8": "1.11.1"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
+ "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.11.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true,
+ "peer": true
+ },
+ "@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true,
+ "peer": true
+ },
+ "acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "peer": true
+ },
+ "acorn-import-assertions": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
+ "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+ "dev": true,
+ "peer": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+ },
+ "axios": {
+ "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",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "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==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ }
+ },
+ "babel-plugin-styled-components": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz",
+ "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.16.0",
+ "@babel/helper-module-imports": "^7.16.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11",
+ "picomatch": "^2.3.0"
+ }
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw=="
+ },
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
+ "blueimp-canvas-to-blob": {
+ "version": "3.29.0",
+ "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
+ "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg=="
+ },
+ "browserslist": {
+ "version": "4.21.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
+ "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001449",
+ "electron-to-chromium": "^1.4.284",
+ "node-releases": "^2.0.8",
+ "update-browserslist-db": "^1.0.10"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "peer": true
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
+ },
+ "camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001469",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz",
+ "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==",
+ "dev": true,
+ "peer": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
+ }
+ }
+ },
+ "chrome-trace-event": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
+ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+ "dev": true,
+ "peer": true
+ },
+ "classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
+ "clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true,
+ "peer": true
+ },
+ "compressorjs": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz",
+ "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
+ "requires": {
+ "blueimp-canvas-to-blob": "^3.29.0",
+ "is-blob": "^2.1.0"
+ }
+ },
+ "compute-scroll-into-view": {
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
+ "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="
+ },
+ "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=="
+ },
+ "copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "requires": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "requires": {
+ "@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"
+ }
+ },
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
+ },
+ "css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+ },
+ "direction": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
+ "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="
+ },
+ "dnd-core": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+ "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+ "requires": {
+ "@react-dnd/asap": "^5.0.1",
+ "@react-dnd/invariant": "^4.0.1",
+ "redux": "^4.2.0"
+ }
+ },
+ "dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "electron-to-chromium": {
+ "version": "1.4.334",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz",
+ "integrity": "sha512-laZ1odk+TRen6q0GeyQx/JEkpD3iSZT7ewopCpKqg9bTjP1l8XRfU3Bg20CFjNPZkp5+NDBl3iqd4o/kPO+Vew==",
+ "dev": true,
+ "peer": true
+ },
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "enhanced-resolve": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
+ "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ }
+ },
+ "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==",
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-module-lexer": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
+ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
+ "dev": true,
+ "peer": true
+ },
+ "esbuild": {
+ "version": "0.17.11",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz",
+ "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==",
+ "dev": true,
+ "requires": {
+ "@esbuild/android-arm": "0.17.11",
+ "@esbuild/android-arm64": "0.17.11",
+ "@esbuild/android-x64": "0.17.11",
+ "@esbuild/darwin-arm64": "0.17.11",
+ "@esbuild/darwin-x64": "0.17.11",
+ "@esbuild/freebsd-arm64": "0.17.11",
+ "@esbuild/freebsd-x64": "0.17.11",
+ "@esbuild/linux-arm": "0.17.11",
+ "@esbuild/linux-arm64": "0.17.11",
+ "@esbuild/linux-ia32": "0.17.11",
+ "@esbuild/linux-loong64": "0.17.11",
+ "@esbuild/linux-mips64el": "0.17.11",
+ "@esbuild/linux-ppc64": "0.17.11",
+ "@esbuild/linux-riscv64": "0.17.11",
+ "@esbuild/linux-s390x": "0.17.11",
+ "@esbuild/linux-x64": "0.17.11",
+ "@esbuild/netbsd-x64": "0.17.11",
+ "@esbuild/openbsd-x64": "0.17.11",
+ "@esbuild/sunos-x64": "0.17.11",
+ "@esbuild/win32-arm64": "0.17.11",
+ "@esbuild/win32-ia32": "0.17.11",
+ "@esbuild/win32-x64": "0.17.11"
+ }
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "peer": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "peer": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "peer": true
+ },
+ "events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "dev": true,
+ "peer": true
+ },
+ "exenv": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+ "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "file-selector": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+ "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
+ "follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
+ },
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true,
+ "peer": true
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+ },
+ "graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "peer": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
+ },
+ "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==",
+ "requires": {
+ "react-is": "^16.7.0"
+ },
+ "dependencies": {
+ "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=="
+ }
+ }
+ },
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "immer": {
+ "version": "9.0.19",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz",
+ "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ=="
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "intl-messageformat": {
+ "version": "10.3.3",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz",
+ "integrity": "sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==",
+ "requires": {
+ "@formatjs/ecma402-abstract": "1.14.3",
+ "@formatjs/fast-memoize": "2.0.1",
+ "@formatjs/icu-messageformat-parser": "2.3.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+ },
+ "is-blob": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz",
+ "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw=="
+ },
+ "is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-hotkey": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
+ "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ=="
+ },
+ "is-lite": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.9.2.tgz",
+ "integrity": "sha512-qZuxbaEiKLOKhX4sbHLfhFN9iA3YciuZLb37/DfXCpWnz8p7qNL2lwkpxYMXfjlS8eEEjpULPZxAUI8N6FYvYQ=="
+ },
+ "is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
+ },
+ "jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "peer": true
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+ },
+ "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=="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true
+ },
+ "lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "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=="
+ },
+ "loader-runner": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true,
+ "peer": true
+ },
+ "loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
+ "localforage": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+ "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+ "requires": {
+ "lie": "3.1.1"
+ }
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "peer": true
+ },
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
+ "moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "peer": true
+ },
+ "node-releases": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
+ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
+ "dev": true,
+ "peer": true
+ },
+ "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=="
+ },
+ "parent-module": {
+ "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==",
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "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==",
+ "requires": {
+ "@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"
+ }
+ },
+ "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=="
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
+ },
+ "philliplm-react-modern-audio-player": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz",
+ "integrity": "sha512-2C/1lpQJmD0gWJMVt6k/QNZoakxHwxGhRFyw17OVAwbpXmptisr9aHDzT/VPFKoIr5qj+y1WajJ4lnghbfzx9Q==",
+ "requires": {
+ "@react-spectrum/layout": "^3.3.1",
+ "@react-spectrum/provider": "^3.4.1",
+ "@react-spectrum/theme-default": "^3.3.1",
+ "@react-spectrum/view": "^3.2.1",
+ "classnames": "^2.3.1",
+ "react-icons": "^4.4.0",
+ "styled-components": "^5.3.5",
+ "wavesurfer.js": "^6.2.0"
+ }
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+ },
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
+ "postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "prettier": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz",
+ "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==",
+ "dev": true
+ },
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ },
+ "dependencies": {
+ "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=="
+ }
+ }
+ },
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "react-copy-to-clipboard": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
+ "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
+ "requires": {
+ "copy-to-clipboard": "^3.3.1",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "react-dnd": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+ "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+ "requires": {
+ "@react-dnd/invariant": "^4.0.1",
+ "@react-dnd/shallowequal": "^4.0.1",
+ "dnd-core": "^16.0.1",
+ "fast-deep-equal": "^3.1.3",
+ "hoist-non-react-statics": "^3.3.2"
+ }
+ },
+ "react-dnd-html5-backend": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+ "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+ "requires": {
+ "dnd-core": "^16.0.1"
+ }
+ },
+ "react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ }
+ },
+ "react-draggable": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz",
+ "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==",
+ "requires": {
+ "clsx": "^1.1.1",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "react-dropzone": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+ "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+ "requires": {
+ "attr-accept": "^2.2.2",
+ "file-selector": "^0.6.0",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "react-floater": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.6.tgz",
+ "integrity": "sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg==",
+ "requires": {
+ "deepmerge": "^4.2.2",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.8.2",
+ "popper.js": "^1.16.0",
+ "prop-types": "^15.8.1",
+ "react-proptype-conditional-require": "^1.0.4",
+ "tree-changes": "^0.9.1"
+ },
+ "dependencies": {
+ "is-lite": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
+ "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
+ }
+ }
+ },
+ "react-grid-layout": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz",
+ "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==",
+ "requires": {
+ "clsx": "^1.1.1",
+ "lodash.isequal": "^4.0.0",
+ "prop-types": "^15.8.1",
+ "react-draggable": "^4.0.0",
+ "react-resizable": "^3.0.4"
+ }
+ },
+ "react-icons": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
+ "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
+ "requires": {}
+ },
+ "react-intersection-observer": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz",
+ "integrity": "sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==",
+ "requires": {}
+ },
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "react-joyride": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.5.4.tgz",
+ "integrity": "sha512-CLV1Ju79iRKIP/KqsEX05nSxMBzT41BhPAyNrTdBQWEYAkpyo6iiCPelk6No2W8iPgw373JKk2cK7AQ5Q3lFew==",
+ "requires": {
+ "deepmerge": "^4.3.1",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.9.2",
+ "prop-types": "^15.8.1",
+ "react-floater": "^0.7.6",
+ "react-is": "^16.13.1",
+ "scroll": "^3.0.1",
+ "scrollparent": "^2.0.1",
+ "tree-changes": "^0.9.2"
+ },
+ "dependencies": {
+ "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=="
+ }
+ }
+ },
+ "react-masonry-css": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz",
+ "integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==",
+ "requires": {}
+ },
+ "react-proptype-conditional-require": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz",
+ "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw=="
+ },
+ "react-redux": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
+ "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ }
+ },
+ "react-resizable": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
+ "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
+ "requires": {
+ "prop-types": "15.x",
+ "react-draggable": "^4.0.3"
+ }
+ },
+ "react-resize-detector": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.0.4.tgz",
+ "integrity": "sha512-ln9pMAob8y8mc9UI4aZuuWFiyMqBjnTs/sxe9Vs9dPXUjwCTeKK1FP8I75ufnb/2mEEZXG6wOo/fjMcBRRuAXw==",
+ "requires": {
+ "lodash": "^4.17.21"
+ }
+ },
+ "react-router": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz",
+ "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==",
+ "requires": {
+ "@remix-run/router": "1.4.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz",
+ "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==",
+ "requires": {
+ "@remix-run/router": "1.4.0",
+ "react-router": "6.9.0"
+ }
+ },
+ "react-toastify": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.2.tgz",
+ "integrity": "sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==",
+ "requires": {
+ "clsx": "^1.1.1"
+ }
+ },
+ "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==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
+ "react-virtuoso": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.3.3.tgz",
+ "integrity": "sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==",
+ "requires": {}
+ },
+ "redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "requires": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "requires": {}
+ },
+ "regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "reselect": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
+ "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
+ },
+ "resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "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=="
+ },
+ "rollup": {
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz",
+ "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "peer": true
+ },
+ "scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "schema-utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+ "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ }
+ },
+ "scroll": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz",
+ "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg=="
+ },
+ "scroll-into-view-if-needed": {
+ "version": "2.2.31",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
+ "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
+ "requires": {
+ "compute-scroll-into-view": "^1.0.20"
+ }
+ },
+ "scrollparent": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz",
+ "integrity": "sha512-HSdN78VMvFCSGCkh0oYX/tY4R3P1DW61f8+TeZZ4j2VLgfwvw0bpRSOv4PCVKisktIwbzHCfZsx+rLbbDBqIBA=="
+ },
+ "serialize-javascript": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+ "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
+ "short-unique-id": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz",
+ "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw=="
+ },
+ "slate": {
+ "version": "0.91.4",
+ "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz",
+ "integrity": "sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==",
+ "requires": {
+ "immer": "^9.0.6",
+ "is-plain-object": "^5.0.0",
+ "tiny-warning": "^1.0.3"
+ }
+ },
+ "slate-history": {
+ "version": "0.86.0",
+ "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.86.0.tgz",
+ "integrity": "sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og==",
+ "requires": {
+ "is-plain-object": "^5.0.0"
+ }
+ },
+ "slate-react": {
+ "version": "0.91.11",
+ "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.91.11.tgz",
+ "integrity": "sha512-2nS29rc2kuTTJrEUOXGyTkFATmTEw/R9KuUXadUYiz+UVwuFOUMnBKuwJWyuIBOsFipS+06SkIayEf5CKdARRQ==",
+ "requires": {
+ "@juggle/resize-observer": "^3.4.0",
+ "@types/is-hotkey": "^0.1.1",
+ "@types/lodash": "^4.14.149",
+ "direction": "^1.0.3",
+ "is-hotkey": "^0.1.6",
+ "is-plain-object": "^5.0.0",
+ "lodash": "^4.17.4",
+ "scroll-into-view-if-needed": "^2.2.20",
+ "tiny-invariant": "1.0.6"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "peer": true
+ }
+ }
+ },
+ "styled-components": {
+ "version": "5.3.9",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz",
+ "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^1.1.0",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ },
+ "dependencies": {
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ }
+ }
+ },
+ "stylis": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz",
+ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA=="
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "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=="
+ },
+ "tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "peer": true
+ },
+ "terser": {
+ "version": "5.16.6",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz",
+ "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@jridgewell/source-map": "^0.3.2",
+ "acorn": "^8.5.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
+ "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^3.1.1",
+ "serialize-javascript": "^6.0.1",
+ "terser": "^5.16.5"
+ }
+ },
+ "tiny-invariant": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
+ "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
+ },
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="
+ },
+ "toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+ },
+ "tree-changes": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz",
+ "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==",
+ "requires": {
+ "@gilbarbara/deep-equal": "^0.1.1",
+ "is-lite": "^0.8.2"
+ },
+ "dependencies": {
+ "is-lite": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
+ "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
+ }
+ }
+ },
+ "tslib": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ },
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ },
+ "update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "requires": {}
+ },
+ "vite": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.0.tgz",
+ "integrity": "sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.17.5",
+ "fsevents": "~2.3.2",
+ "postcss": "^8.4.21",
+ "resolve": "^1.22.1",
+ "rollup": "^3.18.0"
+ }
+ },
+ "watchpack": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
+ "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ }
+ },
+ "wavesurfer.js": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz",
+ "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw=="
+ },
+ "webpack": {
+ "version": "5.76.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz",
+ "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@types/eslint-scope": "^3.7.3",
+ "@types/estree": "^0.0.51",
+ "@webassemblyjs/ast": "1.11.1",
+ "@webassemblyjs/wasm-edit": "1.11.1",
+ "@webassemblyjs/wasm-parser": "1.11.1",
+ "acorn": "^8.7.1",
+ "acorn-import-assertions": "^1.7.6",
+ "browserslist": "^4.14.5",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.10.0",
+ "es-module-lexer": "^0.9.0",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.9",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^3.1.0",
+ "tapable": "^2.1.1",
+ "terser-webpack-plugin": "^5.1.3",
+ "watchpack": "^2.4.0",
+ "webpack-sources": "^3.2.3"
+ }
+ },
+ "webpack-sources": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "dev": true,
+ "peer": true
+ },
+ "worker-loader": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
+ "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ }
+ },
+ "yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f15002d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "q-blog",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.10.6",
+ "@emotion/styled": "^11.10.6",
+ "@mui/icons-material": "^5.11.11",
+ "@mui/material": "^5.11.13",
+ "@reduxjs/toolkit": "^1.9.3",
+ "@types/react-grid-layout": "^1.3.2",
+ "axios": "^1.3.4",
+ "compressorjs": "^1.2.1",
+ "localforage": "^1.10.0",
+ "moment": "^2.29.4",
+ "philliplm-react-modern-audio-player": "^1.4.6",
+ "react": "^18.2.0",
+ "react-copy-to-clipboard": "^5.1.0",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
+ "react-dom": "^18.2.0",
+ "react-dropzone": "^14.2.3",
+ "react-grid-layout": "^1.3.4",
+ "react-intersection-observer": "^9.4.3",
+ "react-joyride": "^2.5.4",
+ "react-masonry-css": "^1.0.16",
+ "react-redux": "^8.0.5",
+ "react-resize-detector": "^8.0.4",
+ "react-router-dom": "^6.9.0",
+ "react-toastify": "^9.1.2",
+ "react-virtuoso": "^4.3.3",
+ "short-unique-id": "^4.4.4",
+ "slate": "^0.91.4",
+ "slate-history": "^0.86.0",
+ "slate-react": "^0.91.11"
+ },
+ "devDependencies": {
+ "@mui/types": "^7.2.3",
+ "@types/react": "^18.0.28",
+ "@types/react-copy-to-clipboard": "^5.0.4",
+ "@types/react-dom": "^18.0.11",
+ "@vitejs/plugin-react-swc": "^3.2.0",
+ "prettier": "^2.8.6",
+ "typescript": "^4.9.3",
+ "vite": "^4.2.0",
+ "worker-loader": "^3.0.8"
+ }
+}
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..76bb87d
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,48 @@
+// @ts-nocheck
+import { useEffect, useState } from "react";
+import { Routes, Route } from "react-router-dom";
+import { ProductPage } from "./pages/Product/ProductPage";
+import { StoreList } from "./pages/StoreList/StoreList";
+import { ThemeProvider } from "@mui/material/styles";
+import { CssBaseline } from "@mui/material";
+import { lightTheme, darkTheme } from "./styles/theme";
+import { store } from "./state/store";
+import { Provider } from "react-redux";
+import { Store } from "./pages/Store/Store/Store";
+import { MyOrders } from "./pages/MyOrders/MyOrders";
+import { ErrorElement } from "./components/common/Error/ErrorElement";
+import GlobalWrapper from "./wrappers/GlobalWrapper";
+import Notification from "./components/common/Notification/Notification";
+import { ProductManager } from "./pages/ProductManager/ProductManager";
+
+function App() {
+ // const themeColor = window._qdnTheme
+
+ const [theme, setTheme] = useState("dark");
+
+ return (
+
+
+
+ setTheme(val)}>
+
+
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/assets/img/ArrrLogoBlack.png b/src/assets/img/ArrrLogoBlack.png
new file mode 100644
index 0000000..954d1c0
Binary files /dev/null and b/src/assets/img/ArrrLogoBlack.png differ
diff --git a/src/assets/img/ArrrLogoWhite.png b/src/assets/img/ArrrLogoWhite.png
new file mode 100644
index 0000000..28e6361
Binary files /dev/null and b/src/assets/img/ArrrLogoWhite.png differ
diff --git a/src/assets/img/Q-AppsLogo.webp b/src/assets/img/Q-AppsLogo.webp
new file mode 100644
index 0000000..399ebe0
Binary files /dev/null and b/src/assets/img/Q-AppsLogo.webp differ
diff --git a/src/assets/img/QShopLogo.webp b/src/assets/img/QShopLogo.webp
new file mode 100644
index 0000000..bf50eb2
Binary files /dev/null and b/src/assets/img/QShopLogo.webp differ
diff --git a/src/assets/img/QShopLogoLight.webp b/src/assets/img/QShopLogoLight.webp
new file mode 100644
index 0000000..484aa96
Binary files /dev/null and b/src/assets/img/QShopLogoLight.webp differ
diff --git a/src/assets/img/arrr.png b/src/assets/img/arrr.png
new file mode 100644
index 0000000..d274565
Binary files /dev/null and b/src/assets/img/arrr.png differ
diff --git a/src/assets/img/btc.png b/src/assets/img/btc.png
new file mode 100644
index 0000000..fe3fd1a
Binary files /dev/null and b/src/assets/img/btc.png differ
diff --git a/src/assets/img/dgb.png b/src/assets/img/dgb.png
new file mode 100644
index 0000000..6950158
Binary files /dev/null and b/src/assets/img/dgb.png differ
diff --git a/src/assets/img/doge.png b/src/assets/img/doge.png
new file mode 100644
index 0000000..d99fa2f
Binary files /dev/null and b/src/assets/img/doge.png differ
diff --git a/src/assets/img/ltc.png b/src/assets/img/ltc.png
new file mode 100644
index 0000000..d743e57
Binary files /dev/null and b/src/assets/img/ltc.png differ
diff --git a/src/assets/img/qort.png b/src/assets/img/qort.png
new file mode 100644
index 0000000..39d090f
Binary files /dev/null and b/src/assets/img/qort.png differ
diff --git a/src/assets/img/rvn.png b/src/assets/img/rvn.png
new file mode 100644
index 0000000..31d7fdd
Binary files /dev/null and b/src/assets/img/rvn.png differ
diff --git a/src/assets/svgs/ARRRSVG.tsx b/src/assets/svgs/ARRRSVG.tsx
new file mode 100644
index 0000000..0966da2
--- /dev/null
+++ b/src/assets/svgs/ARRRSVG.tsx
@@ -0,0 +1,34 @@
+import { IconTypes } from "./IconTypes";
+
+export const ARRRSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/AccountCircleSVG.tsx b/src/assets/svgs/AccountCircleSVG.tsx
new file mode 100644
index 0000000..deb88bd
--- /dev/null
+++ b/src/assets/svgs/AccountCircleSVG.tsx
@@ -0,0 +1,25 @@
+interface AccountCircleSVGProps {
+ color: string
+ height: string
+ width: string
+}
+
+export const AccountCircleSVG: React.FC = ({
+ color,
+ height,
+ width
+}) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/AddSVG.tsx b/src/assets/svgs/AddSVG.tsx
new file mode 100644
index 0000000..8960bda
--- /dev/null
+++ b/src/assets/svgs/AddSVG.tsx
@@ -0,0 +1,15 @@
+import { IconTypes } from "./IconTypes";
+
+export const AddSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/AlignCenterSVG.tsx b/src/assets/svgs/AlignCenterSVG.tsx
new file mode 100644
index 0000000..b78991b
--- /dev/null
+++ b/src/assets/svgs/AlignCenterSVG.tsx
@@ -0,0 +1,21 @@
+import { SVGProps } from './interfaces'
+
+export const AlignCenterSVG: React.FC = ({
+ color,
+ height,
+ width
+}) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/AlignLeftSVG.tsx b/src/assets/svgs/AlignLeftSVG.tsx
new file mode 100644
index 0000000..4ac2eb4
--- /dev/null
+++ b/src/assets/svgs/AlignLeftSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const AlignLeftSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/AlignRightSVG.tsx b/src/assets/svgs/AlignRightSVG.tsx
new file mode 100644
index 0000000..2a2eea8
--- /dev/null
+++ b/src/assets/svgs/AlignRightSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const AlignRightSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/BackArrowSVG.tsx b/src/assets/svgs/BackArrowSVG.tsx
new file mode 100644
index 0000000..670a892
--- /dev/null
+++ b/src/assets/svgs/BackArrowSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const BackArrowSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/BoldSVG.tsx b/src/assets/svgs/BoldSVG.tsx
new file mode 100644
index 0000000..8d06816
--- /dev/null
+++ b/src/assets/svgs/BoldSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const BoldSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/BriefcaseSVG.tsx b/src/assets/svgs/BriefcaseSVG.tsx
new file mode 100644
index 0000000..07dc0a7
--- /dev/null
+++ b/src/assets/svgs/BriefcaseSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const BriefcaseSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CalendarSVG.tsx b/src/assets/svgs/CalendarSVG.tsx
new file mode 100644
index 0000000..3e470de
--- /dev/null
+++ b/src/assets/svgs/CalendarSVG.tsx
@@ -0,0 +1,23 @@
+interface AccountCircleSVGProps {
+ color: string;
+ height: string;
+ width: string;
+}
+
+export const CalendarSVG: React.FC = ({
+ color,
+ height,
+ width
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CancelSVG.tsx b/src/assets/svgs/CancelSVG.tsx
new file mode 100644
index 0000000..bf9afed
--- /dev/null
+++ b/src/assets/svgs/CancelSVG.tsx
@@ -0,0 +1,27 @@
+import { IconTypes } from "./IconTypes";
+
+interface CancelSVGProps extends IconTypes {
+ onMouseDownFunc?: (e: any) => void;
+}
+
+export const CancelSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onMouseDownFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CartSVG.tsx b/src/assets/svgs/CartSVG.tsx
new file mode 100644
index 0000000..9bd5d10
--- /dev/null
+++ b/src/assets/svgs/CartSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const CartSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CategorySVG.tsx b/src/assets/svgs/CategorySVG.tsx
new file mode 100644
index 0000000..113e702
--- /dev/null
+++ b/src/assets/svgs/CategorySVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const CategorySVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CodeBlockSVG.tsx b/src/assets/svgs/CodeBlockSVG.tsx
new file mode 100644
index 0000000..1fad02f
--- /dev/null
+++ b/src/assets/svgs/CodeBlockSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const CodeBlockSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/CompareArrowsSVG.tsx b/src/assets/svgs/CompareArrowsSVG.tsx
new file mode 100644
index 0000000..41558f5
--- /dev/null
+++ b/src/assets/svgs/CompareArrowsSVG.tsx
@@ -0,0 +1,19 @@
+import { IconTypes } from "./IconTypes";
+
+export const CompareArrowsSVG: React.FC = ({
+ color,
+ height,
+ width,
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/CurrencySVG.tsx b/src/assets/svgs/CurrencySVG.tsx
new file mode 100644
index 0000000..6d76d32
--- /dev/null
+++ b/src/assets/svgs/CurrencySVG.tsx
@@ -0,0 +1,15 @@
+import { IconTypes } from "./IconTypes";
+
+export const CurrencySVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/DarkModeSVG.tsx b/src/assets/svgs/DarkModeSVG.tsx
new file mode 100644
index 0000000..fe9ccab
--- /dev/null
+++ b/src/assets/svgs/DarkModeSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from './IconTypes'
+
+export const DarkModeSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/DescriptionSVG.tsx b/src/assets/svgs/DescriptionSVG.tsx
new file mode 100644
index 0000000..9505390
--- /dev/null
+++ b/src/assets/svgs/DescriptionSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const DescriptionSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/DialogsSVG.tsx b/src/assets/svgs/DialogsSVG.tsx
new file mode 100644
index 0000000..1288a1f
--- /dev/null
+++ b/src/assets/svgs/DialogsSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const DialogsSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/DoubleArrowDownSVG.tsx b/src/assets/svgs/DoubleArrowDownSVG.tsx
new file mode 100644
index 0000000..1bf17d8
--- /dev/null
+++ b/src/assets/svgs/DoubleArrowDownSVG.tsx
@@ -0,0 +1,24 @@
+import { IconTypes } from "./IconTypes";
+
+export const DoubleArrowDownSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ id,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/DownloadSVG.tsx b/src/assets/svgs/DownloadSVG.tsx
new file mode 100644
index 0000000..ad89cad
--- /dev/null
+++ b/src/assets/svgs/DownloadSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const DownloadSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/ExpandMoreSVG.tsx b/src/assets/svgs/ExpandMoreSVG.tsx
new file mode 100644
index 0000000..12f1935
--- /dev/null
+++ b/src/assets/svgs/ExpandMoreSVG.tsx
@@ -0,0 +1,22 @@
+import { IconTypes } from "./IconTypes";
+export const ExpandMoreSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/GarbageSVG.tsx b/src/assets/svgs/GarbageSVG.tsx
new file mode 100644
index 0000000..ec32c5e
--- /dev/null
+++ b/src/assets/svgs/GarbageSVG.tsx
@@ -0,0 +1,22 @@
+import { IconTypes } from "./IconTypes";
+export const GarbageSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/H2SVG.tsx b/src/assets/svgs/H2SVG.tsx
new file mode 100644
index 0000000..0328fc3
--- /dev/null
+++ b/src/assets/svgs/H2SVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const H2SVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/H3SVG.tsx b/src/assets/svgs/H3SVG.tsx
new file mode 100644
index 0000000..7268032
--- /dev/null
+++ b/src/assets/svgs/H3SVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const H3SVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/IconTypes.ts b/src/assets/svgs/IconTypes.ts
new file mode 100644
index 0000000..2dd966d
--- /dev/null
+++ b/src/assets/svgs/IconTypes.ts
@@ -0,0 +1,8 @@
+export interface IconTypes {
+ color: string;
+ height: string;
+ width: string;
+ className?: string;
+ onClickFunc?: ((e: React.MouseEvent) => void) | ((params?: any) => void);
+ id?: string;
+}
diff --git a/src/assets/svgs/ItalicSVG.tsx b/src/assets/svgs/ItalicSVG.tsx
new file mode 100644
index 0000000..50a5493
--- /dev/null
+++ b/src/assets/svgs/ItalicSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const ItalicSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/LightModeSVG.tsx b/src/assets/svgs/LightModeSVG.tsx
new file mode 100644
index 0000000..66b056f
--- /dev/null
+++ b/src/assets/svgs/LightModeSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from './IconTypes'
+
+export const LightModeSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/LinkSVG.tsx b/src/assets/svgs/LinkSVG.tsx
new file mode 100644
index 0000000..9994f43
--- /dev/null
+++ b/src/assets/svgs/LinkSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const LinkSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/LocationSVG.tsx b/src/assets/svgs/LocationSVG.tsx
new file mode 100644
index 0000000..b34eb25
--- /dev/null
+++ b/src/assets/svgs/LocationSVG.tsx
@@ -0,0 +1,15 @@
+import { IconTypes } from "./IconTypes";
+
+export const LocationSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/LoyaltySVG.tsx b/src/assets/svgs/LoyaltySVG.tsx
new file mode 100644
index 0000000..cf385a0
--- /dev/null
+++ b/src/assets/svgs/LoyaltySVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const LoyaltySVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/MinimizeSVG.tsx b/src/assets/svgs/MinimizeSVG.tsx
new file mode 100644
index 0000000..15362f8
--- /dev/null
+++ b/src/assets/svgs/MinimizeSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const MinimizeSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/MinusCircle.tsx b/src/assets/svgs/MinusCircle.tsx
new file mode 100644
index 0000000..0914adf
--- /dev/null
+++ b/src/assets/svgs/MinusCircle.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const MinusCircleSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/NewWindowSVG.tsx b/src/assets/svgs/NewWindowSVG.tsx
new file mode 100644
index 0000000..c97d483
--- /dev/null
+++ b/src/assets/svgs/NewWindowSVG.tsx
@@ -0,0 +1,25 @@
+interface NewWindowSVGProps {
+ color: string
+ height: string
+ width: string
+}
+
+export const NewWindowSVG: React.FC = ({
+ color,
+ height,
+ width
+}) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/OrdersSVG.tsx b/src/assets/svgs/OrdersSVG.tsx
new file mode 100644
index 0000000..95125cd
--- /dev/null
+++ b/src/assets/svgs/OrdersSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const OrdersSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/OwnerSVG.tsx b/src/assets/svgs/OwnerSVG.tsx
new file mode 100644
index 0000000..e9a6a2a
--- /dev/null
+++ b/src/assets/svgs/OwnerSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const OwnerSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/PlusCircle.tsx b/src/assets/svgs/PlusCircle.tsx
new file mode 100644
index 0000000..fbac868
--- /dev/null
+++ b/src/assets/svgs/PlusCircle.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const PlusCircleSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/QortalSVG.tsx b/src/assets/svgs/QortalSVG.tsx
new file mode 100644
index 0000000..c8d6942
--- /dev/null
+++ b/src/assets/svgs/QortalSVG.tsx
@@ -0,0 +1,46 @@
+import { IconTypes } from "./IconTypes";
+
+export const QortalSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/ShippingSVG.tsx b/src/assets/svgs/ShippingSVG.tsx
new file mode 100644
index 0000000..a030d5e
--- /dev/null
+++ b/src/assets/svgs/ShippingSVG.tsx
@@ -0,0 +1,15 @@
+import { IconTypes } from "./IconTypes";
+
+export const ShippingSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/StarSVG.tsx b/src/assets/svgs/StarSVG.tsx
new file mode 100644
index 0000000..732d9e4
--- /dev/null
+++ b/src/assets/svgs/StarSVG.tsx
@@ -0,0 +1,21 @@
+import { IconTypes } from "./IconTypes";
+
+export const StarSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/StorefrontSVG.tsx b/src/assets/svgs/StorefrontSVG.tsx
new file mode 100644
index 0000000..853c697
--- /dev/null
+++ b/src/assets/svgs/StorefrontSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const StorefrontSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/TimesSVG.tsx b/src/assets/svgs/TimesSVG.tsx
new file mode 100644
index 0000000..48c31d9
--- /dev/null
+++ b/src/assets/svgs/TimesSVG.tsx
@@ -0,0 +1,23 @@
+import { IconTypes } from "./IconTypes";
+
+export const TimesSVG: React.FC = ({
+ color,
+ height,
+ width,
+ className,
+ onClickFunc
+}) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/UnderlineSVG.tsx b/src/assets/svgs/UnderlineSVG.tsx
new file mode 100644
index 0000000..a65f597
--- /dev/null
+++ b/src/assets/svgs/UnderlineSVG.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from './interfaces'
+
+export const UnderlineSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ )
+}
diff --git a/src/assets/svgs/WarningSVG.tsx b/src/assets/svgs/WarningSVG.tsx
new file mode 100644
index 0000000..75b1c86
--- /dev/null
+++ b/src/assets/svgs/WarningSVG.tsx
@@ -0,0 +1,15 @@
+import { IconTypes } from "./IconTypes";
+
+export const WarningSVG: React.FC = ({ color, height, width }) => {
+ return (
+
+ );
+};
diff --git a/src/assets/svgs/interfaces.ts b/src/assets/svgs/interfaces.ts
new file mode 100644
index 0000000..6a0d5e1
--- /dev/null
+++ b/src/assets/svgs/interfaces.ts
@@ -0,0 +1,5 @@
+export interface SVGProps {
+ color: string
+ height: string
+ width: string
+}
diff --git a/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts b/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts
new file mode 100644
index 0000000..05ebf6e
--- /dev/null
+++ b/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts
@@ -0,0 +1,28 @@
+import { styled } from '@mui/system';
+import {
+ Box,
+ Modal,
+ Typography
+} from '@mui/material';
+
+export const StyledModal = styled(Modal)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+}))
+
+export const ModalContent = styled(Box)(({ theme }) => ({
+ backgroundColor: theme.palette.primary.main,
+ padding: theme.spacing(4),
+ borderRadius: theme.spacing(1),
+ width: '40%',
+ '&:focus': {
+ outline: 'none'
+ }
+}))
+
+export const ModalText = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "25px",
+ color: theme.palette.text.primary,
+}));
\ No newline at end of file
diff --git a/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx b/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx
new file mode 100644
index 0000000..da7fe38
--- /dev/null
+++ b/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx
@@ -0,0 +1,100 @@
+import React, { useState } from "react";
+import {
+ Box,
+ Button,
+ Modal,
+ Typography,
+ SelectChangeEvent,
+ ListItem,
+ List,
+ useTheme
+} from "@mui/material";
+import {
+ StyledModal,
+ ModalContent,
+ ModalText
+} from "./BlockedNamesModal-styles";
+
+interface PostModalProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+export const BlockedNamesModal: React.FC = ({
+ open,
+ onClose
+}) => {
+ const [blockedNames, setBlockedNames] = useState([]);
+ const theme = useTheme();
+ const getBlockedNames = React.useCallback(async () => {
+ try {
+ const listName = `blockedNames_q-blog`;
+ const response = await qortalRequest({
+ action: "GET_LIST_ITEMS",
+ list_name: listName
+ });
+ setBlockedNames(response);
+ } catch (error) {
+ onClose();
+ }
+ }, []);
+
+ React.useEffect(() => {
+ getBlockedNames();
+ }, [getBlockedNames]);
+
+ const removeFromBlockList = async (name: string) => {
+ try {
+ const response = await qortalRequest({
+ action: "DELETE_LIST_ITEM",
+ list_name: "blockedNames_q-blog",
+ item: name
+ });
+
+ if (response === true) {
+ setBlockedNames((prev) => prev.filter((n) => n !== name));
+ }
+ } catch (error) {}
+ };
+
+ return (
+
+
+ Manage blocked names
+
+ {blockedNames.map((name, index) => (
+
+ {name}
+
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/src/components/common/ConfirmationModal/ConfirmationModal-styles.tsx b/src/components/common/ConfirmationModal/ConfirmationModal-styles.tsx
new file mode 100644
index 0000000..5079e58
--- /dev/null
+++ b/src/components/common/ConfirmationModal/ConfirmationModal-styles.tsx
@@ -0,0 +1,18 @@
+import { styled } from "@mui/system";
+import { DialogTitle, DialogContentText } from "@mui/material";
+
+export const DialogTitleStyled = styled(DialogTitle)(({ theme }) => ({
+ fontFamily: "Merriweather Sans",
+ fontSize: "18px",
+ color: theme.palette.text.primary,
+ userSelect: "none"
+}));
+
+export const DialogContentTextStyled = styled(DialogContentText)(
+ ({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "16px",
+ color: theme.palette.text.primary,
+ userSelect: "none"
+ })
+);
diff --git a/src/components/common/ConfirmationModal/ConfirmationModal.tsx b/src/components/common/ConfirmationModal/ConfirmationModal.tsx
new file mode 100644
index 0000000..9de7754
--- /dev/null
+++ b/src/components/common/ConfirmationModal/ConfirmationModal.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { Dialog, DialogActions, DialogContent, Button } from "@mui/material";
+import {
+ DialogContentTextStyled,
+ DialogTitleStyled
+} from "./ConfirmationModal-styles";
+import {
+ CancelButton,
+ CreateButton
+} from "../../modals/CreateStoreModal-styles";
+
+export interface ModalProps {
+ open: boolean;
+ title: string;
+ message: string;
+ handleConfirm: () => void;
+ handleCancel: () => void;
+}
+
+const ConfirmationModal: React.FC = ({
+ open,
+ title,
+ message,
+ handleConfirm,
+ handleCancel
+}) => {
+ return (
+
+ );
+};
+
+export default ConfirmationModal;
diff --git a/src/components/common/ContextMenu/ContextMenuResource.tsx b/src/components/common/ContextMenu/ContextMenuResource.tsx
new file mode 100644
index 0000000..785142d
--- /dev/null
+++ b/src/components/common/ContextMenu/ContextMenuResource.tsx
@@ -0,0 +1,82 @@
+import * as React from "react";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import Typography from "@mui/material/Typography";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import { useDispatch } from "react-redux";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import { Box } from "@mui/material";
+
+export default function ContextMenuResource({
+ children,
+ name,
+ service,
+ identifier,
+ link
+}: any) {
+ const [contextMenu, setContextMenu] = React.useState<{
+ mouseX: number;
+ mouseY: number;
+ } | null>(null);
+ const dispatch = useDispatch();
+ const handleContextMenu = (event: React.MouseEvent) => {
+ event.preventDefault();
+ setContextMenu(
+ contextMenu === null
+ ? {
+ mouseX: event.clientX + 2,
+ mouseY: event.clientY - 6
+ }
+ : // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
+ // Other native context menus might behave different.
+ // With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
+ null
+ );
+ };
+
+ const handleClose = () => {
+ setContextMenu(null);
+ };
+
+ return (
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/common/CustomIcon.tsx b/src/components/common/CustomIcon.tsx
new file mode 100644
index 0000000..ae9ddd5
--- /dev/null
+++ b/src/components/common/CustomIcon.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'
+import { styled } from '@mui/system'
+
+const CustomSvgIcon: React.FC = styled(SvgIcon)(({ theme }) => ({
+ cursor: 'pointer',
+ color: '#5f6368',
+ transition: 'all 0.2s',
+ '&:hover': {
+ transform: 'scale(1.1)'
+ }
+})) as unknown as React.FC
+
+export const CustomIcon: React.FC = (props) => {
+ return
+}
diff --git a/src/components/common/DraggableResizableGrid.tsx b/src/components/common/DraggableResizableGrid.tsx
new file mode 100644
index 0000000..871fec7
--- /dev/null
+++ b/src/components/common/DraggableResizableGrid.tsx
@@ -0,0 +1,55 @@
+// DraggableResizableGrid.tsx
+import React from 'react'
+import { DndProvider } from 'react-dnd'
+import { HTML5Backend } from 'react-dnd-html5-backend'
+import GridLayout, { Layout } from 'react-grid-layout'
+
+import './DraggableResizableGrid.css' // Add your custom CSS for the grid layout
+
+interface GridItem {
+ id: string
+ content: React.ReactNode
+}
+
+interface DraggableResizableGridProps {
+ items: GridItem[]
+ cols?: number
+ rowHeight?: number
+ onLayoutChange?: (layout: Layout[]) => void
+}
+
+const DraggableResizableGrid: React.FC = ({
+ items,
+ cols = 12,
+ rowHeight = 30,
+ onLayoutChange
+}) => {
+ const layout = items.map((item, index) => ({
+ i: item.id,
+ x: index % cols,
+ y: Math.floor(index / cols),
+ w: 4,
+ h: 4
+ }))
+
+ return (
+
+
+ {items.map((item) => (
+
+ {item.content}
+
+ ))}
+
+
+ )
+}
+
+export default DraggableResizableGrid
diff --git a/src/components/common/Error/Error-styles.tsx b/src/components/common/Error/Error-styles.tsx
new file mode 100644
index 0000000..6765f09
--- /dev/null
+++ b/src/components/common/Error/Error-styles.tsx
@@ -0,0 +1,40 @@
+import { styled } from '@mui/system'
+import { Box, Button, Grid, Typography } from '@mui/material'
+
+export const Container = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'column',
+ gap: '15px',
+ padding: '25px 10px'
+}))
+
+export const HeaderRow = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: '10px'
+}))
+
+export const HeaderText = styled(Typography)(({ theme }) => ({
+ fontFamily: 'Oxygen',
+ color: theme.palette.text.primary,
+ fontWeight: '400'
+}))
+
+export const BackButton = styled(Button)(({ theme }) => ({
+ backgroundColor: theme.palette.secondary.light,
+ color: '#fff',
+ padding: '8px 16px',
+ borderRadius: '7px',
+ fontFamily: 'Oxygen',
+ fontSize: '18px',
+ fontWeight: 500,
+ textTransform: 'none',
+ transition: 'all 0.3s ease-in-out',
+ '&:hover': {
+ cursor: 'pointer',
+ backgroundColor: theme.palette.secondary.light,
+ filter: 'brightness(0.9)'
+ }
+}))
diff --git a/src/components/common/Error/ErrorElement.tsx b/src/components/common/Error/ErrorElement.tsx
new file mode 100644
index 0000000..aace6a9
--- /dev/null
+++ b/src/components/common/Error/ErrorElement.tsx
@@ -0,0 +1,34 @@
+import { Container, HeaderText, BackButton, HeaderRow } from "./Error-styles";
+import { useTheme } from "@mui/material";
+import { WarningSVG } from "../../../assets/svgs/WarningSVG";
+
+interface ErrorElementProps {
+ message: string;
+}
+
+export const ErrorElement: React.FC = ({ message }) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ {message}
+
+
+ Please return home or try refreshing the page!
+
+ {
+ window.location.reload();
+ }}
+ >
+ Back Home
+
+
+ );
+};
diff --git a/src/components/common/ErrorBoundary.tsx b/src/components/common/ErrorBoundary.tsx
new file mode 100644
index 0000000..58b3185
--- /dev/null
+++ b/src/components/common/ErrorBoundary.tsx
@@ -0,0 +1,36 @@
+import React, { ReactNode } from 'react'
+
+interface ErrorBoundaryProps {
+ children: ReactNode
+ fallback: ReactNode
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean
+}
+
+class ErrorBoundary extends React.Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ state: ErrorBoundaryState = {
+ hasError: false
+ }
+
+ static getDerivedStateFromError(_: Error): ErrorBoundaryState {
+ return { hasError: true }
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
+ // You can log the error and errorInfo here, for example, to an error reporting service.
+ console.error('Error caught in ErrorBoundary:', error, errorInfo)
+ }
+
+ render(): React.ReactNode {
+ if (this.state.hasError) return this.props.fallback
+
+ return this.props.children
+ }
+}
+
+export default ErrorBoundary
diff --git a/src/components/common/GenericPublishModal.tsx b/src/components/common/GenericPublishModal.tsx
new file mode 100644
index 0000000..6994108
--- /dev/null
+++ b/src/components/common/GenericPublishModal.tsx
@@ -0,0 +1,316 @@
+import React, { useState } from 'react'
+import {
+ Box,
+ Button,
+ Modal,
+ TextField,
+ Typography,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ SelectChangeEvent,
+ OutlinedInput,
+ Chip,
+ IconButton
+} from '@mui/material'
+import { styled } from '@mui/system'
+import { useDropzone } from 'react-dropzone'
+import { toBase64 } from '../../utils/toBase64'
+import AddIcon from '@mui/icons-material/Add'
+import CloseIcon from '@mui/icons-material/Close'
+import { usePublishGeneric } from './PublishGeneric'
+import { useDispatch } from 'react-redux'
+import { setNotification } from '../../state/features/notificationsSlice'
+
+const StyledModal = styled(Modal)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+}))
+
+const ChipContainer = styled(Box)({
+ display: 'flex',
+ flexWrap: 'wrap',
+ '& > *': {
+ margin: '4px'
+ }
+})
+
+const ModalContent = styled(Box)(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ padding: theme.spacing(4),
+ borderRadius: theme.spacing(1),
+ width: '40%',
+ '&:focus': {
+ outline: 'none'
+ }
+}))
+
+interface GenericModalProps {
+ open: boolean
+ onClose: () => void
+ onPublish: (value: any) => void
+ acceptedFileType?: string
+ acceptedFileTypes?: string[]
+ service: string
+ identifierPrefix: string
+ editVideoIdentifier?: string | null | undefined
+}
+
+interface SelectOption {
+ id: string
+ name: string
+}
+const maxSize = 500 * 1024 * 1024
+
+export const GenericModal: React.FC = ({
+ open,
+ onClose,
+ onPublish,
+ acceptedFileType,
+ acceptedFileTypes,
+ service,
+ identifierPrefix,
+ editVideoIdentifier
+}) => {
+ const [file, setFile] = useState(null)
+ const [title, setTitle] = useState('')
+ const [description, setDescription] = useState('')
+ const [selectedOption, setSelectedOption] = useState(
+ null
+ )
+ const [inputValue, setInputValue] = useState('')
+ const [chips, setChips] = useState([])
+
+ const [options, setOptions] = useState([])
+ const [tags, setTags] = useState([])
+ const { publishGeneric } = usePublishGeneric()
+ const dispatch = useDispatch()
+
+ let acceptedFile = {}
+ if (acceptedFileType) {
+ acceptedFile = {
+ [acceptedFileType]: []
+ }
+ }
+ const { getRootProps, getInputProps } = useDropzone({
+ ...acceptedFile,
+ maxFiles: 1,
+ maxSize,
+ onDrop: (acceptedFiles) => {
+ setFile(acceptedFiles[0])
+ },
+ onDropRejected: (rejectedFiles) => {
+ dispatch(
+ setNotification({
+ msg: 'Your file is over the 500mb limit.',
+ alertType: 'error'
+ })
+ )
+ }
+ })
+
+ const handleTitleChange = (event: React.ChangeEvent) => {
+ setTitle(event.target.value)
+ }
+
+ const handleDescriptionChange = (
+ event: React.ChangeEvent
+ ) => {
+ setDescription(event.target.value)
+ }
+
+ const handleOptionChange = (event: SelectChangeEvent) => {
+ const optionId = event.target.value
+ const selectedOption = options.find((option) => option.id === optionId)
+ setSelectedOption(selectedOption || null)
+ }
+
+ const handleChipDelete = (index: number) => {
+ const newChips = [...chips]
+ newChips.splice(index, 1)
+ setChips(newChips)
+ }
+
+ const handleSubmit = async () => {
+ const missingFields = []
+
+ if (!title) missingFields.push('title')
+ if (!file) missingFields.push('file')
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ')
+ const errMsg = `Missing: ${missingFieldsString}`
+
+ return
+ }
+ if (!file) return
+
+ const formattedTags: { [key: string]: string } = {}
+ chips.forEach((tag, i) => {
+ formattedTags[`tag${i + 1}`] = tag
+ })
+
+ try {
+ const base64 = await toBase64(file)
+ if (typeof base64 !== 'string') return
+ const base64String = base64.split(',')[1]
+ const fileExtension = file?.name?.split('.')?.pop()
+ const fileTitle = title?.replace(/ /g, '_')?.slice(0, 20)
+ const filename = `${fileTitle}.${fileExtension}`
+ const res = await publishGeneric({
+ editVideoIdentifier,
+ service,
+ identifierPrefix,
+ title,
+ description,
+ base64: base64String,
+ filename: filename,
+ category: selectedOption?.id || '',
+ ...formattedTags
+ })
+ onPublish(res)
+ setFile(null)
+ setTitle('')
+ setDescription('')
+ onClose()
+ } catch (error) {}
+ }
+
+ const handleInputChange = (event: any) => {
+ setInputValue(event.target.value)
+ }
+
+ const handleInputKeyDown = (event: any) => {
+ if (event.key === 'Enter' && inputValue !== '') {
+ if (chips.length < 5) {
+ setChips([...chips, inputValue])
+ setInputValue('')
+ } else {
+ event.preventDefault()
+ }
+ }
+ }
+
+ const addChip = () => {
+ if (chips.length < 5) {
+ setChips([...chips, inputValue])
+ setInputValue('')
+ }
+ }
+
+ const getListCategories = React.useCallback(async () => {
+ try {
+ const url = `/arbitrary/categories`
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ const responseData = await response.json()
+ setOptions(responseData)
+ } catch (error) {}
+ }, [])
+
+ React.useEffect(() => {
+ getListCategories()
+ }, [getListCategories])
+
+ return (
+
+
+ {editVideoIdentifier && (
+
+ You are editing: {editVideoIdentifier}
+
+ )}
+
+ Upload {service}
+
+
+
+
+ {file
+ ? file.name
+ : 'Drag and drop a file here or click to select a file'}
+
+
+
+
+ {options.length > 0 && (
+
+ Select a Category
+ }
+ value={selectedOption?.id || ''}
+ onChange={handleOptionChange}
+ >
+ {options.map((option) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {chips.map((chip, index) => (
+ handleChipDelete(index)}
+ deleteIcon={}
+ />
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/src/components/common/ImageUploader.tsx b/src/components/common/ImageUploader.tsx
new file mode 100644
index 0000000..0cac393
--- /dev/null
+++ b/src/components/common/ImageUploader.tsx
@@ -0,0 +1,89 @@
+import React, { useCallback } from 'react'
+import { Box, Button, TextField, Typography, Modal } from '@mui/material'
+import {
+ useDropzone,
+ DropzoneRootProps,
+ DropzoneInputProps
+} from 'react-dropzone'
+import Compressor from 'compressorjs'
+
+const toBase64 = (file: File): Promise =>
+ new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(file)
+ reader.onload = () => resolve(reader.result)
+ reader.onerror = (error) => {
+ reject(error)
+ }
+ })
+
+interface ImageUploaderProps {
+ children: React.ReactNode
+ onPick: (base64Img: string) => void
+}
+
+const ImageUploader: React.FC = ({ children, onPick }) => {
+ const onDrop = useCallback(
+ async (acceptedFiles: File[]) => {
+ if (acceptedFiles.length > 1) {
+ return
+ }
+ let compressedFile: File | undefined
+
+ try {
+ const image = acceptedFiles[0]
+ await new Promise((resolve) => {
+ new Compressor(image, {
+ quality: 0.6,
+ maxWidth: 1200,
+ mimeType: 'image/webp',
+ success(result) {
+ const file = new File([result], 'name', {
+ type: 'image/webp'
+ })
+ compressedFile = file
+ resolve()
+ },
+ error(err) {}
+ })
+ })
+ if (!compressedFile) return
+ const base64Img = await toBase64(compressedFile)
+
+ onPick(base64Img as string)
+ } catch (error) {
+ console.error(error)
+ }
+ },
+ [onPick]
+ )
+
+ const {
+ getRootProps,
+ getInputProps,
+ isDragActive
+ }: {
+ getRootProps: () => DropzoneRootProps
+ getInputProps: () => DropzoneInputProps
+ isDragActive: boolean
+ } = useDropzone({
+ onDrop,
+ accept: {
+ 'image/*': []
+ }
+ })
+
+ return (
+
+
+ {children}
+
+ )
+}
+
+export default ImageUploader
diff --git a/src/components/common/LazyLoad.tsx b/src/components/common/LazyLoad.tsx
new file mode 100644
index 0000000..468234e
--- /dev/null
+++ b/src/components/common/LazyLoad.tsx
@@ -0,0 +1,49 @@
+import React, { useState, useEffect, useRef } from "react";
+import { useInView } from "react-intersection-observer";
+import CircularProgress from "@mui/material/CircularProgress";
+
+interface Props {
+ onLoadMore: () => Promise;
+ isLoading?: boolean;
+}
+
+const LazyLoad: React.FC = ({ onLoadMore, isLoading }) => {
+ const [isFetching, setIsFetching] = useState(false);
+
+ const firstLoad = useRef(false);
+ const [ref, inView] = useInView({
+ threshold: 0.7
+ });
+
+ useEffect(() => {
+ if (inView) {
+ setIsFetching(true);
+ onLoadMore().finally(() => {
+ setIsFetching(false);
+ firstLoad.current = true;
+ });
+ }
+ }, [inView]);
+
+ return (
+
+ );
+};
+
+export default LazyLoad;
diff --git a/src/components/common/Notification/Notification.tsx b/src/components/common/Notification/Notification.tsx
new file mode 100644
index 0000000..501f081
--- /dev/null
+++ b/src/components/common/Notification/Notification.tsx
@@ -0,0 +1,86 @@
+import { useDispatch, useSelector } from 'react-redux'
+import { toast, ToastContainer, Zoom, Slide } from 'react-toastify'
+import { removeNotification } from '../../../state/features/notificationsSlice'
+import 'react-toastify/dist/ReactToastify.css'
+import { RootState } from '../../../state/store'
+
+const Notification = () => {
+ const dispatch = useDispatch()
+
+ const { alertTypes } = useSelector((state: RootState) => state.notifications)
+
+ if (alertTypes.alertError) {
+ toast.error(`❌ ${alertTypes?.alertError}`, {
+ position: 'bottom-right',
+ autoClose: 4000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ icon: false
+ })
+ dispatch(removeNotification())
+ }
+ if (alertTypes.alertSuccess) {
+ toast.success(`✔️ ${alertTypes?.alertSuccess}`, {
+ position: 'bottom-right',
+ autoClose: 4000,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ icon: false
+ })
+ dispatch(removeNotification())
+ }
+ if (alertTypes.alertInfo) {
+ toast.info(`${alertTypes?.alertInfo}`, {
+ position: 'top-right',
+ autoClose: 1300,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: true,
+ draggable: true,
+ progress: undefined,
+ theme: 'light'
+ })
+ dispatch(removeNotification())
+ }
+
+ if (alertTypes.alertInfo) {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+}
+
+export default Notification
diff --git a/src/components/common/NumericTextFieldQshop.tsx b/src/components/common/NumericTextFieldQshop.tsx
new file mode 100644
index 0000000..10d470d
--- /dev/null
+++ b/src/components/common/NumericTextFieldQshop.tsx
@@ -0,0 +1,132 @@
+import { IconButton, InputAdornment, TextField } from "@mui/material";
+import AddIcon from "@mui/icons-material/Add";
+import RemoveIcon from "@mui/icons-material/Remove";
+import React, { useImperativeHandle, useState } from "react";
+
+export enum Variant {
+ filled = "filled",
+ standard = "standard",
+ outlined = "outlined"
+}
+interface TextFieldProps {
+ name: string;
+ label: string;
+ required: boolean;
+ minValue: number;
+ maxValue: number;
+ variant?: Variant;
+ addIconButtons?: boolean;
+ allowDecimals?: boolean;
+ onChangeFunc?: (e: string) => void;
+ initialValue?: string;
+ style?: object;
+ className?: string;
+}
+
+export type NumericTextFieldRef = {
+ getTextFieldValue: () => string;
+};
+
+export const NumericTextFieldQshop = React.forwardRef<
+ NumericTextFieldRef,
+ TextFieldProps
+>(
+ (
+ {
+ name,
+ label,
+ variant,
+ required,
+ style,
+ minValue,
+ maxValue,
+ addIconButtons = true,
+ allowDecimals = true,
+ onChangeFunc,
+ initialValue,
+ className
+ }: TextFieldProps,
+ ref
+ ) => {
+ const [textFieldValue, setTextFieldValue] = useState(
+ initialValue || ""
+ );
+ useImperativeHandle(
+ ref,
+ () => ({
+ getTextFieldValue: () => {
+ return textFieldValue;
+ }
+ }),
+ [textFieldValue]
+ );
+
+ const setMinMaxValue = (value: string): string => {
+ const lastIndexIsDecimal = value.charAt(value.length - 1) === ".";
+ if (lastIndexIsDecimal) return value;
+
+ const valueNum = Number(value);
+
+ // Bounds checking on valueNum
+ let minMaxNum = valueNum;
+ minMaxNum = Math.min(minMaxNum, maxValue);
+ minMaxNum = Math.max(minMaxNum, minValue);
+
+ return minMaxNum === valueNum ? value : minMaxNum.toString();
+ };
+
+ const filterValue = (value: string, emptyReturn = "") => {
+ if (allowDecimals === false) value = value.replace(".", "");
+ if (value === "-1") return emptyReturn;
+
+ const isPositiveNum = /^[0-9]*\.?[0-9]*$/;
+
+ if (isPositiveNum.test(value)) {
+ return setMinMaxValue(value);
+ }
+ return textFieldValue;
+ };
+
+ const changeValueWithButton = (changeAmount: number) => {
+ const valueNum = Number(textFieldValue);
+ const newValue = setMinMaxValue((valueNum + changeAmount).toString());
+ setTextFieldValue(newValue);
+ };
+
+ const listeners = (e: React.ChangeEvent) => {
+ const newValue = filterValue(e.target.value || "-1");
+ setTextFieldValue(newValue);
+ if (onChangeFunc) onChangeFunc(newValue);
+ };
+
+ return (
+
+ changeValueWithButton(1)}>
+ {" "}
+
+ changeValueWithButton(-1)}>
+ {" "}
+
+
+ )
+ }
+ : {}
+ }
+ onChange={(e: React.ChangeEvent) => listeners(e)}
+ autoComplete="off"
+ value={textFieldValue}
+ className={className}
+ />
+ );
+ }
+);
diff --git a/src/components/common/PageLoader.tsx b/src/components/common/PageLoader.tsx
new file mode 100644
index 0000000..e1575d2
--- /dev/null
+++ b/src/components/common/PageLoader.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import CircularProgress from "@mui/material/CircularProgress";
+import Box from "@mui/system/Box";
+import { useTheme } from "@mui/material";
+
+interface PageLoaderProps {
+ size?: number;
+ thickness?: number;
+}
+
+const PageLoader: React.FC = ({
+ size = 40,
+ thickness = 5
+}) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+};
+
+export default PageLoader;
diff --git a/src/components/common/Portal.tsx b/src/components/common/Portal.tsx
new file mode 100644
index 0000000..1e0cb26
--- /dev/null
+++ b/src/components/common/Portal.tsx
@@ -0,0 +1,25 @@
+import React, { useEffect, useState } from 'react'
+import { createPortal } from 'react-dom'
+
+interface PortalProps {
+ children: React.ReactNode
+}
+
+const Portal: React.FC = ({ children }) => {
+ const [mounted, setMounted] = useState(false)
+
+ useEffect(() => {
+ setMounted(true)
+
+ return () => setMounted(false)
+ }, [])
+
+ return mounted
+ ? createPortal(
+ children,
+ document.querySelector('#modal-root') as HTMLElement
+ )
+ : null
+}
+
+export default Portal
diff --git a/src/components/common/PostPublishModal.tsx b/src/components/common/PostPublishModal.tsx
new file mode 100644
index 0000000..d9a9a21
--- /dev/null
+++ b/src/components/common/PostPublishModal.tsx
@@ -0,0 +1,280 @@
+import React, { useState } from 'react'
+import {
+ Box,
+ Button,
+ Modal,
+ TextField,
+ Typography,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ SelectChangeEvent,
+ OutlinedInput,
+ Chip,
+ IconButton
+} from '@mui/material'
+import { styled } from '@mui/system'
+import { useDropzone } from 'react-dropzone'
+import { usePublishVideo } from './PublishVideo'
+import { toBase64 } from '../../utils/toBase64'
+import AddIcon from '@mui/icons-material/Add'
+import CloseIcon from '@mui/icons-material/Close'
+const StyledModal = styled(Modal)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+}))
+
+const ChipContainer = styled(Box)({
+ display: 'flex',
+ flexWrap: 'wrap',
+ '& > *': {
+ margin: '4px'
+ }
+})
+
+const ModalContent = styled(Box)(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ padding: theme.spacing(4),
+ borderRadius: theme.spacing(1),
+ width: '40%',
+ '&:focus': {
+ outline: 'none'
+ }
+}))
+
+interface PostModalProps {
+ open: boolean
+ onClose: () => void
+ onPublish: (value: any) => Promise
+ post: any
+ mode?: string
+ metadata?: any
+}
+
+interface SelectOption {
+ id: string
+ name: string
+}
+
+const PostPublishModal: React.FC = ({
+ open,
+ onClose,
+ onPublish,
+ post,
+ mode,
+ metadata
+}) => {
+ const [file, setFile] = useState(null)
+ const [title, setTitle] = useState('')
+ const [description, setDescription] = useState('')
+ const [selectedOption, setSelectedOption] = useState(
+ null
+ )
+ const [inputValue, setInputValue] = useState('')
+ const [chips, setChips] = useState([])
+
+ const [options, setOptions] = useState([])
+ const [tags, setTags] = useState([])
+ const { publishVideo } = usePublishVideo()
+ const { getRootProps, getInputProps } = useDropzone({
+ accept: {
+ 'video/*': []
+ },
+ maxFiles: 1,
+ onDrop: (acceptedFiles) => {
+ setFile(acceptedFiles[0])
+ }
+ })
+
+ React.useEffect(() => {
+ if (post.title) {
+ setTitle(post.title)
+ }
+ // if (post.description) {
+ // setDescription(post.description)
+ // }
+ }, [post])
+
+ React.useEffect(() => {
+ if (mode === 'edit' && metadata) {
+ if (metadata.description) {
+ setDescription(metadata.description)
+ }
+
+ const findCategory = options.find(
+ (option) => option.id === metadata?.category
+ )
+ if (findCategory) {
+ setSelectedOption(findCategory)
+ }
+
+ if (!metadata?.tags || !Array.isArray(metadata?.tags)) return
+
+ setChips(metadata.tags.slice(0, -2))
+ }
+ }, [mode, metadata, options])
+
+ const handleTitleChange = (event: React.ChangeEvent) => {
+ setTitle(event.target.value)
+ }
+
+ const handleDescriptionChange = (
+ event: React.ChangeEvent
+ ) => {
+ setDescription(event.target.value)
+ }
+
+ const handleOptionChange = (event: SelectChangeEvent) => {
+ const optionId = event.target.value
+ const selectedOption = options.find((option) => option.id === optionId)
+ setSelectedOption(selectedOption || null)
+ }
+
+ const handleChipDelete = (index: number) => {
+ const newChips = [...chips]
+ newChips.splice(index, 1)
+ setChips(newChips)
+ }
+
+ const handleSubmit = async () => {
+ const formattedTags: { [key: string]: string } = {}
+ chips.forEach((tag, i) => {
+ formattedTags[`tag${i + 1}`] = tag
+ })
+
+ try {
+ await onPublish({
+ title,
+ description,
+ tags: chips,
+ category: selectedOption?.id || ''
+ })
+ setFile(null)
+ setTitle('')
+ setDescription('')
+ onClose()
+ } catch (error) {}
+ }
+
+ const handleInputChange = (event: any) => {
+ setInputValue(event.target.value)
+ }
+
+ const handleInputKeyDown = (event: any) => {
+ if (event.key === 'Enter' && inputValue !== '') {
+ if (chips.length < 5) {
+ setChips([...chips, inputValue])
+ setInputValue('')
+ } else {
+ event.preventDefault()
+ }
+ }
+ }
+
+ const addChip = () => {
+ if (chips.length < 3) {
+ setChips([...chips, inputValue])
+ setInputValue('')
+ }
+ }
+
+ const getListCategories = React.useCallback(async () => {
+ try {
+ const url = `/arbitrary/categories`
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ const responseData = await response.json()
+ setOptions(responseData)
+ } catch (error) {}
+ }, [])
+
+ React.useEffect(() => {
+ getListCategories()
+ }, [getListCategories])
+
+ return (
+
+
+
+ Upload Blog Post
+
+
+
+
+ {options.length > 0 && (
+
+ Select a Category
+ }
+ value={selectedOption?.id || ''}
+ onChange={handleOptionChange}
+ >
+ {options.map((option) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {chips.map((chip, index) => (
+ handleChipDelete(index)}
+ deleteIcon={}
+ />
+ ))}
+
+
+
+
+
+ )
+}
+
+export default PostPublishModal
diff --git a/src/components/common/PublishAudio.tsx b/src/components/common/PublishAudio.tsx
new file mode 100644
index 0000000..d9e6952
--- /dev/null
+++ b/src/components/common/PublishAudio.tsx
@@ -0,0 +1,111 @@
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { setNotification } from '../../state/features/notificationsSlice'
+import { RootState } from '../../state/store'
+import ShortUniqueId from 'short-unique-id'
+
+const uid = new ShortUniqueId()
+
+interface IPublishVideo {
+ title: string
+ description: string
+ base64: string
+ category: string
+ editVideoIdentifier?: string | null | undefined
+
+}
+
+export const usePublishAudio = () => {
+ const { user } = useSelector((state: RootState) => state.auth)
+ const dispatch = useDispatch()
+ const publishAudio = async ({
+ editVideoIdentifier,
+ title,
+ description,
+ base64,
+ category,
+ ...rest
+ }: IPublishVideo) => {
+ let address
+ let name
+ let errorMsg = ''
+
+ address = user?.address
+ name = user?.name || ''
+
+ const missingFields = []
+ if (!address) {
+ errorMsg = "Cannot post: your address isn't available"
+ }
+ if (!name) {
+ errorMsg = 'Cannot post without a name'
+ }
+ if (!title) missingFields.push('title')
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ')
+ const errMsg = `Missing: ${missingFieldsString}`
+ errorMsg = errMsg
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: 'error'
+ })
+ )
+ throw new Error(errorMsg)
+ }
+
+ try {
+ const id = uid()
+
+ let identifier = `qaudio_qblog_${id}`
+ if(editVideoIdentifier){
+ identifier = editVideoIdentifier
+ }
+ const resourceResponse = await qortalRequest({
+ action: 'PUBLISH_QDN_RESOURCE',
+ name: name,
+ service: 'AUDIO',
+ data64: base64,
+ title: title,
+ description: description,
+ category: category,
+ ...rest,
+ identifier: identifier
+ })
+ dispatch(
+ setNotification({
+ msg: 'Audio successfully published',
+ alertType: 'success'
+ })
+ )
+ return resourceResponse
+ } catch (error: any) {
+ let notificationObj = null
+ if (typeof error === 'string') {
+ notificationObj = {
+ msg: error || 'Failed to publish audio',
+ alertType: 'error'
+ }
+ } else if (typeof error?.error === 'string') {
+ notificationObj = {
+ msg: error?.error || 'Failed to publish audio',
+ alertType: 'error'
+ }
+ } else {
+ notificationObj = {
+ msg: error?.message || error?.message || 'Failed to publish audio',
+ alertType: 'error'
+ }
+ }
+ if (!notificationObj) return
+ dispatch(setNotification(notificationObj))
+
+ }
+ }
+ return {
+ publishAudio
+ }
+}
diff --git a/src/components/common/PublishGeneric.tsx b/src/components/common/PublishGeneric.tsx
new file mode 100644
index 0000000..48478d0
--- /dev/null
+++ b/src/components/common/PublishGeneric.tsx
@@ -0,0 +1,119 @@
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { setNotification } from '../../state/features/notificationsSlice'
+import { RootState } from '../../state/store'
+import ShortUniqueId from 'short-unique-id'
+
+const uid = new ShortUniqueId()
+
+interface IPublishGeneric {
+ title: string
+ description: string
+ base64: string
+ category: string
+ service: string
+ identifierPrefix: string
+ filename: string
+ editVideoIdentifier?: string | null | undefined
+
+}
+
+export const usePublishGeneric = () => {
+ const { user } = useSelector((state: RootState) => state.auth)
+ const dispatch = useDispatch()
+ const publishGeneric = async ({
+ editVideoIdentifier,
+ service,
+ identifierPrefix,
+ filename,
+ title,
+ description,
+ base64,
+ category,
+ ...rest
+ }: IPublishGeneric) => {
+ let address
+ let name
+ let errorMsg = ''
+
+ address = user?.address
+ name = user?.name || ''
+
+ const missingFields = []
+ if (!address) {
+ errorMsg = "Cannot post: your address isn't available"
+ }
+ if (!name) {
+ errorMsg = 'Cannot post without a name'
+ }
+ if (!title) missingFields.push('title')
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ')
+ const errMsg = `Missing: ${missingFieldsString}`
+ errorMsg = errMsg
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: 'error'
+ })
+ )
+ throw new Error(errorMsg)
+ }
+
+ try {
+ const id = uid()
+
+ let identifier = `${identifierPrefix}_${id}`
+ if(editVideoIdentifier){
+ identifier = editVideoIdentifier
+ }
+
+ const resourceResponse = await qortalRequest({
+ action: 'PUBLISH_QDN_RESOURCE',
+ name: name,
+ service: service,
+ data64: base64,
+ title: title,
+ description: description,
+ category: category,
+ filename,
+ ...rest,
+ identifier: identifier
+ })
+ dispatch(
+ setNotification({
+ msg: `${service} successfully published`,
+ alertType: 'success'
+ })
+ )
+ return resourceResponse
+ } catch (error: any) {
+ let notificationObj = null
+ if (typeof error === 'string') {
+ notificationObj = {
+ msg: error || `Failed to publish ${service}`,
+ alertType: 'error'
+ }
+ } else if (typeof error?.error === 'string') {
+ notificationObj = {
+ msg: error?.error || `Failed to publish ${service}`,
+ alertType: 'error'
+ }
+ } else {
+ notificationObj = {
+ msg:
+ error?.message || error?.message || `Failed to publish ${service}`,
+ alertType: 'error'
+ }
+ }
+ if (!notificationObj) return
+ dispatch(setNotification(notificationObj))
+ }
+ }
+ return {
+ publishGeneric
+ }
+}
diff --git a/src/components/common/PublishVideo.tsx b/src/components/common/PublishVideo.tsx
new file mode 100644
index 0000000..4042994
--- /dev/null
+++ b/src/components/common/PublishVideo.tsx
@@ -0,0 +1,112 @@
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { setNotification } from '../../state/features/notificationsSlice'
+import { RootState } from '../../state/store'
+import ShortUniqueId from 'short-unique-id'
+
+const uid = new ShortUniqueId()
+
+interface IPublishVideo {
+ title: string
+ description: string
+ base64?: string
+ category: string
+ editVideoIdentifier?: string | null | undefined
+ file?: File
+}
+
+export const usePublishVideo = () => {
+ const { user } = useSelector((state: RootState) => state.auth)
+ const dispatch = useDispatch()
+ const publishVideo = async ({
+ file,
+ editVideoIdentifier,
+ title,
+ description,
+ base64,
+ category,
+ ...rest
+ }: IPublishVideo) => {
+ let address
+ let name
+ let errorMsg = ''
+
+ address = user?.address
+ name = user?.name || ''
+
+ const missingFields = []
+ if (!address) {
+ errorMsg = "Cannot post: your address isn't available"
+ }
+ if (!name) {
+ errorMsg = 'Cannot post without a name'
+ }
+ if (!title) missingFields.push('title')
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ')
+ const errMsg = `Missing: ${missingFieldsString}`
+ errorMsg = errMsg
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: 'error'
+ })
+ )
+ throw new Error(errorMsg)
+ }
+
+ try {
+ const id = uid()
+
+ let identifier = `qvideo_qblog_${id}`
+ if (editVideoIdentifier) {
+ identifier = editVideoIdentifier
+ }
+ const resourceResponse = await qortalRequest({
+ action: 'PUBLISH_QDN_RESOURCE',
+ name: name,
+ service: 'VIDEO',
+ // data64: base64,
+ file: file,
+ title: title,
+ description: description,
+ category: category,
+ ...rest,
+ identifier: identifier
+ })
+ dispatch(
+ setNotification({
+ msg: 'Video successfully published',
+ alertType: 'success'
+ })
+ )
+ return resourceResponse
+ } catch (error: any) {
+ let notificationObj = null
+ if (typeof error === 'string') {
+ notificationObj = {
+ msg: error || 'Failed to publish video',
+ alertType: 'error'
+ }
+ } else if (typeof error?.error === 'string') {
+ notificationObj = {
+ msg: error?.error || 'Failed to publish video',
+ alertType: 'error'
+ }
+ } else {
+ notificationObj = {
+ msg: error?.message || 'Failed to publish video',
+ alertType: 'error'
+ }
+ }
+ if (!notificationObj) return
+ dispatch(setNotification(notificationObj))
+ }
+ }
+ return {
+ publishVideo
+ }
+}
diff --git a/src/components/common/ResponsiveImage.tsx b/src/components/common/ResponsiveImage.tsx
new file mode 100644
index 0000000..7308e53
--- /dev/null
+++ b/src/components/common/ResponsiveImage.tsx
@@ -0,0 +1,124 @@
+import React, { useState, useEffect, CSSProperties } from "react";
+import Skeleton from "@mui/material/Skeleton";
+import { Box } from "@mui/material";
+
+interface ResponsiveImageProps {
+ src: string;
+ dimensions: string;
+ alt?: string;
+ className?: string;
+ style?: CSSProperties;
+}
+
+const ResponsiveImage: React.FC = ({
+ src,
+ dimensions,
+ alt,
+ className,
+ style
+}) => {
+ const [loading, setLoading] = useState(true);
+ const matchResult = dimensions?.match(/v1\.(\d+(\.\d+)?)x(\d+)/);
+
+ const width = matchResult ? parseFloat(matchResult[1]) : 1; // Default width value
+ const height = matchResult ? parseInt(matchResult[3], 10) : 1; // Default height value
+
+ const aspectRatio = (height / width) * 100;
+
+ useEffect(() => {
+ if (dimensions === "v1.0x0") {
+ setLoading(false);
+ return;
+ }
+ }, [dimensions]);
+
+ if (dimensions === "v1.0x0" || !dimensions) {
+ return null;
+ }
+
+ const imageStyle: CSSProperties = {
+ width: "100%",
+ height: "100%",
+ objectFit: "cover"
+ };
+
+ const wrapperStyle: CSSProperties = {
+ position: "relative",
+ paddingBottom: `${aspectRatio}%`,
+ overflow: "hidden",
+ ...style
+ };
+
+ return (
+
+ {/* setLoading(false)}
+ src={src}
+ style={{
+ width: '100%',
+ height: 'auto',
+ borderRadius: '8px'
+ }}
+ /> */}
+ {loading && (
+
+ )}
+
+ setLoading(false)}
+ src={src}
+ style={{
+ width: "100%",
+ height: "auto",
+ borderRadius: "8px",
+ visibility: loading ? "hidden" : "visible",
+ position: loading ? "absolute" : "unset"
+ }}
+ />
+
+ );
+
+ return (
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default ResponsiveImage;
diff --git a/src/components/common/TabImageList/TabImageList-styles.tsx b/src/components/common/TabImageList/TabImageList-styles.tsx
new file mode 100644
index 0000000..8b9f401
--- /dev/null
+++ b/src/components/common/TabImageList/TabImageList-styles.tsx
@@ -0,0 +1,24 @@
+import { styled } from "@mui/system";
+import { Box } from "@mui/material";
+
+export const TabImageListStyle = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+ gap: "10px",
+ justifyContent: "center",
+ width: "100%"
+}));
+
+export const TabImageContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ width: "100%",
+ gap: "15px"
+}));
+
+export const TabImageStyle = styled("img")(({ theme }) => ({
+ width: "30%",
+ height: "100%"
+}));
diff --git a/src/components/common/TabImageList/TabImageList.tsx b/src/components/common/TabImageList/TabImageList.tsx
new file mode 100644
index 0000000..ca2138e
--- /dev/null
+++ b/src/components/common/TabImageList/TabImageList.tsx
@@ -0,0 +1,65 @@
+import { useState } from "react";
+import {
+ TabImageContainer,
+ TabImageListStyle,
+ TabImageStyle
+} from "./TabImageList-styles";
+import { Box } from "@mui/material";
+import CSS from "csstype";
+
+export interface TabImageListProps {
+ divStyle?: CSS.Properties;
+ imgStyle?: CSS.Properties;
+ images: string[] | undefined;
+}
+const TabImageList = ({
+ divStyle = {},
+ imgStyle = {},
+ images
+}: TabImageListProps) => {
+ if (images) {
+ const [mainImage, setMainImage] = useState(images[0]);
+ const [imageFocusedIndex, setImageFocusedIndex] = useState(0);
+
+ const imageTabOutlineStyle = {
+ outline: "4px solid #03A9F4"
+ };
+
+ const switchMainImage = (index: number) => {
+ setMainImage(images[index]);
+ setImageFocusedIndex(index);
+ };
+ const imageRow =
+ images.length > 1 ? (
+ images.map((image, index) => (
+ switchMainImage(index)}
+ key={image + index.toString()}
+ />
+ ))
+ ) : (
+
+ );
+
+ const defaultStyle = { width: "100%" };
+ return (
+
+
+
+
+ {imageRow}
+
+ );
+ } else {
+ return ;
+ }
+};
+
+export default TabImageList;
diff --git a/src/components/common/VideoContent.tsx b/src/components/common/VideoContent.tsx
new file mode 100644
index 0000000..f8f2bc1
--- /dev/null
+++ b/src/components/common/VideoContent.tsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import { Box, Typography } from '@mui/material'
+import { styled } from '@mui/system'
+import { Description, Movie } from '@mui/icons-material'
+
+interface VideoProps {
+ title: string
+ description: string
+}
+
+const StyledBox = styled(Box)`
+ margin: 20px 0px;
+ display: flex;
+ align-items: center;
+`
+
+const Title = styled(Typography)``
+
+const DescriptionIcon = styled(Description)`
+ color: #666;
+ margin-right: 0.5rem;
+`
+
+const MovieIcon = styled(Movie)`
+ color: #666;
+ margin-right: 0.5rem;
+`
+
+export const VideoContent: React.FC = ({ title, description }) => {
+ return (
+
+
+
+
+ {title}
+
+
+
+
+ {description}
+
+
+
+ )
+}
diff --git a/src/components/common/VideoPublishModal.tsx b/src/components/common/VideoPublishModal.tsx
new file mode 100644
index 0000000..48b238b
--- /dev/null
+++ b/src/components/common/VideoPublishModal.tsx
@@ -0,0 +1,286 @@
+import React, { useState } from "react";
+import {
+ Box,
+ Button,
+ Modal,
+ TextField,
+ Typography,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ SelectChangeEvent,
+ OutlinedInput,
+ Chip,
+ IconButton
+} from "@mui/material";
+import { styled } from "@mui/system";
+import { useDropzone } from "react-dropzone";
+import { usePublishVideo } from "./PublishVideo";
+import AddIcon from "@mui/icons-material/Add";
+import CloseIcon from "@mui/icons-material/Close";
+const StyledModal = styled(Modal)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center"
+}));
+
+const ChipContainer = styled(Box)({
+ display: "flex",
+ flexWrap: "wrap",
+ "& > *": {
+ margin: "4px"
+ }
+});
+
+const ModalContent = styled(Box)(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ padding: theme.spacing(4),
+ borderRadius: theme.spacing(1),
+ width: "40%",
+ "&:focus": {
+ outline: "none"
+ }
+}));
+
+interface VideoModalProps {
+ open: boolean;
+ onClose: () => void;
+ onPublish: (value: any) => void;
+ editVideoIdentifier?: string | null | undefined;
+}
+
+interface SelectOption {
+ id: string;
+ name: string;
+}
+
+const VideoModal: React.FC = ({
+ open,
+ onClose,
+ onPublish,
+ editVideoIdentifier
+}) => {
+ const [file, setFile] = useState(null);
+ const [title, setTitle] = useState("");
+ const [description, setDescription] = useState("");
+ const [selectedOption, setSelectedOption] = useState(
+ null
+ );
+ const [inputValue, setInputValue] = useState("");
+ const [chips, setChips] = useState([]);
+
+ const [options, setOptions] = useState([]);
+ const [tags, setTags] = useState([]);
+ const { publishVideo } = usePublishVideo();
+ const { getRootProps, getInputProps } = useDropzone({
+ accept: {
+ "video/*": []
+ },
+ maxFiles: 1,
+ onDrop: (acceptedFiles) => {
+ setFile(acceptedFiles[0]);
+ }
+ });
+
+ const handleTitleChange = (event: React.ChangeEvent) => {
+ setTitle(event.target.value);
+ };
+
+ const handleDescriptionChange = (
+ event: React.ChangeEvent
+ ) => {
+ setDescription(event.target.value);
+ };
+
+ const handleOptionChange = (event: SelectChangeEvent) => {
+ const optionId = event.target.value;
+ const selectedOption = options.find((option) => option.id === optionId);
+ setSelectedOption(selectedOption || null);
+ };
+
+ const handleChipDelete = (index: number) => {
+ const newChips = [...chips];
+ newChips.splice(index, 1);
+ setChips(newChips);
+ };
+
+ const handleSubmit = async () => {
+ const missingFields = [];
+
+ if (!title) missingFields.push("title");
+ if (!file) missingFields.push("file");
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(", ");
+ const errMsg = `Missing: ${missingFieldsString}`;
+
+ return;
+ }
+ if (!file) return;
+
+ const formattedTags: { [key: string]: string } = {};
+ chips.forEach((tag, i) => {
+ formattedTags[`tag${i + 1}`] = tag;
+ });
+
+ try {
+ // const base64 = await toBase64(file)
+ // if (typeof base64 !== 'string') return
+ // const base64String = base64.split(',')[1]
+ // if (!file) return
+
+ const res = await publishVideo({
+ file: file,
+ editVideoIdentifier,
+ title,
+ description,
+ category: selectedOption?.id || "",
+ ...formattedTags
+ });
+ onPublish(res);
+ setFile(null);
+ setTitle("");
+ setDescription("");
+ onClose();
+ } catch (error) {}
+ };
+
+ const handleInputChange = (event: any) => {
+ setInputValue(event.target.value);
+ };
+
+ const handleInputKeyDown = (event: any) => {
+ if (event.key === "Enter" && inputValue !== "") {
+ if (chips.length < 5) {
+ setChips([...chips, inputValue]);
+ setInputValue("");
+ } else {
+ event.preventDefault();
+ }
+ }
+ };
+
+ const addChip = () => {
+ if (chips.length < 5) {
+ setChips([...chips, inputValue]);
+ setInputValue("");
+ }
+ };
+
+ const getListCategories = React.useCallback(async () => {
+ try {
+ const url = `/arbitrary/categories`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ const responseData = await response.json();
+ setOptions(responseData);
+ } catch (error) {}
+ }, []);
+
+ React.useEffect(() => {
+ getListCategories();
+ }, [getListCategories]);
+
+ return (
+
+
+ {editVideoIdentifier && (
+
+ You are editing: {editVideoIdentifier}
+
+ )}
+
+ Upload Video
+
+
+
+
+ {file
+ ? file.name
+ : "Drag and drop a video file here or click to select a file"}
+
+
+
+
+ {options.length > 0 && (
+
+ Select a Category
+ }
+ value={selectedOption?.id || ""}
+ onChange={handleOptionChange}
+ >
+ {options.map((option) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {chips.map((chip, index) => (
+ handleChipDelete(index)}
+ deleteIcon={}
+ />
+ ))}
+
+
+
+
+
+ );
+};
+
+export default VideoModal;
diff --git a/src/components/editor/BlogEditor.css b/src/components/editor/BlogEditor.css
new file mode 100644
index 0000000..0a3e0e8
--- /dev/null
+++ b/src/components/editor/BlogEditor.css
@@ -0,0 +1,78 @@
+/* src/components/BlogEditor.css */
+.blog-editor {
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 1rem;
+ line-height: 1.5;
+ font-size: 18px;
+ max-height: 50vh;
+ overflow-y: auto;
+ min-height: 200px;
+ z-index: 500;
+ }
+
+ .toolbar {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1rem;
+ }
+
+
+
+ .toolbar-button:focus {
+ outline: none;
+ }
+
+ .code-block {
+ background-color: #2c2b31;
+ color: rgb(238, 234, 234);
+ border-radius: 3px;
+ padding: 10px;
+ margin: 10px 0;
+ font-family: 'Courier New', Courier, monospace;
+ white-space: pre-wrap;
+ overflow-x: auto;
+ max-width: 100%;
+ font-size: 14px;
+ }
+
+ .paragraph {
+ font-size: 20px;
+ margin: 0px;
+ }
+
+ .paragraph-mail {
+ font-size: 16px;
+ margin: 0px;
+ }
+
+ .toolbar-button {
+ background-color: white;
+ border: 1px solid gray;
+ border-radius: 5px;
+ margin-right: 5px;
+ cursor: pointer;
+ outline: none;
+ height: 32px;
+ width: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .toolbar-button.active {
+ background-color: lightgray;
+ }
+
+ .h2 {
+ font-size: 25px
+ }
+
+ .h2 {
+ font-size: 22px
+ }
+
+ .align-center {
+ text-align: center;
+ }
+
diff --git a/src/components/editor/BlogEditor.tsx b/src/components/editor/BlogEditor.tsx
new file mode 100644
index 0000000..0da146c
--- /dev/null
+++ b/src/components/editor/BlogEditor.tsx
@@ -0,0 +1,574 @@
+// src/components/BlogEditor.tsx
+// @ts-nocheck
+
+import React, { useMemo, useState, useCallback } from 'react';
+import { createEditor, Descendant, Editor, Transforms, Range } from 'slate'
+import SvgIcon from '@material-ui/core/SvgIcon'
+import {
+ Slate,
+ Editable,
+ withReact,
+ RenderElementProps,
+ RenderLeafProps,
+ useSlate
+} from 'slate-react'
+import { styled } from '@mui/system'
+import { CustomElement, CustomText, FormatMark } from './customTypes'
+import './BlogEditor.css'
+import { Modal, Box, TextField, Button } from '@mui/material'
+
+import { AlignCenterSVG } from '../../assets/svgs/AlignCenterSVG'
+import { BoldSVG } from '../../assets/svgs/BoldSVG'
+import { ItalicSVG } from '../../assets/svgs/ItalicSVG'
+import { UnderlineSVG } from '../../assets/svgs/UnderlineSVG'
+import { H2SVG } from '../../assets/svgs/H2SVG'
+import { H3SVG } from '../../assets/svgs/H3SVG'
+import { AlignLeftSVG } from '../../assets/svgs/AlignLeftSVG'
+import { AlignRightSVG } from '../../assets/svgs/AlignRightSVG'
+import { CodeBlockSVG } from '../../assets/svgs/CodeBlockSVG'
+import { LinkSVG } from '../../assets/svgs/LinkSVG'
+
+const initialValue: Descendant[] = [
+ {
+ type: 'paragraph',
+ children: [{ text: 'Start writing your blog post...' }]
+ }
+]
+
+interface MyComponentProps {
+ addPostSection?: (value: any) => void
+ editPostSection?: (value: any, section: any) => void
+ defaultValue?: any
+ section?: any
+ value: any
+ setValue: (value: any) => void
+ editorKey?: number
+ mode?: string
+}
+
+const ModalBox = styled(Box)(({ theme }) => ({
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: theme.shadows[5],
+ padding: theme.spacing(2, 4, 3),
+ gap: '15px',
+ borderRadius: '5px',
+ alignItems: 'center',
+ display: 'flex',
+ flex: 0
+}))
+
+const BlogEditor: React.FC = ({
+ addPostSection,
+ editPostSection,
+ defaultValue,
+ section,
+ value,
+ setValue,
+ editorKey,
+ mode
+}) => {
+ const editor = useMemo(() => withReact(createEditor()), [])
+
+ // const [value, setValue] = useState(defaultValue || initialValue);
+ const isTextAlignmentActive = (editor: Editor, alignment: string) => {
+ const [match] = Editor.nodes(editor, {
+ match: (n) => {
+ return n?.textAlign === alignment?.replace(/^align-/, '')
+ }
+ })
+ return !!match
+ }
+
+ const toggleTextAlignment = (editor: Editor, alignment: string) => {
+ const isActive = isTextAlignmentActive(editor, alignment)
+ Transforms.setNodes(
+ editor,
+ { style: { textAlign: isActive ? 'inherit' : alignment } },
+ { match: (n) => Editor.isBlock(editor, n) }
+ )
+ }
+
+ const toggleMark = (editor: Editor, format: FormatMark) => {
+ if (
+ format === 'align-left' ||
+ format === 'align-center' ||
+ format === 'align-right'
+ ) {
+ toggleTextAlignment(editor, format)
+ } else {
+ const isActive = Editor?.marks(editor)?.[format] === true
+ if (isActive) {
+ Editor?.removeMark(editor, format)
+ } else {
+ Editor?.addMark(editor, format, true)
+ }
+ }
+ }
+
+ const newValue = useMemo(() => [...(value || initialValue)], [value])
+
+ const types = ['paragraph', 'heading-2', 'heading-3']
+
+ const setTextAlignment = (editor, alignment) => {
+ const isActive = isTextAlignmentActive(editor, alignment)
+ const alignmentType = ''
+ Transforms?.setNodes(
+ editor,
+ {
+ textAlign: isActive ? null : alignment
+ },
+ {
+ match: (n) =>
+ n.type === 'heading-2' ||
+ n.type === 'heading-3' ||
+ n.type === 'paragraph'
+ }
+ )
+ }
+
+ const ToolbarButton: React.FC<{
+ format: FormatMark | string
+ label: string
+ editor: Editor
+ children: React.ReactNode
+ }> = ({ format, label, editor, children }) => {
+ useSlate()
+
+ let onClick = () => {
+ if (format === 'heading-2' || format === 'heading-3') {
+ toggleBlock(editor, format)
+ } else if (
+ format === 'bold' ||
+ format === 'italic' ||
+ format === 'underline' ||
+ format === ''
+ ) {
+ toggleMark(editor, format)
+ } else if (
+ format === 'align-left' ||
+ format === 'align-center' ||
+ format === 'align-right'
+ ) {
+ setTextAlignment(editor, format?.replace(/^align-/, ''))
+ }
+ }
+
+ let isActive = false
+
+ try {
+ if (
+ format === 'align-left' ||
+ format === 'align-center' ||
+ format === 'align-right'
+ ) {
+ isActive = isTextAlignmentActive(editor, format)
+ } else if (format === 'heading-2' || format === 'heading-3') {
+ isActive = isBlockActive(editor, format)
+ } else if (
+ format === 'bold' ||
+ format === 'italic' ||
+ format === 'underline' ||
+ format === ''
+ ) {
+ isActive = Editor?.marks(editor)?.[format] === true
+ }
+ } catch (error) {}
+
+ return (
+
+ )
+ }
+
+ const ToolbarButtonCodeBlock: React.FC<{
+ format: FormatMark | string
+ label: string
+ editor: Editor
+ children: React.ReactNode
+ }> = ({ format, label, editor, children }) => {
+ const editor2 = useSlate()
+
+ let onClick = () => {
+ if (format === 'code-block') {
+ toggleBlock(editor, 'code-block')
+ }
+ }
+ let isActive = false
+ try {
+ if (format === 'code-block') {
+ isActive = isBlockActive(editor, format)
+ }
+ } catch (error) {}
+
+ return (
+
+ )
+ }
+
+ const ToolbarButtonAlign: React.FC<{
+ format: string
+ label: string
+ editor: Editor
+ }> = ({ format, label, editor }) => {
+ const isActive =
+ Editor?.nodes(editor, {
+ match: (n) => n?.align === format
+ })?.length > 0
+
+ return (
+
+ )
+ }
+
+ const ToolbarButtonCodeLink: React.FC<{
+ format: FormatMark | string
+ label: string
+ editor: Editor
+ children: React.ReactNode
+ }> = ({ format, label, editor, children }) => {
+ useSlate()
+
+ let isActive = false
+ try {
+ if (format === 'link') {
+ isActive = !!Editor?.marks(editor)?.link
+ }
+ } catch (error) {}
+
+ return (
+
+ )
+ }
+
+ // Create a toggleBlock function and an isBlockActive function to handle block elements
+ const toggleBlock = (editor: Editor, format: string) => {
+ const isActive = isBlockActive(editor, format)
+ Transforms?.unwrapNodes(editor, {
+ match: (n) => Editor?.isBlock(editor, n),
+ split: true
+ })
+
+ if (isActive) {
+ Transforms?.setNodes(editor, { type: 'paragraph' })
+ } else {
+ Transforms?.setNodes(editor, { type: format })
+ }
+ }
+
+ const isBlockActive = (editor: Editor, format: string) => {
+ const [match] = Editor?.nodes(editor, {
+ match: (n) => n?.type === format
+ })
+ return !!match
+ }
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter' && isBlockActive(editor, 'code-block')) {
+ event.preventDefault()
+ editor?.insertText('\n')
+ }
+
+ if (event.key === 'ArrowDown' && isBlockActive(editor, 'code-block')) {
+ event.preventDefault()
+ Transforms?.insertNodes(editor, {
+ type: 'paragraph',
+ children: [{ text: '' }]
+ })
+ }
+ }
+
+ const handleChange = (newValue: Descendant[]) => {
+ setValue(newValue)
+ }
+
+ const toggleLink = (editor: Editor, url: string) => {
+ const { selection } = editor
+
+ if (selection && !Range.isCollapsed(selection)) {
+ const isLink = Editor?.marks(editor)?.link === true
+ const isInsideLink = isLinkActive(editor)
+
+ if (isLink) {
+ Editor?.removeMark(editor, 'link')
+ } else if (url) {
+ Editor?.addMark(editor, 'link', url)
+ }
+ }
+ }
+
+ const [open, setOpen] = useState(false)
+
+ const initialValue = 'qortal://'
+ const [inputValue, setInputValue] = useState(initialValue)
+
+ const handleChangeLink = (event) => {
+ const newValue = event?.target?.value
+ if (newValue?.startsWith(initialValue)) {
+ setInputValue(newValue)
+ }
+ }
+ const isLinkActive = (editor: Editor) => {
+ const [link] = Editor?.nodes(editor, {
+ match: (n) => n?.type === 'link'
+ })
+ return !!link
+ }
+ const handleSaveClick = () => {
+ const marks = Editor?.marks(editor)
+ const isLink = marks?.link === true
+
+ if (isLink) {
+ Editor?.removeMark(editor, 'link')
+ return // Return early to skip the rest of the function
+ }
+ toggleLink(editor, inputValue)
+ setOpen(false)
+ }
+
+ const onClose = () => {
+ setOpen(false)
+ }
+
+ const handlePaste = (event: React.ClipboardEvent) => {
+ event.preventDefault()
+ const text = event?.clipboardData?.getData('text/plain')
+ const isCodeBlock = isBlockActive(editor, 'code-block')
+
+ if (isCodeBlock) {
+ const lines = text?.split('\n')
+ const fragment: Descendant[] = [
+ {
+ type: 'code-block',
+ children: lines?.map((line) => ({
+ type: 'code-line',
+ children: [{ text: line }]
+ }))
+ }
+ ]
+
+ Transforms?.insertFragment(editor, fragment)
+ } else if (text) {
+ const fragment = text?.split('\n').map((line) => ({
+ type: 'paragraph',
+ children: [{ text: line }]
+ }))
+
+ Transforms?.insertFragment(editor, fragment)
+ }
+ }
+
+ return (
+
+ handleChange(newValue)}
+ key={editorKey || 1}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ renderElement({ ...props, mode })}
+ renderLeaf={renderLeaf}
+ onKeyDown={handleKeyDown}
+ onPaste={handlePaste}
+ mode={mode}
+ />
+
+
+
+
+
+
+
+ {editPostSection && (
+
+ )}
+
+ )
+}
+
+export default BlogEditor
+
+type ExtendedRenderElementProps = RenderElementProps & { mode?: string }
+
+export const renderElement = ({
+ attributes,
+ children,
+ element,
+ mode
+}: ExtendedRenderElementProps) => {
+ switch (element.type) {
+ case 'block-quote':
+ return {children}
+ case 'heading-2':
+ return (
+
+ {children}
+
+ )
+ case 'heading-3':
+ return (
+
+ {children}
+
+ )
+ case 'code-block':
+ return (
+
+ {children}
+
+ )
+ case 'code-line':
+ return {children}
+ case 'link':
+ return (
+
+ {children}
+
+ )
+ default:
+ return (
+
+ {children}
+
+ )
+ }
+}
+
+
+export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
+ let el = children
+
+ if (leaf.bold) {
+ el = {el}
+ }
+
+ if (leaf.italic) {
+ el = {el}
+ }
+
+ if (leaf.underline) {
+ el = {el}
+ }
+
+ if (leaf.link) {
+ el = (
+
+ {el}
+
+ )
+ }
+
+ return {el}
+}
\ No newline at end of file
diff --git a/src/components/editor/ReadOnlySlate.tsx b/src/components/editor/ReadOnlySlate.tsx
new file mode 100644
index 0000000..cd2616c
--- /dev/null
+++ b/src/components/editor/ReadOnlySlate.tsx
@@ -0,0 +1,25 @@
+import React, { useMemo } from 'react';
+import { createEditor, Descendant, Editor } from 'slate';
+import { withReact, Slate, Editable, RenderElementProps, RenderLeafProps } from 'slate-react';
+import { renderElement, renderLeaf } from './BlogEditor';
+
+interface ReadOnlySlateProps {
+ content: any
+ mode?: string
+}
+const ReadOnlySlate: React.FC = ({ content, mode }) => {
+ const editor = useMemo(() => withReact(createEditor()), [])
+ const value = useMemo(() => content, [content])
+
+ return (
+ {}}>
+ renderElement({ ...props, mode })}
+ renderLeaf={renderLeaf}
+ />
+
+ )
+}
+
+export default ReadOnlySlate;
\ No newline at end of file
diff --git a/src/components/editor/customTypes.ts b/src/components/editor/customTypes.ts
new file mode 100644
index 0000000..46142fb
--- /dev/null
+++ b/src/components/editor/customTypes.ts
@@ -0,0 +1,47 @@
+// src/customTypes.ts
+import { BaseEditor } from 'slate';
+import { ReactEditor } from 'slate-react';
+
+export type CustomText = {
+ text: string
+ bold?: boolean
+ italic?: boolean
+ underline?: boolean
+ code?: boolean
+}
+
+export type HeadingElement = {
+ type: 'heading'
+ children: CustomText[]
+}
+
+export type BlockQuoteElement = {
+ type: 'block-quote'
+ children: CustomText[]
+}
+
+export type ParagraphElement = {
+ type: 'paragraph'
+ children: CustomText[]
+}
+
+export type CodeBlockElement = {
+ type: 'code-block'
+ children: CustomText[]
+}
+
+export type CustomElement =
+ | HeadingElement
+ | BlockQuoteElement
+ | ParagraphElement
+ | CodeBlockElement
+
+export type FormatMark = 'bold' | 'italic' | 'underline' | 'code'
+
+declare module 'slate' {
+ interface CustomTypes {
+ Editor: BaseEditor & ReactEditor;
+ Element: CustomElement;
+ Text: CustomText;
+ }
+}
diff --git a/src/components/layout/Navbar/Navbar-styles.tsx b/src/components/layout/Navbar/Navbar-styles.tsx
new file mode 100644
index 0000000..f8a45b8
--- /dev/null
+++ b/src/components/layout/Navbar/Navbar-styles.tsx
@@ -0,0 +1,211 @@
+import { AppBar, Button, Typography, Box, Popover } from "@mui/material";
+import { styled } from "@mui/system";
+import { LightModeSVG } from "../../../assets/svgs/LightModeSVG";
+import { DarkModeSVG } from "../../../assets/svgs/DarkModeSVG";
+import { StorefrontSVG } from "../../../assets/svgs/StorefrontSVG";
+import { CartSVG } from "../../../assets/svgs/CartSVG";
+
+export const CustomAppBar = styled(AppBar)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ width: "100%",
+ padding: "5px 16px",
+ backgroundImage: "none",
+ borderBottom: `1px solid ${theme.palette.primary.light}`,
+ backgroundColor: theme.palette.background.default,
+ [theme.breakpoints.only("xs")]: {
+ gap: "15px"
+ }
+}));
+
+export const QShopLogoContainer = styled("img")({
+ width: "12%",
+ minWidth: "50px",
+ height: "auto",
+ padding: "2px 0",
+ userSelect: "none",
+ objectFit: "contain",
+ cursor: "pointer"
+});
+
+export const CustomTitle = styled(Typography)({
+ fontWeight: 600,
+ color: "#000000"
+});
+
+export const StoreManagerIcon = styled(StorefrontSVG)(({ theme }) => ({
+ cursor: "pointer",
+ "&:hover": {
+ filter:
+ theme.palette.mode === "dark"
+ ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
+ : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))"
+ }
+}));
+
+export const CartIcon = styled(CartSVG)(({ theme }) => ({
+ cursor: "pointer",
+ "&:hover": {
+ filter:
+ theme.palette.mode === "dark"
+ ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
+ : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))"
+ }
+}));
+
+export const CreateBlogButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ padding: "8px 15px",
+ borderRadius: "40px",
+ gap: "4px",
+ backgroundColor: theme.palette.secondary.main,
+ color: "#fff",
+ fontFamily: "Raleway",
+ transition: "all 0.3s ease-in-out",
+ boxShadow: "none",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;",
+ backgroundColor: theme.palette.secondary.main,
+ filter: "brightness(1.1)"
+ }
+}));
+
+export const AuthenticateButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ padding: "8px 15px",
+ borderRadius: "40px",
+ gap: "4px",
+ backgroundColor: theme.palette.secondary.main,
+ color: "#fff",
+ fontFamily: "Raleway",
+ transition: "all 0.3s ease-in-out",
+ boxShadow: "none",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;",
+ backgroundColor: theme.palette.secondary.dark,
+ filter: "brightness(1.1)"
+ }
+}));
+
+export const AvatarContainer = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ "&:hover": {
+ cursor: "pointer",
+ "& #expand-icon": {
+ transition: "all 0.3s ease-in-out",
+ filter: "brightness(0.7)"
+ }
+ }
+});
+
+export const DropdownContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ backgroundColor: theme.palette.background.paper,
+ padding: "10px 15px",
+ transition: "all 0.4s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ filter:
+ theme.palette.mode === "light" ? "brightness(0.95)" : "brightness(1.7)"
+ }
+}));
+
+export const DropdownText = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ color: theme.palette.text.primary,
+ userSelect: "none"
+}));
+
+export const NavbarName = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "18px",
+ color: theme.palette.text.primary,
+ margin: "0 10px"
+}));
+
+export const ThemeSelectRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ flexBasis: 0
+});
+
+export const LightModeIcon = styled(LightModeSVG)(({ theme }) => ({
+ transition: "all 0.1s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ filter:
+ theme.palette.mode === "dark"
+ ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
+ : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))"
+ }
+}));
+
+export const DarkModeIcon = styled(DarkModeSVG)(({ theme }) => ({
+ transition: "all 0.1s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ filter:
+ theme.palette.mode === "dark"
+ ? "drop-shadow(0px 4px 6px rgba(255, 255, 255, 0.6))"
+ : "drop-shadow(0px 4px 6px rgba(99, 88, 88, 0.1))"
+ }
+}));
+
+export const StoresButton = styled(Button)(({ theme }) => ({
+ backgroundColor: theme.palette.secondary.main,
+ textTransform: "none",
+ fontFamily: "Raleway",
+ gap: "5px",
+ fontSize: "17px",
+ borderRadius: "5px",
+ border: "none",
+ color: theme.palette.text.primary,
+ padding: "2px 15px",
+ transition: "all 0.3s ease-in-out",
+ boxShadow:
+ "rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.secondary.dark,
+ boxShadow:
+ "rgba(50, 50, 93, 0.35) 0px 3px 5px -1px, rgba(0, 0, 0, 0.4) 0px 2px 3px -1px;"
+ }
+}));
+
+export const CustomPopover = styled(Popover)(({ theme }) => ({
+ maxHeight: "400px",
+ overflowY: "auto",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar": {
+ width: "8px",
+ height: "10px",
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f"
+ }
+}));
diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx
new file mode 100644
index 0000000..88b6282
--- /dev/null
+++ b/src/components/layout/Navbar/Navbar.tsx
@@ -0,0 +1,288 @@
+import React, { useRef, useState } from "react";
+import { RootState } from "../../../state/store";
+import { useSelector } from "react-redux";
+import { Box, Popover, useTheme } from "@mui/material";
+import ExitToAppIcon from "@mui/icons-material/ExitToApp";
+import { useNavigate } from "react-router-dom";
+import {
+ resetProducts,
+ toggleCreateStoreModal
+} from "../../../state/features/globalSlice";
+import { useDispatch } from "react-redux";
+import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal";
+import EmailIcon from "@mui/icons-material/Email";
+import {
+ AvatarContainer,
+ CustomAppBar,
+ DropdownContainer,
+ DropdownText,
+ AuthenticateButton,
+ NavbarName,
+ LightModeIcon,
+ DarkModeIcon,
+ ThemeSelectRow,
+ QShopLogoContainer,
+ StoreManagerIcon,
+ StoresButton
+} from "./Navbar-styles";
+import { AccountCircleSVG } from "../../../assets/svgs/AccountCircleSVG";
+import QShopLogo from "../../../assets/img/QShopLogo.webp";
+import QShopLogoLight from "../../../assets/img/QShopLogoLight.webp";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import PersonOffIcon from "@mui/icons-material/PersonOff";
+
+import { Store } from "../../../state/features/storeSlice";
+import { OrdersSVG } from "../../../assets/svgs/OrdersSVG";
+import { resetOrders } from "../../../state/features/orderSlice";
+interface Props {
+ isAuthenticated: boolean;
+ userName: string | null;
+ userAvatar: string;
+ authenticate: () => void;
+ hasAttemptedToFetchShopInitial: boolean;
+ setTheme: (val: string) => void;
+}
+
+const NavBar: React.FC = ({
+ isAuthenticated,
+ userName,
+ userAvatar,
+ authenticate,
+ hasAttemptedToFetchShopInitial,
+ setTheme
+}) => {
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const theme = useTheme();
+
+ // Get All My Stores from Redux To Display In Store Manager Dropdown
+
+ const myStores = useSelector((state: RootState) => state.store.myStores);
+ const hashMapStores = useSelector(
+ (state: RootState) => state.store.hashMapStores
+ );
+
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [isOpenBlockedNamesModal, setIsOpenBlockedNamesModal] =
+ useState(false);
+ const [openStoreManagerDropdown, setOpenStoreManagerDropdown] =
+ useState(false);
+ const [openUserDropdown, setOpenUserDropdown] = useState(false);
+
+ const searchValRef = useRef("");
+ const inputRef = useRef(null);
+
+ const handleClick = (event?: React.MouseEvent) => {
+ const target = event?.currentTarget as unknown as HTMLButtonElement | null;
+ setAnchorEl(target);
+ };
+
+ const handleCloseUserDropdown = () => {
+ setAnchorEl(null);
+ setOpenUserDropdown(false);
+ };
+
+ const handleCloseStoreDropdown = () => {
+ setAnchorEl(null);
+ setOpenStoreManagerDropdown(false);
+ };
+
+ const onCloseBlockedNames = () => {
+ setIsOpenBlockedNamesModal(false);
+ };
+
+ return (
+
+
+ {theme.palette.mode === "dark" ? (
+ setTheme("light")}
+ color="white"
+ height="22"
+ width="22"
+ />
+ ) : (
+ setTheme("dark")}
+ color="black"
+ height="22"
+ width="22"
+ />
+ )}
+ {
+ navigate(`/`);
+ searchValRef.current = "";
+ if (!inputRef.current) return;
+ inputRef.current.value = "";
+ }}
+ />
+
+
+ {!isAuthenticated && (
+
+
+ Authenticate
+
+ )}
+ {isAuthenticated && userName && hasAttemptedToFetchShopInitial && (
+ {
+ if (myStores.length > 0) {
+ handleClick(e);
+ setOpenStoreManagerDropdown(true);
+ } else {
+ dispatch(toggleCreateStoreModal(true));
+ }
+ }}
+ >
+ My Stores
+
+
+ )}
+ {isAuthenticated && userName && (
+ <>
+ {
+ handleClick(e);
+ setOpenUserDropdown(true);
+ }}
+ >
+ {userName}
+ {!userAvatar ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ )}
+
+
+ {
+ dispatch(toggleCreateStoreModal(true));
+ handleCloseStoreDropdown();
+ }}
+ >
+ Create Store
+
+
+ {myStores.length > 0 &&
+ myStores.map((store: Store) => (
+
+ {
+ dispatch(resetOrders());
+ dispatch(resetProducts());
+ navigate(`/${userName}/${store.id}`);
+ handleCloseStoreDropdown();
+ }}
+ >
+ {hashMapStores[store.id]?.title}
+
+
+ ))}
+
+
+ {
+ handleCloseUserDropdown();
+ handleCloseStoreDropdown();
+ navigate("/my-orders");
+ }}
+ >
+
+ My Orders
+
+ {
+ setIsOpenBlockedNamesModal(true);
+ handleCloseUserDropdown();
+ handleCloseStoreDropdown();
+ }}
+ >
+
+ Blocked Names
+
+
+
+
+
+ Q-Mail
+
+
+
+ {isOpenBlockedNamesModal && (
+
+ )}
+
+
+ );
+};
+
+export default NavBar;
diff --git a/src/components/modals/ConsentModal.tsx b/src/components/modals/ConsentModal.tsx
new file mode 100644
index 0000000..b65be72
--- /dev/null
+++ b/src/components/modals/ConsentModal.tsx
@@ -0,0 +1,70 @@
+import * as React from "react";
+import Button from "@mui/material/Button";
+import Dialog from "@mui/material/Dialog";
+import DialogActions from "@mui/material/DialogActions";
+import DialogContent from "@mui/material/DialogContent";
+import DialogContentText from "@mui/material/DialogContentText";
+import DialogTitle from "@mui/material/DialogTitle";
+import localForage from "localforage";
+import { useTheme } from "@mui/material";
+const generalLocal = localForage.createInstance({
+ name: "q-blog-general"
+});
+
+export default function ConsentModal() {
+ const theme = useTheme();
+
+ const [open, setOpen] = React.useState(false);
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const getIsConsented = React.useCallback(async () => {
+ try {
+ const hasConsented = await generalLocal.getItem("general-consent");
+ if (hasConsented) return;
+
+ setOpen(true);
+ generalLocal.setItem("general-consent", true);
+ } catch (error) {}
+ }, []);
+
+ React.useEffect(() => {
+ getIsConsented();
+ }, []);
+ return (
+
+
+
+ );
+}
diff --git a/src/components/modals/CreateStoreModal-styles.tsx b/src/components/modals/CreateStoreModal-styles.tsx
new file mode 100644
index 0000000..890728e
--- /dev/null
+++ b/src/components/modals/CreateStoreModal-styles.tsx
@@ -0,0 +1,197 @@
+import { styled } from "@mui/system";
+import { Box, Button, TextField, Theme, Typography } from "@mui/material";
+import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
+import { TimesSVG } from "../../assets/svgs/TimesSVG";
+import { NumericTextFieldQshop } from "../common/NumericTextFieldQshop";
+import { DownloadSVG } from "../../assets/svgs/DownloadSVG";
+
+export const ModalBody = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ backgroundColor: theme.palette.background.default,
+ borderRadius: "4px",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+ width: "75%",
+ padding: "15px 35px",
+ display: "flex",
+ flexDirection: "column",
+ gap: "17px",
+ overflowY: "auto",
+ maxHeight: "95vh",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)"
+ : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: theme.palette.background.paper,
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: theme.palette.background.paper,
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646",
+ },
+}));
+
+export const ModalTitle = styled(Typography)(({ theme }) => ({
+ fontWeight: 400,
+ fontFamily: "Raleway",
+ fontSize: "25px",
+ userSelect: "none",
+}));
+
+export const StoreLogoPreview = styled("img")(({ theme }) => ({
+ width: "100px",
+ height: "100px",
+ objectFit: "contain",
+ userSelect: "none",
+ borderRadius: "3px",
+ marginBottom: "10px",
+}));
+
+export const AddLogoButton = styled(Button)(({ theme }) => ({
+ backgroundColor: theme.palette.secondary.main,
+ color: "#fff",
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ padding: "5px 10px",
+ borderRadius: "5px",
+ gap: "5px",
+ border: "none",
+ transition: "all 0.3s ease-in-out",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)"
+ : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
+ marginBottom: "5px",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)"
+ : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
+ backgroundColor: theme.palette.secondary.dark,
+ },
+}));
+
+export const LogoPreviewRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+}));
+
+export const AddLogoIcon = styled(AddPhotoAlternateIcon)(({ theme }) => ({
+ color: "#fff",
+ height: "25px",
+ width: "auto",
+}));
+
+export const TimesIcon = styled(TimesSVG)(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: "50%",
+ padding: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ scale: "1.1",
+ },
+}));
+
+const customInputStyle = (theme: Theme) => {
+ return {
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.background.default,
+ borderColor: theme.palette.background.paper,
+ "& label": {
+ color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ letterSpacing: "0px",
+ },
+ "& label.Mui-focused": {
+ color: theme.palette.mode === "light" ? "#A0AAB4" : "#d7d8da",
+ },
+ "& .MuiInput-underline:after": {
+ borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
+ },
+ "& .MuiOutlinedInput-root": {
+ "& fieldset": {
+ borderColor: "#E0E3E7",
+ },
+ "&:hover fieldset": {
+ borderColor: "#B2BAC2",
+ },
+ "&.Mui-focused fieldset": {
+ borderColor: "#6F7E8C",
+ },
+ },
+ "& .MuiInputBase-root": {
+ fontFamily: "Karla",
+ fontSize: "18px",
+ letterSpacing: "0px",
+ },
+ "& .MuiFilledInput-root:after": {
+ borderBottomColor: theme.palette.secondary.main,
+ },
+ };
+};
+
+export const CustomInputField = styled(TextField)(({ theme }) =>
+ customInputStyle(theme as Theme)
+);
+
+export const CustomNumberField = styled(NumericTextFieldQshop)(({ theme }) =>
+ customInputStyle(theme as Theme)
+);
+
+export const ButtonRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ gap: "10px",
+ justifyContent: "flex-end",
+}));
+
+export const CancelButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+}));
+
+export const CreateButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ backgroundColor: "#32d43a",
+ color: "black",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#2bb131",
+ },
+}));
+
+export const WalletRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+}));
+
+export const DownloadArrrWalletIcon = styled(DownloadSVG)(({ theme }) => ({
+ padding: "5px 7px",
+ borderRadius: "50%",
+ backgroundColor: theme.palette.background.paper,
+ "&:hover": {
+ cursor: "pointer",
+ },
+}));
diff --git a/src/components/modals/CreateStoreModal.tsx b/src/components/modals/CreateStoreModal.tsx
new file mode 100644
index 0000000..9635199
--- /dev/null
+++ b/src/components/modals/CreateStoreModal.tsx
@@ -0,0 +1,419 @@
+import { FC, ChangeEvent, useState, useEffect } from "react";
+import {
+ Typography,
+ Modal,
+ FormControl,
+ useTheme,
+ IconButton,
+ Zoom,
+ Tooltip,
+} from "@mui/material";
+import { useDispatch } from "react-redux";
+import { setIsLoadingGlobal, toggleCreateStoreModal } from "../../state/features/globalSlice";
+import ImageUploader from "../common/ImageUploader";
+import {
+ ModalTitle,
+ StoreLogoPreview,
+ AddLogoButton,
+ AddLogoIcon,
+ TimesIcon,
+ LogoPreviewRow,
+ CustomInputField,
+ ModalBody,
+ ButtonRow,
+ CancelButton,
+ CreateButton,
+ WalletRow,
+ DownloadArrrWalletIcon,
+} from "./CreateStoreModal-styles";
+import {
+ FilterSelect,
+ FilterSelectMenuItems,
+ FiltersCheckbox,
+ FiltersChip,
+ FiltersOption,
+} from "../../pages/Store/Store/Store-styles";
+import { supportedCoinsArray } from "../../constants/supported-coins";
+import { QortalSVG } from "../../assets/svgs/QortalSVG";
+import { ARRRSVG } from "../../assets/svgs/ARRRSVG";
+import { setNotification } from "../../state/features/notificationsSlice";
+export interface ForeignCoins {
+ [key: string]: string;
+}
+
+export interface onPublishParam {
+ title: string;
+ description: string;
+ shipsTo: string;
+ location: string;
+ storeIdentifier: string;
+ logo: string;
+ foreignCoins: ForeignCoins;
+ supportedCoins: string[];
+}
+
+interface CreateStoreModalProps {
+ open: boolean;
+ closeCreateStoreModal: boolean;
+ setCloseCreateStoreModal: (val: boolean) => void;
+ onPublish: (param: onPublishParam) => Promise;
+ username: string;
+}
+
+
+const CreateStoreModal: React.FC = ({
+ open,
+ closeCreateStoreModal,
+ setCloseCreateStoreModal,
+ onPublish,
+ username,
+}) => {
+ const dispatch = useDispatch();
+ const theme = useTheme();
+
+ const [title, setTitle] = useState("");
+ const [description, setDescription] = useState("");
+ const [location, setLocation] = useState("");
+ const [shipsTo, setShipsTo] = useState("");
+ const [errorMessage, setErrorMessage] = useState("");
+ const [storeIdentifier, setStoreIdentifier] = useState("");
+ const [logo, setLogo] = useState(null);
+ const [supportedCoinsSelected, setSupportedCoinsSelected] = useState<
+ string[]
+ >(["QORT"]);
+ const [qortWalletAddress, setQortWalletAddress] = useState("");
+ const [arrrWalletAddress, setArrrWalletAddress] = useState("");
+
+ const handlePublish = async (): Promise => {
+ try {
+ setErrorMessage("");
+ if (!logo) {
+ setErrorMessage("A logo is required");
+ return;
+ }
+ const foreignCoins: ForeignCoins = {
+ ARRR: arrrWalletAddress
+ }
+ supportedCoinsSelected.filter((coin)=> coin !== 'QORT').forEach((item: string)=> {
+ if(!foreignCoins[item]) throw new Error(`Please add a ${item} address`)
+ })
+ await onPublish({
+ title,
+ description,
+ shipsTo,
+ location,
+ storeIdentifier,
+ logo,
+ foreignCoins: {
+ ARRR: arrrWalletAddress
+ },
+ supportedCoins: supportedCoinsSelected
+ });
+ } catch (error: any) {
+ setErrorMessage(error.message);
+ }
+ };
+
+ const handleClose = (): void => {
+ setTitle("");
+ setDescription("");
+ setErrorMessage("");
+ setArrrWalletAddress("")
+ setSupportedCoinsSelected(["QORT"])
+ dispatch(toggleCreateStoreModal(false));
+ };
+
+ const handleInputChangeId = (event: ChangeEvent) => {
+ // Replace any non-alphanumeric and non-space characters with an empty string
+ // Replace multiple spaces with a single dash and remove any dashes that come one after another
+ let newValue = event.target.value
+ .replace(/[^a-zA-Z0-9\s-]/g, "")
+ .replace(/\s+/g, "-")
+ .replace(/-+/g, "-")
+ .trim();
+
+ if (newValue.toLowerCase().includes("post")) {
+ // Replace the 'post' string with an empty string
+ newValue = newValue.replace(/post/gi, "");
+ }
+ if (newValue.toLowerCase().includes("q-shop")) {
+ // Replace the 'q-shop' string with an empty string
+ newValue = newValue.replace(/q-shop/gi, "");
+ }
+ setStoreIdentifier(newValue);
+ };
+
+ // Close modal when closeCreateStoreModal is true and reset closeCreateStoreModal to false. This is done once the data container is created inside the GlobalWrapper createStore function.
+ useEffect(() => {
+ if (closeCreateStoreModal) {
+ handleClose();
+ setCloseCreateStoreModal(false);
+ }
+ }, [closeCreateStoreModal]);
+
+ const handleChipSelect = (value: string[]) => {
+ setSupportedCoinsSelected(value);
+ };
+
+ const handleChipRemove = (chip: string) => {
+ if (chip === "QORT") return;
+ setSupportedCoinsSelected(prevChips => prevChips.filter(c => c !== chip));
+ };
+
+ const importAddress = async (coin: string)=> {
+ try {
+ dispatch(setIsLoadingGlobal(true));
+
+ const res = await qortalRequest({
+ action: 'GET_USER_WALLET',
+ coin
+ })
+
+ if(res?.address){
+ setArrrWalletAddress(res.address)
+ }
+ } catch (error) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Unable to import ARRR address. Please insert it manually",
+ })
+ );
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ }
+
+ return (
+
+
+ Create Shop
+ {!logo ? (
+ setLogo(img)}>
+
+ Add Shop Logo
+
+
+
+ ) : (
+
+
+ setLogo(null)}
+ height={"32"}
+ width={"32"}
+ >
+
+ )}
+
+ setTitle(e.target.value)}
+ fullWidth
+ disabled={true}
+ variant="filled"
+ />
+
+
+
+ setTitle(e.target.value)}
+ fullWidth
+ required
+ variant="filled"
+ inputProps={{ maxLength: 50 }}
+ />
+
+ setDescription(e.target.value)}
+ multiline
+ rows={4}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ setLocation(e.target.value)}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ setShipsTo(e.target.value)}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ {/* QORT Wallet Input Field */}
+ {/*
+ {
+ setQortWalletAddress(e.target.value);
+ }}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+
+
+
+
+ */}
+
+ {/* ARRR Wallet Input Field */}
+
+ {
+ setArrrWalletAddress(e.target.value);
+ }}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ importAddress('ARRR')}>
+
+
+
+
+ {/* Coin selection available for your shop */}
+ {
+ if (e.target.textContent === "QORT") return;
+ handleChipSelect(value as string[]);
+ }}
+ renderTags={(values: any) =>
+ values.map((value: string) => {
+ return (
+ handleChipRemove(value) : undefined
+ }
+ clickable={value === "QORT" ? false : true}
+ />
+ );
+ })
+ }
+ renderOption={(props, option: any) => {
+ const isDisabled = option === "QORT";
+ return (
+
+ coin === option)}
+ />
+ {option === "QORT" ? (
+
+ ) : option === "ARRR" ? (
+
+ ) : null}
+ {option}
+
+ );
+ }}
+ renderInput={params => (
+
+ )}
+ />
+
+
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+ Cancel
+
+
+ Create Shop
+
+
+
+
+ );
+};
+
+export default CreateStoreModal;
diff --git a/src/components/modals/EditStoreModal.tsx b/src/components/modals/EditStoreModal.tsx
new file mode 100644
index 0000000..f475d1e
--- /dev/null
+++ b/src/components/modals/EditStoreModal.tsx
@@ -0,0 +1,383 @@
+import { useState, useEffect } from "react";
+import {
+ Typography,
+ Modal,
+ FormControl,
+ useTheme,
+ IconButton,
+ Tooltip,
+ Zoom,
+} from "@mui/material";
+import { useDispatch, useSelector } from "react-redux";
+import { toggleCreateStoreModal } from "../../state/features/globalSlice";
+import { RootState } from "../../state/store";
+import {
+ AddLogoButton,
+ AddLogoIcon,
+ WalletRow,
+ ButtonRow,
+ CancelButton,
+ CreateButton,
+ CustomInputField,
+ DownloadArrrWalletIcon,
+ LogoPreviewRow,
+ ModalBody,
+ ModalTitle,
+ StoreLogoPreview,
+ TimesIcon,
+} from "./CreateStoreModal-styles";
+import ImageUploader from "../common/ImageUploader";
+import {
+ FilterSelect,
+ FilterSelectMenuItems,
+ FiltersCheckbox,
+ FiltersChip,
+ FiltersOption,
+} from "../../pages/Store/Store/Store-styles";
+import { supportedCoinsArray } from "../../constants/supported-coins";
+import { QortalSVG } from "../../assets/svgs/QortalSVG";
+import { ARRRSVG } from "../../assets/svgs/ARRRSVG";
+
+interface ForeignCoins {
+ [key: string]: string;
+}
+export interface onPublishParamEdit {
+ title: string;
+ description: string;
+ shipsTo: string;
+ location: string;
+ logo: string;
+ foreignCoins: ForeignCoins;
+ supportedCoins: string[];
+}
+interface MyModalProps {
+ open: boolean;
+ onClose: () => void;
+ onPublish: (param: onPublishParamEdit) => Promise;
+ username: string;
+}
+
+const MyModal: React.FC = ({ open, onClose, onPublish }) => {
+ const dispatch = useDispatch();
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+
+ const storeId = useSelector((state: RootState) => state.store.storeId);
+
+ const [title, setTitle] = useState("");
+ const [description, setDescription] = useState("");
+ const [location, setLocation] = useState("");
+ const [shipsTo, setShipsTo] = useState("");
+ const [errorMessage, setErrorMessage] = useState("");
+ const [logo, setLogo] = useState(null);
+ const [supportedCoinsSelected, setSupportedCoinsSelected] = useState<
+ string[]
+ >(["QORT"]);
+ const [qortWalletAddress, setQortWalletAddress] = useState("");
+ const [arrrWalletAddress, setArrrWalletAddress] = useState("");
+
+ const theme = useTheme();
+
+ const handlePublish = async (): Promise => {
+ try {
+ setErrorMessage("");
+ if (!logo) {
+ setErrorMessage("A logo is required");
+ return;
+ }
+
+ const foreignCoins: ForeignCoins = {
+ ARRR: arrrWalletAddress
+ }
+ supportedCoinsSelected.filter((coin)=> coin !== 'QORT').forEach((item: string)=> {
+ if(!foreignCoins[item]) throw new Error(`Please add a ${item} address`)
+ })
+ await onPublish({
+ title,
+ description,
+ shipsTo,
+ location,
+ logo,
+ foreignCoins: {
+ ARRR: arrrWalletAddress
+ },
+ supportedCoins: supportedCoinsSelected
+ });
+ handleClose();
+ } catch (error: any) {
+ setErrorMessage(error.message);
+ }
+ };
+
+ useEffect(() => {
+ if (open && currentStore && storeId === currentStore.id) {
+ setTitle(currentStore?.title || "");
+ setDescription(currentStore?.description || "");
+ setLogo(currentStore?.logo || null);
+ setLocation(currentStore?.location || "");
+ setShipsTo(currentStore?.shipsTo || "");
+ setSupportedCoinsSelected(currentStore?.supportedCoins || ['QORT'])
+ setArrrWalletAddress(currentStore?.foreignCoins?.ARRR || "")
+ }
+ }, [currentStore, storeId, open]);
+
+ const handleClose = (): void => {
+ setTitle("");
+ setDescription("");
+ setErrorMessage("");
+ setDescription("");
+ setLogo(null);
+ setLocation("");
+ setShipsTo("");
+ setArrrWalletAddress("")
+ setSupportedCoinsSelected(["QORT"])
+ dispatch(toggleCreateStoreModal(false));
+ onClose();
+ };
+
+ const handleChipSelect = (value: string[]) => {
+ setSupportedCoinsSelected(value);
+ };
+
+ const handleChipRemove = (chip: string) => {
+ if (chip === "QORT") return;
+ setSupportedCoinsSelected(prevChips => prevChips.filter(c => c !== chip));
+ };
+
+ const importAddress = async (coin: string)=> {
+ try {
+ const res = await qortalRequest({
+ action: 'GET_USER_WALLET',
+ coin
+ })
+ if(res?.address){
+ setArrrWalletAddress(res.address)
+ }
+ } catch (error) {
+
+ }
+ }
+
+ return (
+
+
+
+ Edit Shop
+
+ {!logo ? (
+ setLogo(img)}>
+
+ Add Shop Logo
+
+
+
+ ) : (
+
+
+ setLogo(null)}
+ height={"32"}
+ width={"32"}
+ >
+
+ )}
+ setTitle(e.target.value)}
+ inputProps={{ maxLength: 50 }}
+ fullWidth
+ required
+ variant="filled"
+ />
+ setDescription(e.target.value)}
+ multiline
+ rows={4}
+ fullWidth
+ required
+ variant="filled"
+ />
+ setLocation(e.target.value)}
+ fullWidth
+ required
+ variant="filled"
+ />
+ setShipsTo(e.target.value)}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ {/* QORT Wallet Input Field */}
+ {/*
+ {
+ setQortWalletAddress(e.target.value);
+ }}
+ fullWidth
+ required
+ variant="filled"
+ />
+ {
+ try {
+ const res = await qortalRequest({
+ action: 'GET_USER_WALLET',
+ coin
+ })
+ if(res?.address){
+ setArrrWalletAddress(res.address)
+ }
+ console.log({res})
+ } catch (error) {
+
+ }
+ }
+ title="Import your QORT Wallet Address from your current account"
+ >
+
+
+
+
+ */}
+
+ {/* ARRR Wallet Input Field */}
+
+ {
+ setArrrWalletAddress(e.target.value);
+ }}
+ fullWidth
+ required
+ variant="filled"
+ />
+
+ importAddress('ARRR')}>
+
+
+
+
+ {
+ if (e.target.textContent === "QORT") return;
+ handleChipSelect(value as string[]);
+ }}
+ renderTags={(values: any) =>
+ values.map((value: string) => {
+ return (
+ handleChipRemove(value) : undefined
+ }
+ clickable={value === "QORT" ? false : true}
+ />
+ );
+ })
+ }
+ renderOption={(props, option: any) => {
+ const isDisabled = option === "QORT";
+ return (
+
+ coin === option)}
+ />
+ {option === "QORT" ? (
+
+ ) : option === "ARRR" ? (
+
+ ) : null}
+ {option}
+
+ );
+ }}
+ renderInput={params => (
+
+ )}
+ />
+
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+ Cancel
+
+
+ Edit Shop
+
+
+
+
+ );
+};
+
+export default MyModal;
diff --git a/src/components/modals/ReusableModal-styles.tsx b/src/components/modals/ReusableModal-styles.tsx
new file mode 100644
index 0000000..e1feabf
--- /dev/null
+++ b/src/components/modals/ReusableModal-styles.tsx
@@ -0,0 +1,39 @@
+import { styled } from "@mui/system";
+import { Box } from "@mui/material";
+
+export const ReusableModalBody = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+ width: "75%",
+ backgroundColor: theme.palette.background.paper,
+ padding: "15px 35px",
+ display: "flex",
+ flexDirection: "column",
+ gap: "17px",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)"
+ : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: theme.palette.background.paper
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: theme.palette.background.paper
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646"
+ }
+}));
diff --git a/src/components/modals/ReusableModal.tsx b/src/components/modals/ReusableModal.tsx
new file mode 100644
index 0000000..89f2f2a
--- /dev/null
+++ b/src/components/modals/ReusableModal.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import { Box, Modal, useTheme } from "@mui/material";
+import { ReusableModalBody } from "./ReusableModal-styles";
+
+interface MyModalProps {
+ open: boolean;
+ onClose?: () => void;
+ // onSubmit?: (obj: any) => Promise;
+ children: any;
+ customStyles?: any;
+ id?: string;
+}
+
+export const ReusableModal: React.FC = ({
+ id,
+ open,
+ onClose,
+ // onSubmit,
+ children,
+ customStyles = {}
+}) => {
+ const theme = useTheme();
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/src/constants/countries.json b/src/constants/countries.json
new file mode 100644
index 0000000..710e644
--- /dev/null
+++ b/src/constants/countries.json
@@ -0,0 +1,245 @@
+[
+ { "text": "Afghanistan", "value": "AF" },
+ { "text": "Åland Islands", "value": "AX" },
+ { "text": "Albania", "value": "AL" },
+ { "text": "Algeria", "value": "DZ" },
+ { "text": "American Samoa", "value": "AS" },
+ { "text": "Andorra", "value": "AD" },
+ { "text": "Angola", "value": "AO" },
+ { "text": "Anguilla", "value": "AI" },
+ { "text": "Antarctica", "value": "AQ" },
+ { "text": "Antigua and Barbuda", "value": "AG" },
+ { "text": "Argentina", "value": "AR" },
+ { "text": "Armenia", "value": "AM" },
+ { "text": "Aruba", "value": "AW" },
+ { "text": "Australia", "value": "AU" },
+ { "text": "Austria", "value": "AT" },
+ { "text": "Azerbaijan", "value": "AZ" },
+ { "text": "Bahamas", "value": "BS" },
+ { "text": "Bahrain", "value": "BH" },
+ { "text": "Bangladesh", "value": "BD" },
+ { "text": "Barbados", "value": "BB" },
+ { "text": "Belarus", "value": "BY" },
+ { "text": "Belgium", "value": "BE" },
+ { "text": "Belize", "value": "BZ" },
+ { "text": "Benin", "value": "BJ" },
+ { "text": "Bermuda", "value": "BM" },
+ { "text": "Bhutan", "value": "BT" },
+ { "text": "Bolivia", "value": "BO" },
+ { "text": "Bosnia and Herzegovina", "value": "BA" },
+ { "text": "Botswana", "value": "BW" },
+ { "text": "Bouvet Island", "value": "BV" },
+ { "text": "Brazil", "value": "BR" },
+ { "text": "British Indian Ocean Territory", "value": "IO" },
+ { "text": "Brunei Darussalam", "value": "BN" },
+ { "text": "Bulgaria", "value": "BG" },
+ { "text": "Burkina Faso", "value": "BF" },
+ { "text": "Burundi", "value": "BI" },
+ { "text": "Cambodia", "value": "KH" },
+ { "text": "Cameroon", "value": "CM" },
+ { "text": "Canada", "value": "CA" },
+ { "text": "Cape Verde", "value": "CV" },
+ { "text": "Cayman Islands", "value": "KY" },
+ { "text": "Central African Republic", "value": "CF" },
+ { "text": "Chad", "value": "TD" },
+ { "text": "Chile", "value": "CL" },
+ { "text": "China", "value": "CN" },
+ { "text": "Christmas Island", "value": "CX" },
+ { "text": "Cocos (Keeling) Islands", "value": "CC" },
+ { "text": "Colombia", "value": "CO" },
+ { "text": "Comoros", "value": "KM" },
+ { "text": "Congo", "value": "CG" },
+ { "text": "Congo, The Democratic Republic of the", "value": "CD" },
+ { "text": "Cook Islands", "value": "CK" },
+ { "text": "Costa Rica", "value": "CR" },
+ { "text": "Cote D'Ivoire", "value": "CI" },
+ { "text": "Croatia", "value": "HR" },
+ { "text": "Cuba", "value": "CU" },
+ { "text": "Cyprus", "value": "CY" },
+ { "text": "Czech Republic", "value": "CZ" },
+ { "text": "Denmark", "value": "DK" },
+ { "text": "Djibouti", "value": "DJ" },
+ { "text": "Dominica", "value": "DM" },
+ { "text": "Dominican Republic", "value": "DO" },
+ { "text": "Ecuador", "value": "EC" },
+ { "text": "Egypt", "value": "EG" },
+ { "text": "El Salvador", "value": "SV" },
+ { "text": "Equatorial Guinea", "value": "GQ" },
+ { "text": "Eritrea", "value": "ER" },
+ { "text": "Estonia", "value": "EE" },
+ { "text": "Ethiopia", "value": "ET" },
+ { "text": "Falkland Islands (Malvinas)", "value": "FK" },
+ { "text": "Faroe Islands", "value": "FO" },
+ { "text": "Fiji", "value": "FJ" },
+ { "text": "Finland", "value": "FI" },
+ { "text": "France", "value": "FR" },
+ { "text": "French Guiana", "value": "GF" },
+ { "text": "French Polynesia", "value": "PF" },
+ { "text": "French Southern Territories", "value": "TF" },
+ { "text": "Gabon", "value": "GA" },
+ { "text": "Gambia", "value": "GM" },
+ { "text": "Georgia", "value": "GE" },
+ { "text": "Germany", "value": "DE" },
+ { "text": "Ghana", "value": "GH" },
+ { "text": "Gibraltar", "value": "GI" },
+ { "text": "Greece", "value": "GR" },
+ { "text": "Greenland", "value": "GL" },
+ { "text": "Grenada", "value": "GD" },
+ { "text": "Guadeloupe", "value": "GP" },
+ { "text": "Guam", "value": "GU" },
+ { "text": "Guatemala", "value": "GT" },
+ { "text": "Guernsey", "value": "GG" },
+ { "text": "Guinea", "value": "GN" },
+ { "text": "Guinea-Bissau", "value": "GW" },
+ { "text": "Guyana", "value": "GY" },
+ { "text": "Haiti", "value": "HT" },
+ { "text": "Heard Island and Mcdonald Islands", "value": "HM" },
+ { "text": "Holy See (Vatican City State)", "value": "VA" },
+ { "text": "Honduras", "value": "HN" },
+ { "text": "Hong Kong", "value": "HK" },
+ { "text": "Hungary", "value": "HU" },
+ { "text": "Iceland", "value": "IS" },
+ { "text": "India", "value": "IN" },
+ { "text": "Indonesia", "value": "ID" },
+ { "text": "Iran, Islamic Republic Of", "value": "IR" },
+ { "text": "Iraq", "value": "IQ" },
+ { "text": "Ireland", "value": "IE" },
+ { "text": "Isle of Man", "value": "IM" },
+ { "text": "Israel", "value": "IL" },
+ { "text": "Italy", "value": "IT" },
+ { "text": "Jamaica", "value": "JM" },
+ { "text": "Japan", "value": "JP" },
+ { "text": "Jersey", "value": "JE" },
+ { "text": "Jordan", "value": "JO" },
+ { "text": "Kazakhstan", "value": "KZ" },
+ { "text": "Kenya", "value": "KE" },
+ { "text": "Kiribati", "value": "KI" },
+ { "text": "Korea, Democratic People'S Republic of", "value": "KP" },
+ { "text": "Korea, Republic of", "value": "KR" },
+ { "text": "Kuwait", "value": "KW" },
+ { "text": "Kyrgyzstan", "value": "KG" },
+ { "text": "Lao People'S Democratic Republic", "value": "LA" },
+ { "text": "Latvia", "value": "LV" },
+ { "text": "Lebanon", "value": "LB" },
+ { "text": "Lesotho", "value": "LS" },
+ { "text": "Liberia", "value": "LR" },
+ { "text": "Libyan Arab Jamahiriya", "value": "LY" },
+ { "text": "Liechtenstein", "value": "LI" },
+ { "text": "Lithuania", "value": "LT" },
+ { "text": "Luxembourg", "value": "LU" },
+ { "text": "Macao", "value": "MO" },
+ { "text": "Macedonia, The Former Yugoslav Republic of", "value": "MK" },
+ { "text": "Madagascar", "value": "MG" },
+ { "text": "Malawi", "value": "MW" },
+ { "text": "Malaysia", "value": "MY" },
+ { "text": "Maldives", "value": "MV" },
+ { "text": "Mali", "value": "ML" },
+ { "text": "Malta", "value": "MT" },
+ { "text": "Marshall Islands", "value": "MH" },
+ { "text": "Martinique", "value": "MQ" },
+ { "text": "Mauritania", "value": "MR" },
+ { "text": "Mauritius", "value": "MU" },
+ { "text": "Mayotte", "value": "YT" },
+ { "text": "Mexico", "value": "MX" },
+ { "text": "Micronesia, Federated States of", "value": "FM" },
+ { "text": "Moldova, Republic of", "value": "MD" },
+ { "text": "Monaco", "value": "MC" },
+ { "text": "Mongolia", "value": "MN" },
+ { "text": "Montserrat", "value": "MS" },
+ { "text": "Morocco", "value": "MA" },
+ { "text": "Mozambique", "value": "MZ" },
+ { "text": "Myanmar", "value": "MM" },
+ { "text": "Namibia", "value": "NA" },
+ { "text": "Nauru", "value": "NR" },
+ { "text": "Nepal", "value": "NP" },
+ { "text": "Netherlands", "value": "NL" },
+ { "text": "Netherlands Antilles", "value": "AN" },
+ { "text": "New Caledonia", "value": "NC" },
+ { "text": "New Zealand", "value": "NZ" },
+ { "text": "Nicaragua", "value": "NI" },
+ { "text": "Niger", "value": "NE" },
+ { "text": "Nigeria", "value": "NG" },
+ { "text": "Niue", "value": "NU" },
+ { "text": "Norfolk Island", "value": "NF" },
+ { "text": "Northern Mariana Islands", "value": "MP" },
+ { "text": "Norway", "value": "NO" },
+ { "text": "Oman", "value": "OM" },
+ { "text": "Pakistan", "value": "PK" },
+ { "text": "Palau", "value": "PW" },
+ { "text": "Palestinian Territory, Occupied", "value": "PS" },
+ { "text": "Panama", "value": "PA" },
+ { "text": "Papua New Guinea", "value": "PG" },
+ { "text": "Paraguay", "value": "PY" },
+ { "text": "Peru", "value": "PE" },
+ { "text": "Philippines", "value": "PH" },
+ { "text": "Pitcairn", "value": "PN" },
+ { "text": "Poland", "value": "PL" },
+ { "text": "Portugal", "value": "PT" },
+ { "text": "Puerto Rico", "value": "PR" },
+ { "text": "Qatar", "value": "QA" },
+ { "text": "Reunion", "value": "RE" },
+ { "text": "Romania", "value": "RO" },
+ { "text": "Russian Federation", "value": "RU" },
+ { "text": "RWANDA", "value": "RW" },
+ { "text": "Saint Helena", "value": "SH" },
+ { "text": "Saint Kitts and Nevis", "value": "KN" },
+ { "text": "Saint Lucia", "value": "LC" },
+ { "text": "Saint Pierre and Miquelon", "value": "PM" },
+ { "text": "Saint Vincent and the Grenadines", "value": "VC" },
+ { "text": "Samoa", "value": "WS" },
+ { "text": "San Marino", "value": "SM" },
+ { "text": "Sao Tome and Principe", "value": "ST" },
+ { "text": "Saudi Arabia", "value": "SA" },
+ { "text": "Senegal", "value": "SN" },
+ { "text": "Serbia and Montenegro", "value": "CS" },
+ { "text": "Seychelles", "value": "SC" },
+ { "text": "Sierra Leone", "value": "SL" },
+ { "text": "Singapore", "value": "SG" },
+ { "text": "Slovakia", "value": "SK" },
+ { "text": "Slovenia", "value": "SI" },
+ { "text": "Solomon Islands", "value": "SB" },
+ { "text": "Somalia", "value": "SO" },
+ { "text": "South Africa", "value": "ZA" },
+ { "text": "South Georgia and the South Sandwich Islands", "value": "GS" },
+ { "text": "Spain", "value": "ES" },
+ { "text": "Sri Lanka", "value": "LK" },
+ { "text": "Sudan", "value": "SD" },
+ { "text": "Suriname", "value": "SR" },
+ { "text": "Svalbard and Jan Mayen", "value": "SJ" },
+ { "text": "Swaziland", "value": "SZ" },
+ { "text": "Sweden", "value": "SE" },
+ { "text": "Switzerland", "value": "CH" },
+ { "text": "Syrian Arab Republic", "value": "SY" },
+ { "text": "Taiwan, Province of China", "value": "TW" },
+ { "text": "Tajikistan", "value": "TJ" },
+ { "text": "Tanzania, United Republic of", "value": "TZ" },
+ { "text": "Thailand", "value": "TH" },
+ { "text": "Timor-Leste", "value": "TL" },
+ { "text": "Togo", "value": "TG" },
+ { "text": "Tokelau", "value": "TK" },
+ { "text": "Tonga", "value": "TO" },
+ { "text": "Trinidad and Tobago", "value": "TT" },
+ { "text": "Tunisia", "value": "TN" },
+ { "text": "Turkey", "value": "TR" },
+ { "text": "Turkmenistan", "value": "TM" },
+ { "text": "Turks and Caicos Islands", "value": "TC" },
+ { "text": "Tuvalu", "value": "TV" },
+ { "text": "Uganda", "value": "UG" },
+ { "text": "Ukraine", "value": "UA" },
+ { "text": "United Arab Emirates", "value": "AE" },
+ { "text": "United Kingdom", "value": "GB" },
+ { "text": "United States", "value": "US" },
+ { "text": "United States Minor Outlying Islands", "value": "UM" },
+ { "text": "Uruguay", "value": "UY" },
+ { "text": "Uzbekistan", "value": "UZ" },
+ { "text": "Vanuatu", "value": "VU" },
+ { "text": "Venezuela", "value": "VE" },
+ { "text": "Viet Nam", "value": "VN" },
+ { "text": "Virgin Islands, British", "value": "VG" },
+ { "text": "Virgin Islands, U.S.", "value": "VI" },
+ { "text": "Wallis and Futuna", "value": "WF" },
+ { "text": "Western Sahara", "value": "EH" },
+ { "text": "Yemen", "value": "YE" },
+ { "text": "Zambia", "value": "ZM" },
+ { "text": "Zimbabwe", "value": "ZW" }
+]
\ No newline at end of file
diff --git a/src/constants/identifiers.ts b/src/constants/identifiers.ts
new file mode 100644
index 0000000..46a7101
--- /dev/null
+++ b/src/constants/identifiers.ts
@@ -0,0 +1,18 @@
+// TESTING
+
+export const STORE_BASE = "test-q-store-general";
+export const DATA_CONTAINER_BASE = "test-datacontainer";
+export const CATALOGUE_BASE = "test-q-store-catalogue";
+export const ORDER_BASE = "test-q-store-order";
+export const REVIEW_BASE = "test-q-store-review";
+export const PRODUCT_BASE = "test-q-store-product";
+
+// PRODUCTION
+
+// export const STORE_BASE = "q-store-general";
+// export const DATA_CONTAINER_BASE = "datacontainer";
+// export const CATALOGUE_BASE = "q-store-catalogue";
+// export const ORDER_BASE = "q-store-order";
+// export const REVIEW_BASE = "q-store-review";
+// export const PRODUCT_BASE = "q-store-product";
+
diff --git a/src/constants/mail.ts b/src/constants/mail.ts
new file mode 100644
index 0000000..bbb6e7b
--- /dev/null
+++ b/src/constants/mail.ts
@@ -0,0 +1,3 @@
+export const MAIL_SERVICE_TYPE: 'MAIL_PRIVATE' = 'MAIL_PRIVATE'
+export const MAIL_ATTACHMENT_SERVICE_TYPE: 'ATTACHMENT_PRIVATE' =
+ 'ATTACHMENT_PRIVATE'
diff --git a/src/constants/product-status.ts b/src/constants/product-status.ts
new file mode 100644
index 0000000..3680d9d
--- /dev/null
+++ b/src/constants/product-status.ts
@@ -0,0 +1,3 @@
+export const AVAILABLE = 'AVAILABLE'
+export const RETIRED = 'RETIRED'
+export const OUT_OF_STOCK = 'OUT_OF_STOCK'
diff --git a/src/constants/states.json b/src/constants/states.json
new file mode 100644
index 0000000..0f7414e
--- /dev/null
+++ b/src/constants/states.json
@@ -0,0 +1,238 @@
+[
+ {
+ "text": "Alabama",
+ "value": "AL"
+ },
+ {
+ "text": "Alaska",
+ "value": "AK"
+ },
+ {
+ "text": "American Samoa",
+ "value": "AS"
+ },
+ {
+ "text": "Arizona",
+ "value": "AZ"
+ },
+ {
+ "text": "Arkansas",
+ "value": "AR"
+ },
+ {
+ "text": "California",
+ "value": "CA"
+ },
+ {
+ "text": "Colorado",
+ "value": "CO"
+ },
+ {
+ "text": "Connecticut",
+ "value": "CT"
+ },
+ {
+ "text": "Delaware",
+ "value": "DE"
+ },
+ {
+ "text": "District Of Columbia",
+ "value": "DC"
+ },
+ {
+ "text": "Federated States Of Micronesia",
+ "value": "FM"
+ },
+ {
+ "text": "Florida",
+ "value": "FL"
+ },
+ {
+ "text": "Georgia",
+ "value": "GA"
+ },
+ {
+ "text": "Guam",
+ "value": "GU"
+ },
+ {
+ "text": "Hawaii",
+ "value": "HI"
+ },
+ {
+ "text": "Idaho",
+ "value": "ID"
+ },
+ {
+ "text": "Illinois",
+ "value": "IL"
+ },
+ {
+ "text": "Indiana",
+ "value": "IN"
+ },
+ {
+ "text": "Iowa",
+ "value": "IA"
+ },
+ {
+ "text": "Kansas",
+ "value": "KS"
+ },
+ {
+ "text": "Kentucky",
+ "value": "KY"
+ },
+ {
+ "text": "Louisiana",
+ "value": "LA"
+ },
+ {
+ "text": "Maine",
+ "value": "ME"
+ },
+ {
+ "text": "Marshall Islands",
+ "value": "MH"
+ },
+ {
+ "text": "Maryland",
+ "value": "MD"
+ },
+ {
+ "text": "Massachusetts",
+ "value": "MA"
+ },
+ {
+ "text": "Michigan",
+ "value": "MI"
+ },
+ {
+ "text": "Minnesota",
+ "value": "MN"
+ },
+ {
+ "text": "Mississippi",
+ "value": "MS"
+ },
+ {
+ "text": "Missouri",
+ "value": "MO"
+ },
+ {
+ "text": "Montana",
+ "value": "MT"
+ },
+ {
+ "text": "Nebraska",
+ "value": "NE"
+ },
+ {
+ "text": "Nevada",
+ "value": "NV"
+ },
+ {
+ "text": "New Hampshire",
+ "value": "NH"
+ },
+ {
+ "text": "New Jersey",
+ "value": "NJ"
+ },
+ {
+ "text": "New Mexico",
+ "value": "NM"
+ },
+ {
+ "text": "New York",
+ "value": "NY"
+ },
+ {
+ "text": "North Carolina",
+ "value": "NC"
+ },
+ {
+ "text": "North Dakota",
+ "value": "ND"
+ },
+ {
+ "text": "Northern Mariana Islands",
+ "value": "MP"
+ },
+ {
+ "text": "Ohio",
+ "value": "OH"
+ },
+ {
+ "text": "Oklahoma",
+ "value": "OK"
+ },
+ {
+ "text": "Oregon",
+ "value": "OR"
+ },
+ {
+ "text": "Palau",
+ "value": "PW"
+ },
+ {
+ "text": "Pennsylvania",
+ "value": "PA"
+ },
+ {
+ "text": "Puerto Rico",
+ "value": "PR"
+ },
+ {
+ "text": "Rhode Island",
+ "value": "RI"
+ },
+ {
+ "text": "South Carolina",
+ "value": "SC"
+ },
+ {
+ "text": "South Dakota",
+ "value": "SD"
+ },
+ {
+ "text": "Tennessee",
+ "value": "TN"
+ },
+ {
+ "text": "Texas",
+ "value": "TX"
+ },
+ {
+ "text": "Utah",
+ "value": "UT"
+ },
+ {
+ "text": "Vermont",
+ "value": "VT"
+ },
+ {
+ "text": "Virgin Islands",
+ "value": "VI"
+ },
+ {
+ "text": "Virginia",
+ "value": "VA"
+ },
+ {
+ "text": "Washington",
+ "value": "WA"
+ },
+ {
+ "text": "West Virginia",
+ "value": "WV"
+ },
+ {
+ "text": "Wisconsin",
+ "value": "WI"
+ },
+ {
+ "text": "Wyoming",
+ "value": "WY"
+ }
+]
\ No newline at end of file
diff --git a/src/constants/supported-coins.ts b/src/constants/supported-coins.ts
new file mode 100644
index 0000000..ce41262
--- /dev/null
+++ b/src/constants/supported-coins.ts
@@ -0,0 +1,8 @@
+
+enum SupportedCoins {
+ ARRR = 'ARRR',
+ QORT = 'QORT',
+}
+
+export const supportedCoinsArray: string[] = Object.values(SupportedCoins);
+
diff --git a/src/constants/timeout.ts b/src/constants/timeout.ts
new file mode 100644
index 0000000..cce5728
--- /dev/null
+++ b/src/constants/timeout.ts
@@ -0,0 +1 @@
+export const TIMEOUT = 5000; // 5 seconds, adjust as needed
diff --git a/src/global.d.ts b/src/global.d.ts
new file mode 100644
index 0000000..7e712fc
--- /dev/null
+++ b/src/global.d.ts
@@ -0,0 +1,60 @@
+// src/global.d.ts
+interface QortalRequestOptions {
+ action: string
+ name?: string
+ service?: string
+ data64?: string
+ title?: string
+ description?: string
+ category?: string
+ tags?: string[]
+ identifier?: string
+ address?: string
+ metaData?: string
+ encoding?: string
+ includeMetadata?: boolean
+ limit?: number
+ offset?: number
+ reverse?: boolean
+ resources?: any[]
+ filename?: string
+ list_name?: string
+ item?: string
+ items?: strings[]
+ tag1?: string
+ tag2?: string
+ tag3?: string
+ tag4?: string
+ tag5?: string
+ coin?: string
+ destinationAddress?: string
+ amount?: number
+ blob?: Blob
+ mimeType?: string
+ file?: File
+ encryptedData?: string
+ prefix?: boolean
+ exactMatchNames?: boolean,
+ mode?: string
+}
+
+declare function qortalRequest(options: QortalRequestOptions): Promise
+declare function qortalRequestWithTimeout(
+ options: QortalRequestOptions,
+ time: number
+): Promise
+
+declare global {
+ interface Window {
+ _qdnBase: any // Replace 'any' with the appropriate type if you know it
+ _qdnTheme: string
+ }
+}
+
+declare global {
+ interface Window {
+ showSaveFilePicker: (
+ options?: SaveFilePickerOptions
+ ) => Promise
+ }
+}
diff --git a/src/hooks/useConfirmModal.tsx b/src/hooks/useConfirmModal.tsx
new file mode 100644
index 0000000..c2249c2
--- /dev/null
+++ b/src/hooks/useConfirmModal.tsx
@@ -0,0 +1,36 @@
+import { useState } from "react";
+import ConfirmationModal, {
+ ModalProps
+} from "../components/common/ConfirmationModal/ConfirmationModal";
+
+const useConfirmationModal = (props: any) => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [resolvePromise, setResolvePromise] =
+ useState<(value: boolean) => void>();
+
+ const showModal = async () => {
+ setIsModalOpen(true);
+ return new Promise((resolve) => {
+ setResolvePromise(() => resolve);
+ });
+ };
+
+ const handleUserAction = (userConfirmed: boolean) => {
+ setIsModalOpen(false);
+ resolvePromise?.(userConfirmed);
+ };
+
+ const Modal = () => (
+ handleUserAction(true)}
+ handleCancel={() => handleUserAction(false)}
+ />
+ );
+
+ return { Modal, showModal };
+};
+
+export default useConfirmationModal;
diff --git a/src/hooks/useFetchCatalogues.tsx b/src/hooks/useFetchCatalogues.tsx
new file mode 100644
index 0000000..c9ec939
--- /dev/null
+++ b/src/hooks/useFetchCatalogues.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { addToHashMap } from '../state/features/storeSlice'
+
+import { RootState } from '../state/store'
+import { fetchAndEvaluateProducts } from '../utils/fetchPosts'
+
+interface Resource {
+ id: string
+ updated: number
+}
+export const useFetchProducts = () => {
+ const dispatch = useDispatch()
+ const hashMapProducts = useSelector(
+ (state: RootState) => state.store.hashMapProducts
+ )
+
+ const getProduct = async (user: string, productId: string, content: any) => {
+ const res = await fetchAndEvaluateProducts({
+ user,
+ productId,
+ content
+ })
+
+ dispatch(addToHashMap(res))
+ }
+
+ const checkAndUpdateResource = React.useCallback(
+ (resource: Resource) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource = hashMapProducts[resource.id]
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true
+ } else if (
+ resource?.updated &&
+ existingResource?.updated &&
+ resource.updated > existingResource.updated
+ ) {
+ // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
+ return true
+ } else {
+ return false
+ }
+ },
+ [hashMapProducts]
+ )
+
+ return {
+ getProduct,
+ hashMapProducts,
+ checkAndUpdateResource
+ }
+}
diff --git a/src/hooks/useFetchOrders.tsx b/src/hooks/useFetchOrders.tsx
new file mode 100644
index 0000000..6e1083b
--- /dev/null
+++ b/src/hooks/useFetchOrders.tsx
@@ -0,0 +1,267 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { Product } from "../state/features/storeSlice";
+import { addToHashMap } from "../state/features/orderSlice";
+
+import { RootState } from "../state/store";
+import { Order, upsertOrders } from "../state/features/orderSlice";
+import { fetchAndEvaluateOrders } from "../utils/fetchOrders";
+import {
+ Catalogue,
+ ProductDataContainer,
+ setCatalogueHashMap,
+ setIsLoadingGlobal,
+ upsertMyOrders,
+ upsertProducts
+} from "../state/features/globalSlice";
+import { fetchAndEvaluateCatalogues } from "../utils/fetchCatalogues";
+import { ORDER_BASE, STORE_BASE } from "../constants/identifiers";
+
+interface Resource {
+ id: string;
+ updated: number;
+}
+export const useFetchOrders = () => {
+ const dispatch = useDispatch();
+ const hashMapOrders = useSelector(
+ (state: RootState) => state.order.hashMapOrders
+ );
+ const orders = useSelector((state: RootState) => state.order.orders);
+ const store = useSelector(
+ (state: RootState) => state.global?.currentStore?.id
+ );
+ const myOrders = useSelector((state: RootState) => state.global.myOrders);
+
+ const products: Product[] = useSelector(
+ (state: RootState) => state.global.products
+ );
+ const listProducts = useSelector(
+ (state: RootState) => state.global.listProducts
+ );
+
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+
+ const getOrder = async (user: string, orderId: string, content: any) => {
+ const res = await fetchAndEvaluateOrders({
+ user,
+ orderId,
+ content
+ });
+ dispatch(addToHashMap(res));
+ return res;
+ };
+
+ const getCatalogue = async (user: string, catalogueId: string) => {
+ const res = await fetchAndEvaluateCatalogues({
+ user,
+ catalogueId
+ });
+ if (res?.isValid) {
+ dispatch(setCatalogueHashMap(res));
+ return res;
+ }
+ };
+
+ const checkAndUpdateResource = React.useCallback(
+ (resource: Resource) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource: Order | undefined = hashMapOrders[resource.id];
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true;
+ } else if (
+ resource?.updated &&
+ existingResource?.updated &&
+ resource.updated > existingResource.updated
+ ) {
+ // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
+ return true;
+ } else {
+ return false;
+ }
+ },
+ [hashMapOrders]
+ );
+
+ const checkAndUpdateResourceCatalogue = React.useCallback(
+ (resource: { id: string }) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource: Catalogue | undefined =
+ catalogueHashMap[resource.id];
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true;
+ } else {
+ return false;
+ }
+ },
+ [catalogueHashMap]
+ );
+
+ // Get the orders that you've received from your own store
+ const getOrders = React.useCallback(async () => {
+ if (!store) return;
+
+ try {
+ dispatch(setIsLoadingGlobal(true));
+ const offset = orders.length;
+ const parts = store.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+
+ const query = `${ORDER_BASE}-${shortStoreId}`;
+ const url = `/arbitrary/resources/search?service=DOCUMENT_PRIVATE&query=${query}&limit=20&includemetadata=true&mode=ALL&offset=${offset}&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ const responseData = await response.json();
+
+ const structureData = responseData.map((order: any): Order => {
+ return {
+ created: order?.created,
+ updated: order?.updated,
+ user: order.name,
+ id: order.identifier
+ };
+ });
+
+ dispatch(upsertOrders(structureData));
+ // Get the order raw data from getOrder API Call only if the hashMapOrders doesn't have the order or if the order is more recently updated than the existing order
+ let localOrderHashMap: Record = {};
+ for (const content of structureData) {
+ if (hashMapOrders[content.id] || localOrderHashMap[content.id]) {
+ continue;
+ }
+ if (content.user && content.id) {
+ const res = checkAndUpdateResource(content);
+ if (res) {
+ const fetchedOrder: Order = await getOrder(
+ content.user,
+ content.id,
+ content
+ );
+ if (fetchedOrder) {
+ localOrderHashMap = {
+ ...localOrderHashMap,
+ [fetchedOrder.id]: fetchedOrder
+ };
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ }, [store, orders]);
+
+ // Get the orders that you've made from other stores (not the ones that you've received)
+ const getMyOrders = React.useCallback(
+ async (name: string) => {
+ try {
+ dispatch(setIsLoadingGlobal(true));
+ const offset = orders.length;
+ const query = `${ORDER_BASE}-`;
+ const url = `/arbitrary/resources/search?service=DOCUMENT_PRIVATE&query=${query}&limit=20&includemetadata=true&offset=${offset}&name=${name}&exactmatchnames=true&mode=ALL&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ const responseData = await response.json();
+
+ const structureData = responseData.map((order: any): Order => {
+ return {
+ created: order?.created,
+ updated: order?.updated,
+ user: order.name,
+ id: order.identifier
+ };
+ });
+
+ dispatch(upsertMyOrders(structureData));
+ // Get the order raw data from getOrder API Call only if the hashMapOrders doesn't have the order or if the order is more recently updated than the existing order
+ let localOrderHashMap: Record = {};
+ for (const content of structureData) {
+ if (hashMapOrders[content.id] || localOrderHashMap[content.id]) {
+ continue;
+ }
+ if (content.user && content.id) {
+ const res = checkAndUpdateResource(content);
+ if (res) {
+ const fetchedOrder: Order = await getOrder(
+ content.user,
+ content.id,
+ content
+ );
+ if (fetchedOrder) {
+ localOrderHashMap = {
+ ...localOrderHashMap,
+ [fetchedOrder.id]: fetchedOrder
+ };
+ }
+ }
+ }
+ }
+ } catch (error) {
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ },
+ [store, myOrders]
+ );
+
+ // Fetch the product resources and set it with its metadata inside the catalogueHashMap. The product resources were originally set in Redux in the global wrapper by fetching the data-container from QDN
+ const getProducts = React.useCallback(async () => {
+ try {
+ dispatch(setIsLoadingGlobal(true));
+ const offset = products.length;
+ const productList = listProducts.products;
+ const responseData = productList.slice(offset, offset + 20);
+ const structureData = responseData.map(
+ (product: ProductDataContainer): Product => {
+ return {
+ created: product?.created,
+ catalogueId: product.catalogueId,
+ id: product?.productId || "",
+ user: product?.user || "",
+ status: product?.status || ""
+ };
+ }
+ );
+
+ dispatch(upsertProducts(structureData));
+ for (const content of structureData) {
+ if (content.user && content.id) {
+ const res = checkAndUpdateResourceCatalogue({
+ id: content.catalogueId
+ });
+ if (res) {
+ getCatalogue(content.user, content.catalogueId);
+ }
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ }, [products, listProducts]);
+
+ return {
+ getOrder,
+ hashMapOrders,
+ checkAndUpdateResource,
+ getOrders,
+ getProducts,
+ getCatalogue,
+ checkAndUpdateResourceCatalogue,
+ getMyOrders
+ };
+};
diff --git a/src/hooks/useFetchProducts.tsx b/src/hooks/useFetchProducts.tsx
new file mode 100644
index 0000000..c9ec939
--- /dev/null
+++ b/src/hooks/useFetchProducts.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { addToHashMap } from '../state/features/storeSlice'
+
+import { RootState } from '../state/store'
+import { fetchAndEvaluateProducts } from '../utils/fetchPosts'
+
+interface Resource {
+ id: string
+ updated: number
+}
+export const useFetchProducts = () => {
+ const dispatch = useDispatch()
+ const hashMapProducts = useSelector(
+ (state: RootState) => state.store.hashMapProducts
+ )
+
+ const getProduct = async (user: string, productId: string, content: any) => {
+ const res = await fetchAndEvaluateProducts({
+ user,
+ productId,
+ content
+ })
+
+ dispatch(addToHashMap(res))
+ }
+
+ const checkAndUpdateResource = React.useCallback(
+ (resource: Resource) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource = hashMapProducts[resource.id]
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true
+ } else if (
+ resource?.updated &&
+ existingResource?.updated &&
+ resource.updated > existingResource.updated
+ ) {
+ // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
+ return true
+ } else {
+ return false
+ }
+ },
+ [hashMapProducts]
+ )
+
+ return {
+ getProduct,
+ hashMapProducts,
+ checkAndUpdateResource
+ }
+}
diff --git a/src/hooks/useFetchStoreReviews.tsx b/src/hooks/useFetchStoreReviews.tsx
new file mode 100644
index 0000000..77b10f8
--- /dev/null
+++ b/src/hooks/useFetchStoreReviews.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { addToHashMapStoreReviews } from "../state/features/storeSlice";
+import { RootState } from "../state/store";
+import { fetchAndEvaluateStoreReviews } from "../utils/fetchStoreReviews";
+
+interface Resource {
+ id: string;
+ updated?: number;
+}
+
+export const useFetchStoreReviews = () => {
+ const dispatch = useDispatch();
+ const hashMapStoreReviews = useSelector(
+ (state: RootState) => state.store.hashMapStoreReviews
+ );
+
+ // Get the review raw data from QDN
+ const getReview = async (owner: string, reviewId: string, content: any) => {
+ const res = await fetchAndEvaluateStoreReviews({
+ owner,
+ reviewId,
+ content
+ });
+
+ dispatch(addToHashMapStoreReviews(res));
+ };
+
+ // Make sure that raw data isn't already present in Redux hashmap
+ const checkAndUpdateResource = React.useCallback(
+ (resource: Resource) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource = hashMapStoreReviews[resource.id];
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true;
+ } else if (
+ resource?.updated &&
+ existingResource?.updated &&
+ resource.updated > existingResource.updated
+ ) {
+ // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
+ return true;
+ } else {
+ return false;
+ }
+ },
+ [hashMapStoreReviews]
+ );
+
+ return {
+ getReview,
+ checkAndUpdateResource
+ };
+};
diff --git a/src/hooks/useFetchStores.tsx b/src/hooks/useFetchStores.tsx
new file mode 100644
index 0000000..45afef2
--- /dev/null
+++ b/src/hooks/useFetchStores.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { addToHashMapStores } from "../state/features/storeSlice";
+
+import { RootState } from "../state/store";
+import { fetchAndEvaluateProducts } from "../utils/fetchPosts";
+import { fetchAndEvaluateStores } from "../utils/fetchStores";
+
+interface Resource {
+ id: string;
+ updated: number;
+}
+export const useFetchStores = () => {
+ const dispatch = useDispatch();
+ const hashMapStores = useSelector(
+ (state: RootState) => state.store.hashMapStores
+ );
+
+ const getStore = async (owner: string, storeId: string, content: any) => {
+ const res = await fetchAndEvaluateStores({
+ owner,
+ storeId,
+ content
+ });
+
+ dispatch(addToHashMapStores(res));
+ return res;
+ };
+
+ const checkAndUpdateResource = React.useCallback(
+ (resource: Resource) => {
+ // Check if the post exists in hashMapPosts
+ const existingResource = hashMapStores[resource.id];
+ if (!existingResource) {
+ // If the post doesn't exist, add it to hashMapPosts
+ return true;
+ } else if (
+ resource?.updated &&
+ existingResource?.updated &&
+ resource.updated > existingResource.updated
+ ) {
+ // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts
+ return true;
+ } else {
+ return false;
+ }
+ },
+ [hashMapStores]
+ );
+
+ return {
+ getStore,
+ checkAndUpdateResource
+ };
+};
diff --git a/src/hooks/useProductsToSaveLocalStorage.tsx b/src/hooks/useProductsToSaveLocalStorage.tsx
new file mode 100644
index 0000000..5c85793
--- /dev/null
+++ b/src/hooks/useProductsToSaveLocalStorage.tsx
@@ -0,0 +1,26 @@
+import { useState, useEffect } from "react";
+import { Product } from "../state/features/storeSlice";
+import { RootState } from "../state/store";
+import { useSelector } from "react-redux";
+
+export const useProductsToSaveLocalStorage = () => {
+ const { currentStore } = useSelector((state: RootState) => state.global);
+
+ const [productsToSaveLS, setProductsToSaveLS] = useState(() => {
+ // Retrieve the productsToSave object from local storage or initialize as an empty object
+ const storedProductsToSave = localStorage.getItem("productsToSave");
+ return storedProductsToSave ? JSON.parse(storedProductsToSave) : {};
+ });
+
+ useEffect(() => {
+ // Store the productsToSave object in local storage whenever it changes
+ localStorage.setItem("productsToSave", JSON.stringify(productsToSaveLS));
+ }, [productsToSaveLS]);
+
+ const saveProductsToLocalStorage = (newProductsToSave: any) => {
+ // Update the productsToSave state
+ setProductsToSaveLS(newProductsToSave);
+ };
+
+ return { productsToSaveLS, saveProductsToLocalStorage };
+};
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..9855117
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,178 @@
+@font-face {
+ font-family: "Cambon Light";
+ src: url("./styles/fonts/Cambon-Light.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Merriweather Sans";
+ src: url("./styles/fonts/Merriweather Sans.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Karla";
+ src: url("./styles/fonts/Karla.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Proxima Nova";
+ src: url("./styles/fonts/ProximaNova.otf") format("opentype");
+}
+
+@font-face {
+ font-family: "Raleway";
+ src: url("./styles/fonts/Raleway.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Catamaran";
+ src: url("./styles/fonts/Catamaran.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Oxygen";
+ src: url("./styles/fonts/Oxygen.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Cairo";
+ src: url("./styles/fonts/Cairo.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Montserrat";
+ src: url("./styles/fonts/Montserrat.ttf") format("truetype");
+}
+
+:root {
+ padding: 0px;
+ margin: 0px;
+ box-sizing: border-box;
+}
+
+.line-clamp {
+ height: 100px;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 5; /* number of lines to show */
+ -webkit-box-orient: vertical;
+ text-overflow: ellipsis;
+}
+
+.edit-btn:hover {
+ opacity: 0.75;
+ transition: 0.2s all;
+}
+
+.post-image {
+ max-width: 100%;
+ border-radius: 5px;
+ width: 100%;
+ height: 100%;
+}
+
+.grid-item {
+ /* Other styles */
+ /* overflow: auto; */
+}
+
+.grid-item-view {
+ /* Other styles */
+ /* overflow: auto; */
+}
+
+.test-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ min-height: 25px;
+}
+
+.test-grid-item {
+ border: 1px solid powderblue;
+}
+
+body::-webkit-scrollbar-track {
+ background-color: transparent;
+}
+
+body::-webkit-scrollbar-track:hover {
+ background-color: transparent;
+}
+
+body::-webkit-scrollbar {
+ width: 16px;
+ height: 10px;
+ background-color: white;
+}
+
+body::-webkit-scrollbar-thumb {
+ background-color: #838eee;
+ border-radius: 8px;
+ background-clip: content-box;
+ border: 4px solid transparent;
+}
+
+body::-webkit-scrollbar-thumb:hover {
+ background-color: #6270f0;
+}
+
+.MuiList-root::-webkit-scrollbar-track {
+ background-color: transparent;
+}
+.MuiList-root::-webkit-scrollbar-track:hover {
+ background-color: transparent;
+}
+
+.MuiList-root::-webkit-scrollbar {
+ width: 14px;
+ height: 10px;
+ background-color: white;
+}
+
+.MuiList-root::-webkit-scrollbar-thumb {
+ background-color: lightgray;
+ border-radius: 8px;
+ background-clip: content-box;
+ border: 4px solid transparent;
+}
+
+.MuiList-root::-webkit-scrollbar-thumb:hover {
+ background-color: lightslategray;
+}
+
+.my-masonry-grid {
+ display: -webkit-box; /* Not needed if autoprefixing */
+ display: -ms-flexbox; /* Not needed if autoprefixing */
+ display: flex;
+ margin-left: -20px; /* gutter size offset */
+ width: auto;
+ padding: 15px 20px;
+}
+
+.my-masonry-grid_column {
+ padding-left: 20px; /* gutter size */
+ background-clip: padding-box;
+}
+
+/* Style your items */
+.my-masonry-grid_column > li {
+ /* change div to reference your elements you put in */
+ margin-bottom: 30px;
+}
+
+.my-svg path {
+ fill: red;
+}
+
+.qortal-link {
+ text-decoration: none; /* Removes the underline */
+ color: inherit; /* Inherits the color of the parent element */
+}
+.qortal-link:hover,
+a:focus {
+ text-decoration: underline; /* Adds underline on hover and focus for accessibility */
+}
diff --git a/src/index.d.ts b/src/index.d.ts
new file mode 100644
index 0000000..996a468
--- /dev/null
+++ b/src/index.d.ts
@@ -0,0 +1,9 @@
+declare module 'webworker:getBlogWorker' {
+ const value: new () => Worker;
+ export default value;
+}
+
+declare module 'webworker:decodeBase64' {
+ const value: new () => Worker
+ export default value
+}
\ No newline at end of file
diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts
new file mode 100644
index 0000000..48b7666
--- /dev/null
+++ b/src/interfaces/interfaces.ts
@@ -0,0 +1,8 @@
+export interface BlogContent {
+ postContent: any[]
+ title: string
+ createdAt: number
+ user?: any
+ postId?: string
+ layouts?: any
+}
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..b456834
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+import './index.css'
+import { HashRouter, BrowserRouter } from 'react-router-dom'
+interface CustomWindow extends Window {
+ _qdnBase: any // Replace 'any' with the appropriate type if you know it
+}
+
+const customWindow = window as unknown as CustomWindow
+
+// Now you can access the _qdnTheme property without TypeScript errors
+const baseUrl = customWindow?._qdnBase || ''
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+)
diff --git a/src/pages/Cart/Cart-styles.tsx b/src/pages/Cart/Cart-styles.tsx
new file mode 100644
index 0000000..98c2961
--- /dev/null
+++ b/src/pages/Cart/Cart-styles.tsx
@@ -0,0 +1,284 @@
+import { styled } from "@mui/system";
+import { Box, Typography, Grid } from "@mui/material";
+import { PlusCircleSVG } from "../../assets/svgs/PlusCircle";
+import { MinusCircleSVG } from "../../assets/svgs/MinusCircle";
+import { GarbageSVG } from "../../assets/svgs/GarbageSVG";
+
+export const CartContainer = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+ gap: 1,
+ flexGrow: 1,
+ overflow: "auto",
+ width: "100%",
+ padding: "0 10px",
+ overflowX: "hidden",
+}));
+
+export const ProductContainer = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "row",
+ flexWrap: "nowrap",
+ padding: "15px 25px",
+ borderTop: `1px solid ${theme.palette.background.paper}`,
+ borderBottom: `1px solid ${theme.palette.background.paper}`,
+ justifyContent: "space-evenly",
+ gap: "20px",
+ width: "100%",
+ height: "100%",
+ maxHeight: "250px",
+}));
+
+export const ColumnTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "20px",
+ fontWeight: 300,
+ letterSpacing: 0,
+ userSelect: "none",
+ padding: "10px",
+ color: theme.palette.text.primary,
+}));
+
+export const ProductInfoCol = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+ alignItems: "center",
+ flexGrow: 1,
+}));
+
+export const ProductDetailsCol = styled(Grid)(({ theme }) => ({
+ boxSizing: "border-box",
+ margin: "0px",
+ flexBasis: "100%",
+ WebkitBoxFlex: "0",
+ flexGrow: 0,
+ maxWidth: "100%",
+ display: "grid",
+ gridTemplateRows: "1fr auto",
+ height: "100%",
+ rowGap: "8px",
+}));
+
+export const ProductTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans",
+ fontSize: "22px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "nowrap",
+ width: "100%",
+}));
+
+export const ProductDescription = styled(Box)(({ theme }) => ({
+ display: "flex",
+ color: theme.palette.text.primary,
+ paddingRight: "5px",
+ margin: "0px",
+ fontWeight: 300,
+ lineHeight: 1.5,
+ letterSpacing: 0,
+ fontFamily: "Karla",
+ fontSize: "18px",
+ userSelect: "none",
+ overflowY: "auto",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "5px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: `4px solid ${theme.palette.background.default}`,
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+}));
+
+export const QuantityRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+}));
+
+export const ProductImage = styled("img")({
+ height: "140px",
+ width: "-webkit-fill-available",
+ borderRadius: "3px",
+ objectFit: "contain",
+});
+
+export const ProductDetailsRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ width: "100%",
+}));
+
+export const IconsRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "20px",
+}));
+
+export const RemoveQuantityButton = styled(MinusCircleSVG)(({ theme }) => ({
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.05)",
+ },
+}));
+
+export const AddQuantityButton = styled(PlusCircleSVG)(({ theme }) => ({
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.05)",
+ },
+}));
+
+export const GarbageIcon = styled(GarbageSVG)(({ theme }) => ({
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.05)",
+ },
+}));
+
+export const ProductPriceFont = styled(Typography)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ "& span": {
+ display: "flex",
+ alignItems: "center",
+ gap: "3px",
+ },
+}));
+
+export const TotalSumContainer = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "space-evenly",
+ border: `1px solid ${theme.palette.background.paper}`,
+ borderRadius: "3px",
+ padding: "15px",
+ backgroundColor: theme.palette.background.default,
+ height: "100%",
+ flexGrow: 1,
+ marginTop: "35px",
+ marginRight: "10px",
+}));
+
+export const TotalSumHeader = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "flex-start",
+ fontFamily: "Karla",
+ fontWeight: "bold",
+ fontSize: "18px",
+ userSelect: "none",
+ paddingBottom: "15px",
+}));
+
+export const TotalSumItems = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+ width: "100%",
+ gap: "10px",
+ paddingBottom: "15px",
+ borderBottom: `1px solid ${theme.palette.background.paper}`,
+}));
+
+export const TotalSumItem = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ width: "100%",
+ gap: "3px",
+}));
+
+export const TotalSumItemTitle = styled(Typography)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ letterSpacing: 0,
+ userSelect: "none",
+ color: theme.palette.text.primary,
+ gap: "3px",
+}));
+
+export const OrderTotalRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ fontFamily: "Karla",
+ fontWeight: "bold",
+ letterSpacing: 0,
+ borderBottom: `1px solid ${theme.palette.background.paper}`,
+ padding: "15px 0px",
+ gap: "3px",
+ width: "100%",
+ "& span": {
+ marginRight: "5px",
+ },
+}));
+
+export const ConfirmPurchaseContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: "15px",
+ padding: "25px",
+}));
+
+export const ConfirmPurchaseRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ fontFamily: "Karla",
+ fontSize: "21px",
+ fontWeight: 300,
+ letterSpacing: 0,
+ userSelect: "none",
+}));
+
+export const CoinToPayInRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ gap: "10px",
+ fontFamily: "Karla",
+ fontSize: "21px",
+ fontWeight: 300,
+ letterSpacing: 0,
+ padding: "2px 0",
+ userSelect: "none",
+}));
diff --git a/src/pages/Cart/Cart.tsx b/src/pages/Cart/Cart.tsx
new file mode 100644
index 0000000..48cdbec
--- /dev/null
+++ b/src/pages/Cart/Cart.tsx
@@ -0,0 +1,1359 @@
+import { useState, useEffect, useMemo } from "react";
+import { ReusableModal } from "../../components/modals/ReusableModal";
+import {
+ CircularProgress,
+ Grid,
+ useTheme,
+ useMediaQuery,
+ FormControl,
+ SelectChangeEvent,
+ Box,
+} from "@mui/material";
+import {
+ addQuantityToCart,
+ subtractQuantityFromCart,
+ removeCartFromCarts,
+ Order,
+ removeProductFromCart,
+} from "../../state/features/cartSlice";
+import ShortUniqueId from "short-unique-id";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../state/store";
+import { setIsOpen } from "../../state/features/cartSlice";
+import { objectToBase64 } from "../../utils/toBase64";
+import { MAIL_SERVICE_TYPE } from "../../constants/mail";
+import { Cart as CartInterface } from "../../state/features/cartSlice";
+import {
+ AddQuantityButton,
+ CartContainer,
+ CoinToPayInRow,
+ ColumnTitle,
+ ConfirmPurchaseContainer,
+ ConfirmPurchaseRow,
+ GarbageIcon,
+ IconsRow,
+ OrderTotalRow,
+ ProductContainer,
+ ProductDescription,
+ ProductDetailsCol,
+ ProductDetailsRow,
+ ProductImage,
+ ProductInfoCol,
+ ProductPriceFont,
+ ProductTitle,
+ QuantityRow,
+ RemoveQuantityButton,
+ TotalSumContainer,
+ TotalSumHeader,
+ TotalSumItem,
+ TotalSumItemTitle,
+ TotalSumItems,
+} from "./Cart-styles";
+import {
+ BackToStorefrontButton as BackToCheckoutButton,
+ BackToStorefrontButton as CheckoutButton,
+} from "../Store/Store/Store-styles";
+import {
+ Catalogue,
+ setIsLoadingGlobal,
+} from "../../state/features/globalSlice";
+import { QortalSVG } from "../../assets/svgs/QortalSVG";
+import { setNotification } from "../../state/features/notificationsSlice";
+import {
+ CreateButton as ConfirmPurchaseButton,
+ CancelButton,
+ CustomInputField,
+} from "../../components/modals/CreateStoreModal-styles";
+import {
+ CustomMenuItem,
+ CustomSelect,
+ InputFieldCustomLabel,
+} from "../ProductManager/NewProduct/NewProduct-styles";
+import countries from "../../constants/countries.json";
+import states from "../../constants/states.json";
+import moment from "moment";
+import { BackArrowSVG } from "../../assets/svgs/BackArrowSVG";
+import { CloseIconModal } from "../Store/StoreReviews/StoreReviews-styles";
+import { ORDER_BASE, STORE_BASE } from "../../constants/identifiers";
+import QORTLogo from "../../assets/img/qort.png";
+import ARRRLogo from "../../assets/img/arrr.png";
+import { AcceptedCoin } from "../StoreList/StoreList-styles";
+import { ARRRSVG } from "../../assets/svgs/ARRRSVG";
+import { setPreferredCoin } from "../../state/features/storeSlice";
+import { CoinFilter } from "../Store/Store/Store";
+
+/* Currency must be replaced in the order confirmation email by proper currency */
+
+interface CountryProps {
+ text: string;
+ value: string;
+}
+
+export const Cart = () => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+
+ const uid = new ShortUniqueId({
+ length: 10,
+ });
+ const mailUid = new ShortUniqueId();
+
+ const dispatch = useDispatch();
+ const username = useSelector((state: RootState) => state.auth.user?.name);
+ const usernamePublicKey = useSelector(
+ (state: RootState) => state.auth.user?.publicKey
+ );
+ // Redux state to open the cart
+ const isOpen = useSelector((state: RootState) => state.cart.isOpen);
+ // Redux state to get the current cart
+ const carts = useSelector((state: RootState) => state.cart.carts);
+ // Redux state to get the storeId
+ const storeOwner = useSelector((state: RootState) => state.store.storeOwner);
+ const storeId = useSelector((state: RootState) => state.store.storeId);
+ const hashMapStores = useSelector(
+ (state: RootState) => state.store.hashMapStores
+ );
+ // Redux state to get the catalogue hashmap
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+
+ // Coins to pay in options
+ const coinOptions = [
+ { value: "QORT", label: "QORT", icon: QORTLogo },
+ { value: "ARRR", label: "ARRR", icon: ARRRLogo },
+ ];
+
+ const [cartOrders, setCartOrders] = useState([]);
+ const [localCart, setLocalCart] = useState();
+ const [checkoutPage, setCheckoutPage] = useState(false);
+ const [customerName, setCustomerName] = useState("");
+ const [streetAddress, setStreetAddress] = useState("");
+ const [country, setCountry] = useState("");
+ const [state, setState] = useState("");
+ const [city, setCity] = useState("");
+ const [region, setRegion] = useState("");
+ const [zipCode, setZipCode] = useState("");
+ const [deliveryNote, setDeliveryNote] = useState("");
+ const [confirmPurchaseModalOpen, setConfirmPurchaseModalOpen] =
+ useState(false);
+ const preferredCoin = useSelector((state: RootState) => state.store.preferredCoin);
+ const [exchangeRate, setExchangeRate] = useState(
+ null
+ );
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const currentViewedStore = useSelector(
+ (state: RootState) => state.store.currentViewedStore
+ );
+
+
+ const calculateARRRExchangeRate = async()=> {
+ try {
+ const url = '/crosschain/price/PIRATECHAIN?maxtrades=10&inverse=true'
+ const info = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseDataStore = await info.text();
+
+ const ratio = +responseDataStore /100000000
+ if(isNaN(ratio)) throw new Error('Cannot get exchange rate')
+ setExchangeRate(ratio)
+ } catch (error) {
+ dispatch(setPreferredCoin(CoinFilter.qort))
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot get exchange rate- reverted to QORT",
+ })
+ );
+ }
+
+ }
+ const storeToUse = useMemo(()=> {
+ return currentViewedStore
+ }, [currentViewedStore])
+
+ const switchCoin = async ()=> {
+ dispatch(setIsLoadingGlobal(true));
+
+ await calculateARRRExchangeRate()
+ dispatch(setIsLoadingGlobal(false));
+
+
+ }
+
+ useEffect(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ switchCoin()
+ }
+ }, [preferredCoin, storeToUse])
+
+ const coinToUse = useMemo(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ return CoinFilter.arrr
+ } else {
+ return CoinFilter.qort
+ }
+ }, [preferredCoin, storeToUse])
+
+ // Set cart & orders to local state
+ useEffect(() => {
+ if (storeId && Object.keys(carts).length > 0) {
+ const shopCart: CartInterface = carts[storeId];
+ // Get the orders of this cart
+ const orders = shopCart?.orders || {};
+ setLocalCart(shopCart);
+ setCartOrders(Object.keys(orders));
+ }
+ }, [storeId, carts]);
+
+ // Set username to customer name on cart open
+
+ useEffect(() => {
+ if (username) setCustomerName(username);
+ }, [isOpen, username]);
+
+ const closeModal = () => {
+ dispatch(setIsOpen(false));
+ setCheckoutPage(false);
+ };
+
+ const handleSelectCountry = (event: SelectChangeEvent) => {
+ const optionId = event.target.value;
+ const selectedOption = countries.find(country => country.text === optionId);
+ setCountry(selectedOption?.text || null);
+ };
+
+ const handleSelectState = (event: SelectChangeEvent) => {
+ const optionId = event.target.value;
+ const selectedOption = states.find(state => state.text === optionId);
+ setState(selectedOption?.text || null);
+ };
+
+ // Check to see if any of the products in the cart are digital and that there are none which are physical
+ const isDigitalOrder = useMemo(() => {
+ if (!localCart) return false;
+ return Object.keys(localCart.orders).every(key => {
+ const order = localCart.orders[key];
+ const productId = order?.productId;
+ const catalogueId = order?.catalogueId;
+ let product = null;
+ if (productId && catalogueId && catalogueHashMap[catalogueId]) {
+ product = catalogueHashMap[catalogueId]?.products[productId];
+ }
+ if (!product) return false;
+ return product.type === "digital";
+ });
+ }, [localCart]);
+
+ const handlePurchase = async () => {
+ if (!localCart) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot find a cart! Please try refreshing the page and trying again!",
+ })
+ );
+ return;
+ }
+ if (!storeId) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot find a store! Please try refreshing the page and trying again!",
+ })
+ );
+ return;
+ }
+ if (!storeOwner) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot find a store owner! Please try refreshing the page and trying again!"
+ })
+ );
+ return;
+ }
+ if (cartOrders.length === 0) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "You have no items in your cart",
+ })
+ );
+ return;
+ }
+ if (
+ !isDigitalOrder &&
+ (!customerName ||
+ !streetAddress ||
+ !country ||
+ !city ||
+ (country === "United States" && !state) ||
+ (country !== "United States" && !region) ||
+ !zipCode)
+ ) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Please fill in all the required delivery fields",
+ })
+ );
+ return;
+ }
+ const details = (cartOrders || []).reduce(
+ (acc: any, key) => {
+ const order = localCart?.orders[key];
+ const quantity = order?.quantity;
+ const productId = order?.productId;
+ const catalogueId = order?.catalogueId;
+ let product = null;
+ if (productId && catalogueId && catalogueHashMap[catalogueId]) {
+ product = catalogueHashMap[catalogueId]?.products[productId];
+ }
+ if (!product) return acc;
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+
+ if(coinToUse === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ } else if(price && exchangeRate && coinToUse !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+ if (!price) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Could not find the price",
+ })
+ );
+ return;
+ }
+ const totalProductPrice = price * quantity;
+ acc[productId] = {
+ product,
+ catalogueId,
+ quantity,
+ pricePerUnit: price,
+ totalProductPrice: price * quantity,
+ };
+ acc["totalPrice"] = acc["totalPrice"] + totalProductPrice;
+ return acc;
+ },
+ {
+ totalPrice: 0,
+ }
+ );
+ details["totalPrice"] = details["totalPrice"].toFixed(8);
+ const priceToPay = details["totalPrice"];
+ if (!storeOwner) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot find the store owner",
+ })
+ );
+ return;
+ }
+ let res = await qortalRequest({
+ action: "GET_NAME_DATA",
+ name: storeOwner,
+ });
+ const address = res.owner;
+ const resAddress = await qortalRequest({
+ action: "GET_ACCOUNT_DATA",
+ address: address,
+ });
+ if (!resAddress?.publicKey) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot find the store owner",
+ })
+ );
+ return;
+ }
+
+ let responseSendCoin = null
+ let signature = null
+
+ if(coinToUse === CoinFilter.qort){
+ responseSendCoin = await qortalRequest({
+ action: "SEND_COIN",
+ coin: "QORT",
+ destinationAddress: address,
+ amount: priceToPay,
+ });
+ signature = responseSendCoin.signature;
+
+ } else if(coinToUse === CoinFilter.arrr){
+
+ if(!storeToUse?.foreignCoins?.ARRR) throw new Error('Store has not set an ARRR address')
+ responseSendCoin = await qortalRequest({
+ action: "SEND_COIN",
+ coin: "ARRR",
+ destinationAddress: storeToUse?.foreignCoins?.ARRR.trim(),
+ amount: priceToPay,
+ });
+ } else {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Currency not found",
+ })
+ );
+ }
+
+ try {
+ dispatch(setIsLoadingGlobal(true));
+ // Validate whether order is coming from the USA to put state instead of region
+ const orderObjectNotUSA: any = {
+ created: Date.now(),
+ version: 1,
+ details,
+ storeName: hashMapStores[storeId]?.title,
+ sellerName: storeOwner,
+ delivery: {
+ customerName,
+ shippingAddress: {
+ streetAddress,
+ city,
+ region,
+ country,
+ zipCode,
+ deliveryNote,
+ },
+ },
+ payment: {
+ total: priceToPay,
+ currency: coinToUse,
+ transactionSignature: signature,
+ arrrAddressUsed: coinToUse === CoinFilter.arrr ? storeToUse?.foreignCoins?.ARRR.trim() : ""
+ },
+ communicationMethod: ["Q-Mail"],
+ };
+ const orderObjectUSA: any = {
+ created: Date.now(),
+ version: 1,
+ details,
+ storeName: hashMapStores[storeId]?.title,
+ sellerName: storeOwner,
+ delivery: {
+ customerName,
+ shippingAddress: {
+ streetAddress,
+ city,
+ state,
+ country,
+ zipCode,
+ deliveryNote,
+ },
+ },
+ payment: {
+ total: priceToPay,
+ currency: coinToUse,
+ transactionSignature: signature,
+ arrrAddressUsed: coinToUse === CoinFilter.arrr ? storeToUse?.foreignCoins?.ARRR.trim() : ""
+ },
+ communicationMethod: ["Q-Mail"],
+ };
+ const orderToBase64 = await objectToBase64(
+ country === "United States" ? orderObjectUSA : orderObjectNotUSA
+ );
+ const orderId = uid();
+ if (!storeId) throw new Error("Cannot find store identifier");
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+ const productRequestBody = {
+ action: "PUBLISH_QDN_RESOURCE",
+ identifier: `${ORDER_BASE}-${shortStoreId}-${orderId}`,
+ name: username,
+ service: "DOCUMENT_PRIVATE",
+ data64: orderToBase64,
+ };
+
+ const mailId = mailUid();
+ let identifier = `qortal_qmail_${storeOwner?.slice(0, 20)}_${address.slice(
+ -6
+ )}_mail_${mailId}`;
+
+ // HTML with the order details being sent to seller by Q-Mail
+ const htmlContent = `
+
+
+ You have sold a product for your shop!
+
+
+ ${Object.keys(details)
+ .filter(key => key !== "totalPrice")
+ .map(key => {
+ const { product, quantity, pricePerUnit, totalProductPrice } =
+ details[key];
+ return `
+
+
Shop: ${
+ hashMapStores[storeId]?.title
+ }
+
+
Product: ${
+ product.title
+ }
+
x ${quantity}
+
+
+
+ Price:
+
+ ${
+ orderObjectUSA?.payment?.currency === "QORT"
+ ? `
`
+ : `
`
+ } ${pricePerUnit} ${
+ country === "United States"
+ ? orderObjectUSA?.payment?.currency
+ : orderObjectNotUSA?.payment?.currency
+ }
+
+
+
+
+
+ Total Price:
+
+ ${
+ orderObjectUSA?.payment?.currency === "QORT"
+ ? `
`
+ : `
`
+ }
+ ${totalProductPrice} ${
+ country === "United States"
+ ? orderObjectUSA?.payment?.currency
+ : orderObjectNotUSA?.payment?.currency
+ }
+
+
+
+
+ `;
+ })
+ .join("")}
+
+ ${
+ !isDigitalOrder
+ ? `
+
+
+ Delivery Information
+
+
+
+ Qortal Username: ${username}
+
+
+ Customer Name: ${customerName}
+
+
+ Street Address: ${streetAddress}
+
+
+ City: ${city}
+
+ ${
+ country === "United States"
+ ? `
+ State: ${state}
+
`
+ : `
+ Region: ${region}
+
`
+ }
+
+ Country: ${country}
+
+
+ Zip Code: ${zipCode}
+
+
+ Delivery Note: ${deliveryNote}
+
+
+
+ `
+ : `
`
+ }
+
+
+ Date of purchase
+
+
+
+ Date: ${moment(
+ country === "United States"
+ ? orderObjectUSA?.created
+ : orderObjectNotUSA?.created
+ ).format("llll")}
+
+
+
+
+ `;
+
+ const mailObject: any = {
+ title: `Order for shop ${shortStoreId} from ${username}`,
+ subject: "New order",
+ createdAt: Date.now(),
+ version: 1,
+ attachments: [],
+ textContent: "",
+ htmlContent: htmlContent,
+ generalData: {
+ thread: [],
+ },
+ recipient: storeOwner,
+ };
+
+ const mailObjectToBase64 = await objectToBase64(mailObject);
+ let mailRequestBody: any = {
+ action: "PUBLISH_QDN_RESOURCE",
+ name: username,
+ service: MAIL_SERVICE_TYPE,
+ data64: mailObjectToBase64,
+ identifier,
+ };
+
+ const multiplePublish = {
+ action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
+ resources: [productRequestBody, mailRequestBody],
+ encrypt: true,
+ publicKeys: [resAddress.publicKey, usernamePublicKey],
+ };
+ await qortalRequest(multiplePublish);
+ // Clear this cart state from global carts redux
+ dispatch(removeCartFromCarts({ storeId }));
+ // Clear cart local state
+ setCheckoutPage(false);
+ setCustomerName("");
+ setStreetAddress("");
+ setCountry("");
+ setState("");
+ setCity("");
+ setRegion("");
+ setZipCode("");
+ setDeliveryNote("");
+ setConfirmPurchaseModalOpen(false);
+ // Close the modal and set notification message
+ closeModal();
+ dispatch(
+ setNotification({
+ alertType: "success",
+ msg: "Order placed successfully!",
+ })
+ );
+ } catch (error) {
+ console.log({ error });
+ const errMsg = "Order failed to be placed! Please try again!";
+ dispatch(
+ setNotification({
+ msg: errMsg,
+ alertType: "error",
+ })
+ );
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ };
+
+ // Calculate sum total for cart
+ const calculateCartTotalSum = (
+ cartOrders: string[],
+ localCart: CartInterface | undefined,
+ catalogueHashMap: Record
+ ): number => {
+ let totalSum = 0;
+ cartOrders.forEach(key => {
+ const order: Order | undefined = localCart?.orders[key];
+ const { quantity, productId, catalogueId } = order || {};
+
+ if (productId && catalogueId && catalogueHashMap[catalogueId]) {
+ const product = catalogueHashMap[catalogueId].products[productId];
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+
+ if(coinToUse === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ } else if(price && exchangeRate && coinToUse !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+
+ if (price !== null && price !== undefined && quantity !== undefined) {
+ totalSum += price * quantity;
+ }
+ }
+ });
+
+ return totalSum;
+ };
+
+ const totalSum = useMemo(
+ () => calculateCartTotalSum(cartOrders, localCart, catalogueHashMap),
+ [cartOrders, localCart, catalogueHashMap, coinToUse, exchangeRate]
+ );
+
+ return (
+ <>
+
+
+
+ {!localCart || cartOrders.length === 0 ? (
+
+ No items in cart
+
+ ) : (
+ <>
+ {checkoutPage && !isDigitalOrder ? (
+ <>
+ {
+ setCheckoutPage(false);
+ }}
+ style={{ marginBottom: "5px" }}
+ >
+ {" "}
+ Checkout
+
+ Delivery Information
+
+
+ {/* Customer Name */}
+
+
+ setCustomerName(e.target.value as string)
+ }
+ />
+
+ {/* Street Address */}
+
+
+ setStreetAddress(e.target.value as string)
+ }
+ />
+
+ {/* City */}
+
+ setCity(e.target.value as string)}
+ />
+
+ {/* Country */}
+
+
+ Country
+
+ {
+ handleSelectCountry(
+ event as SelectChangeEvent
+ );
+ }}
+ required
+ fullWidth
+ >
+ {countries.map((country: CountryProps) => {
+ return (
+
+ {country.text}
+
+ );
+ })}
+
+
+
+
+ {/* State or region */}
+ {country === "United States" ? (
+
+
+ State
+
+ {
+ handleSelectState(
+ event as SelectChangeEvent
+ );
+ }}
+ required
+ fullWidth
+ >
+ {states.map((state: CountryProps) => {
+ return (
+
+ {state.text}
+
+ );
+ })}
+
+
+ ) : (
+
+
+ setRegion(e.target.value as string)
+ }
+ />
+
+ )}
+ {/* Zip Code */}
+
+ setZipCode(e.target.value as string)}
+ />
+
+ {/* Delivery Note */}
+
+
+ setDeliveryNote(e.target.value as string)
+ }
+ />
+
+
+
+ >
+ ) : (
+ <>
+ My Cart
+ {(cartOrders || []).map(key => {
+ const order = localCart?.orders[key];
+ const quantity = order?.quantity;
+ const productId = order?.productId;
+ const catalogueId = order?.catalogueId;
+ let product = null;
+ if (
+ productId &&
+ catalogueId &&
+ catalogueHashMap[catalogueId]
+ ) {
+ product =
+ catalogueHashMap[catalogueId]?.products[productId];
+ }
+ if (!product) return null;
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+
+ if(coinToUse === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ } else if(price && exchangeRate && coinToUse !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+ return (
+
+
+ {product.title}
+
+
+
+
+ {product.description}
+
+
+
+ Price per unit:
+
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+ {price}
+
+
+ {price && (
+
+ Total Price:
+
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+ {price * quantity}
+
+
+ )}
+
+ {
+ dispatch(
+ removeProductFromCart({
+ storeId,
+ productId,
+ })
+ );
+ }}
+ color={theme.palette.text.primary}
+ height={"30"}
+ width={"30"}
+ />
+
+ {
+ dispatch(
+ subtractQuantityFromCart({
+ storeId,
+ productId,
+ })
+ );
+ }}
+ color={theme.palette.text.primary}
+ height={"30"}
+ width={"30"}
+ />
+ {quantity}
+
+ dispatch(
+ addQuantityToCart({
+ storeId,
+ productId,
+ })
+ )
+ }
+ color={theme.palette.text.primary}
+ height={"30"}
+ width={"30"}
+ />
+
+
+
+
+
+ );
+ })}
+ >
+ )}
+ >
+ )}
+
+ {localCart && cartOrders.length > 0 && (
+
+
+ Order Summary
+
+ {(cartOrders || []).map(key => {
+ const order = localCart?.orders[key];
+ const quantity = order?.quantity;
+ const productId = order?.productId;
+ const catalogueId = order?.catalogueId;
+ let product = null;
+ if (
+ productId &&
+ catalogueId &&
+ catalogueHashMap[catalogueId]
+ ) {
+ product =
+ catalogueHashMap[catalogueId]?.products[productId];
+ }
+ if (!product) return null;
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+
+ if(coinToUse === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ } else if(price && exchangeRate && coinToUse !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+ return (
+
+
+ x{quantity} {product.title}
+
+
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+ {price}
+
+
+ );
+ })}
+
+
+ Total:
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+ {totalSum}
+
+ {(checkoutPage || (!checkoutPage && isDigitalOrder)) && (
+
+
+
+ Coin to pay with
+
+ {
+ const option = coinOptions.find(
+ opt => opt.value === coinToUse
+ );
+ return (
+
+
+ {option?.label}
+
+ );
+ }}
+ onChange={event => {
+ dispatch(setPreferredCoin(event.target.value))
+ }}
+ required
+ fullWidth
+ >
+
+
+ QORT
+
+
+
+ ARRR
+
+
+
+
+ )}
+ {
+ if (checkoutPage) {
+ handlePurchase();
+ } else if (!checkoutPage && !isDigitalOrder) {
+ setCheckoutPage(true);
+ } else if (!checkoutPage && isDigitalOrder) {
+ setConfirmPurchaseModalOpen(true);
+ } else {
+ setCheckoutPage(true);
+ }
+ }}
+ >
+ {checkoutPage || (!checkoutPage && isDigitalOrder)
+ ? "Purchase"
+ : "Checkout"}
+
+
+
+ )}
+
+
+
+
+
+
+ Are you sure you wish to complete this purchase?
+
+
+ {
+ setConfirmPurchaseModalOpen(false);
+ }}
+ >
+ Cancel
+
+
+ Confirm
+
+
+
+
+ >
+ );
+};
diff --git a/src/pages/MyOrders/MyOrders.tsx b/src/pages/MyOrders/MyOrders.tsx
new file mode 100644
index 0000000..ac49e0f
--- /dev/null
+++ b/src/pages/MyOrders/MyOrders.tsx
@@ -0,0 +1,78 @@
+import { useMemo, useState, useCallback, useEffect } from "react";
+import { useSelector } from "react-redux";
+import { RootState } from "../../state/store";
+import { Box } from "@mui/material";
+import LazyLoad from "../../components/common/LazyLoad";
+import { ShowOrder } from "../ProductManager/ShowOrder/ShowOrder";
+import { useFetchOrders } from "../../hooks/useFetchOrders";
+import { OrderTable } from "../ProductManager/OrderTable/OrderTable";
+
+export const MyOrders = () => {
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const myOrders = useSelector((state: RootState) => state.global.myOrders);
+ const store = useSelector(
+ (state: RootState) => state.global?.currentStore?.id
+ );
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [order, setOrder] = useState(null);
+
+ const userName: string = useMemo(() => {
+ if (!user?.name) return "";
+ return user.name;
+ }, [user]);
+
+ const { getMyOrders } = useFetchOrders();
+
+ const handleGetOrders = useCallback(async () => {
+ if (!userName) return;
+ await getMyOrders(userName);
+ }, [getMyOrders, userName]);
+
+ // Get My Orders if store changes (on hyperlink for example, or if there's a page refresh)
+ useEffect(() => {
+ if (userName) {
+ handleGetOrders();
+ }
+ }, [userName]);
+
+ return (
+
+
+
+ {
+ setOrder(order);
+ setIsOpen(true);
+ }}
+ data={myOrders}
+ from="MyOrders"
+ />
+
+
+
+ );
+};
diff --git a/src/pages/Product/ProductPage-styles.tsx b/src/pages/Product/ProductPage-styles.tsx
new file mode 100644
index 0000000..6e6c4f3
--- /dev/null
+++ b/src/pages/Product/ProductPage-styles.tsx
@@ -0,0 +1,190 @@
+import { styled } from "@mui/system";
+import { Box, Button, Switch, Typography } from "@mui/material";
+import ARRRLogoGold from "../../assets/img/arrr.png";
+import ARRRLogoWhite from "../../assets/img/ArrrLogoWhite.png";
+
+export const ProductLayout = styled(Box)(({ theme }) => ({
+ position: "relative",
+ display: "grid",
+ gridTemplateColumns: "1fr 2fr",
+ padding: "70px 45px 45px 45px",
+ gap: "60px",
+}));
+
+export const CartBox = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ top: "15px",
+ right: "35px",
+ display: "flex",
+ justifyContent: "end",
+}));
+
+export const ProductNotFound = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ top: "50px",
+ left: "50%",
+ transform: "translateX(-50%)",
+ fontFamily: "Raleway",
+ fontSize: "22px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const ProductDetailsContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-start",
+ justifyContent: "flex-start",
+ gap: "30px",
+}));
+
+export const ProductTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans, sans-serif",
+ color: theme.palette.text.primary,
+}));
+
+export const ProductDescription = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ letterSpacing: 0,
+ color: theme.palette.text.primary,
+ fontWeight: 400,
+}));
+
+export const ProductPriceRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ gap: "25px",
+});
+
+export const ProductPrice = styled(Typography)(({ theme }) => ({
+ display: "flex",
+ gap: "5px",
+ fontFamily: "Karla",
+ fontSize: "24px",
+ letterSpacing: 0,
+ lineHeight: "28px",
+ color: theme.palette.text.primary,
+ fontWeight: 400,
+}));
+
+export const AddToCartButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "4px 12px",
+ width: "300px",
+ fontFamily: "Raleway",
+ fontSize: "18px",
+ color: "#ffffff",
+ backgroundColor: theme.palette.secondary.main,
+ border: "none",
+ borderRadius: "5px",
+ gap: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.secondary.dark,
+ },
+}));
+
+export const UnavailableButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "4px 12px",
+ width: "300px",
+ fontFamily: "Raleway",
+ fontSize: "18px",
+ color: "#ffffff",
+ backgroundColor: "#da2d2d",
+ border: "none",
+ borderRadius: "5px",
+ gap: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#b82525",
+ },
+}));
+
+export const BackToStoreButton = styled(Button)(({ theme }) => ({
+ position: "absolute",
+ top: "15px",
+ left: "35px",
+ display: "flex",
+ alignItems: "center",
+ padding: "2px 12px",
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ gap: "3px",
+ color: "#ffffff",
+ backgroundColor: theme.palette.mode === "dark" ? "#bdba02" : "#e1dd04",
+ border: "none",
+ borderRadius: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.mode === "dark" ? "#a5a201" : "#c7c402",
+ },
+}));
+
+export const ArrrSwitch = styled(Switch)(({ theme }) => ({
+ position: "absolute",
+ bottom: "-200px",
+ right: "30px",
+ width: 88,
+ height: 57,
+ padding: 7,
+ borderWidth: "20px",
+ "& .MuiSwitch-switchBase": {
+ margin: 1,
+ padding: 0,
+ transform: "translateX(6px)",
+ "&.Mui-checked": {
+ backgroundColor: "transparent",
+ color: "#fff",
+ transform: "translateX(38px)",
+ "& .MuiSwitch-thumb:before": {
+ content: "''",
+ position: "absolute",
+ width: "100%",
+ height: "100%",
+ left: 0,
+ top: 0,
+ backgroundRepeat: "no-repeat",
+ backgroundPosition: "center",
+ borderRadius: "50%",
+ backgroundImage: `url(${ARRRLogoGold})`,
+ filter: "contrast(1)",
+ },
+ "& + .MuiSwitch-track": {
+ opacity: 1,
+ backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be",
+ },
+ },
+ },
+ "& .MuiSwitch-thumb": {
+ backgroundColor: "transparent",
+ width: 45,
+ height: 45,
+ transform: "translate(0px, 4px)",
+ backgroundRepeat: "no-repeat",
+ backgroundPosition: "center",
+ borderRadius: "50%",
+ "&:before": {
+ filter: "contrast(0.6) blur(0.3px)",
+ content: "''",
+ position: "absolute",
+ width: "100%",
+ height: "100%",
+ left: 0,
+ top: 0,
+ backgroundImage: `url(${ARRRLogoWhite})`,
+ backgroundSize: "cover",
+ },
+ },
+ "& .MuiSwitch-track": {
+ opacity: 1,
+ backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be",
+ borderRadius: "20px",
+ },
+}));
diff --git a/src/pages/Product/ProductPage.tsx b/src/pages/Product/ProductPage.tsx
new file mode 100644
index 0000000..e8ed91a
--- /dev/null
+++ b/src/pages/Product/ProductPage.tsx
@@ -0,0 +1,330 @@
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ Cart as CartInterface,
+ setIsOpen,
+ setProductToCart,
+} from "../../state/features/cartSlice";
+import { RootState } from "../../state/store";
+import { useNavigate, useParams } from "react-router-dom";
+import { useTheme } from "@mui/material";
+import TabImageList from "../../components/common/TabImageList/TabImageList";
+import { Product, setPreferredCoin } from "../../state/features/storeSlice";
+import DangerousIcon from "@mui/icons-material/Dangerous";
+import { CartIcon } from "../../components/layout/Navbar/Navbar-styles";
+import {
+ CartIconContainer,
+ NotificationBadge,
+} from "../Store/Store/Store-styles";
+import { useFetchOrders } from "../../hooks/useFetchOrders";
+import {
+ AddToCartButton,
+ ArrrSwitch,
+ BackToStoreButton,
+ CartBox,
+ ProductDescription,
+ ProductDetailsContainer,
+ ProductLayout,
+ ProductNotFound,
+ ProductPrice,
+ ProductPriceRow,
+ ProductTitle,
+ UnavailableButton,
+} from "./ProductPage-styles";
+import { QortalSVG } from "../../assets/svgs/QortalSVG";
+import { setNotification } from "../../state/features/notificationsSlice";
+import { BackArrowSVG } from "../../assets/svgs/BackArrowSVG";
+import {
+ NumericTextFieldQshop,
+ NumericTextFieldRef,
+ Variant,
+} from "../../components/common/NumericTextFieldQshop";
+import { ARRRSVG } from "../../assets/svgs/ARRRSVG";
+import { CoinFilter } from "../Store/Store/Store";
+import { setIsLoadingGlobal } from "../../state/features/globalSlice";
+
+export const ProductPage = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const params = useParams();
+ const theme = useTheme();
+ const ref = useRef(null);
+
+ // Get params for when user refreshes page or receives url link
+ const storeOwner: string = params.user || "";
+ const productID: string = params.product || "";
+ const catalogueID: string = params.catalogue || "";
+ const storeId: string = params.store || "";
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const currentViewedStore = useSelector(
+ (state: RootState) => state.store.currentViewedStore
+ );
+ const [product, setProduct] = useState(null);
+ const [cartAddAmount, setCartAddAmount] = useState(0);
+ const preferredCoin = useSelector((state: RootState) => state.store.preferredCoin);
+ const [exchangeRate, setExchangeRate] = useState(
+ null
+ );
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+ const carts = useSelector((state: RootState) => state.cart.carts);
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const calculateARRRExchangeRate = async()=> {
+ try {
+ const url = '/crosschain/price/PIRATECHAIN?maxtrades=10&inverse=true'
+ const info = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseDataStore = await info.text();
+
+ const ratio = +responseDataStore /100000000
+ if(isNaN(ratio)) throw new Error('Cannot get exchange rate')
+ setExchangeRate(ratio)
+ } catch (error) {
+ dispatch(setPreferredCoin(CoinFilter.qort))
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot get exchange rate- reverted to QORT",
+ })
+ );
+ }
+
+ }
+ const storeToUse = useMemo(()=> {
+ return storeOwner === user?.name
+ ? currentStore
+ : currentViewedStore
+ }, [storeOwner, user?.name, currentStore, currentViewedStore])
+
+ const switchCoin = async ()=> {
+ dispatch(setIsLoadingGlobal(true));
+
+ await calculateARRRExchangeRate()
+ dispatch(setIsLoadingGlobal(false));
+
+
+ }
+
+ useEffect(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ switchCoin()
+ }
+ }, [preferredCoin, storeToUse])
+
+ const coinToUse = useMemo(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ return CoinFilter.arrr
+ } else {
+ return CoinFilter.qort
+ }
+ }, [preferredCoin, storeToUse])
+
+
+ const { checkAndUpdateResourceCatalogue, getCatalogue } = useFetchOrders();
+
+ const minCart = 1;
+ const maxCart = 99;
+
+ // Set cart notifications when cart changes
+ useEffect(() => {
+ if (user?.name && storeId) {
+ const shopCart: CartInterface = carts[storeId];
+ // Get the orders of this cart
+ const orders = shopCart?.orders || {};
+ let totalQuantity = 0;
+ Object.keys(orders).forEach(key => {
+ const order = orders[key];
+ const { quantity } = order;
+ totalQuantity += quantity;
+ });
+ setCartAddAmount(totalQuantity);
+ }
+ }, [carts, user, storeId]);
+
+ const getProductData = async () => {
+ const productInRedux = catalogueHashMap[catalogueID]?.products[productID];
+ const paramsLoaded = productID && catalogueID && storeOwner && storeId;
+ if (productInRedux) {
+ setProduct(productInRedux);
+ return;
+ } else if (!productInRedux && paramsLoaded) {
+ checkAndUpdateResourceCatalogue({ id: catalogueID });
+ await getCatalogue(storeOwner, catalogueID);
+ } else {
+ return null;
+ }
+ };
+
+ useEffect(() => {
+ const awaitProductData = async () => {
+ await getProductData();
+ };
+ awaitProductData();
+ }, [catalogueHashMap]);
+
+
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+
+ if(coinToUse === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ } else if(price && exchangeRate && coinToUse !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+
+ const addToCart = () => {
+ if (user?.name === storeOwner) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "You own this store! You cannot add your own products to your cart!",
+ })
+ );
+ return;
+ }
+ if (product && ref?.current?.getTextFieldValue() !== "") {
+ for (let i = 0; i < Number(ref?.current?.getTextFieldValue() || 0); i++) {
+ dispatch(
+ setProductToCart({
+ productId: product.id,
+ catalogueId: product.catalogueId,
+ storeId,
+ storeOwner,
+ })
+ );
+ }
+ }
+ };
+ const status = product?.status;
+ const available = status === "AVAILABLE";
+ const availableJSX = (
+ <>
+
+
+
+ Add to Cart
+
+ >
+ );
+
+ const unavailableJSX = (
+ {
+ if (user?.name === storeOwner) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "You own this store! You cannot add your own products to your cart!",
+ })
+ );
+ return;
+ }
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "This product is out of stock!",
+ })
+ );
+ }}
+ >
+
+ Out of Stock
+
+ );
+
+ return product ? (
+
+ {
+ navigate(`/${storeOwner}/${storeId}`);
+ }}
+ >
+
+ Store
+
+
+
+ {product.title}
+
+ {product.description}
+
+
+ {coinToUse === CoinFilter.qort && (
+
+ {" "}
+ {price}
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ {" "}
+ {price}
+
+ )}
+
+ {available ? availableJSX : unavailableJSX}
+
+ {/* Toggle to show price of ARRR or not */}
+ {
+ if (coinToUse !== CoinFilter.arrr) {
+ dispatch(setPreferredCoin(CoinFilter.arrr))
+ } else {
+ dispatch(setPreferredCoin(CoinFilter.qort))
+ }
+ }}
+ />
+ {user?.name && user?.name !== storeOwner ? (
+
+
+ {
+ dispatch(setIsOpen(true));
+ }}
+ />
+ {cartAddAmount > 0 && (
+
+ {cartAddAmount}
+
+ )}
+
+
+ ) : null}
+
+ ) : (
+ Product ID ${productID} Not Found
+ );
+};
diff --git a/src/pages/ProductManager/NewProduct/NewProduct-styles.tsx b/src/pages/ProductManager/NewProduct/NewProduct-styles.tsx
new file mode 100644
index 0000000..51f21b3
--- /dev/null
+++ b/src/pages/ProductManager/NewProduct/NewProduct-styles.tsx
@@ -0,0 +1,108 @@
+import { styled } from "@mui/system";
+import { Button, Box, InputLabel, Select, MenuItem } from "@mui/material";
+import { TimesSVG } from "../../../assets/svgs/TimesSVG";
+
+export const CreateProductButton = styled(Button)(({ theme }) => ({
+ backgroundColor: theme.palette.secondary.main,
+ textTransform: "none",
+ fontFamily: "Merriweather Sans",
+ gap: "5px",
+ fontSize: "15px",
+ borderRadius: "5px",
+ border: "none",
+ color: "white",
+ padding: "5px 15px",
+ transition: "all 0.3s ease-in-out",
+ boxShadow:
+ "rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.secondary.dark,
+ boxShadow:
+ "rgba(50, 50, 93, 0.35) 0px 3px 5px -1px, rgba(0, 0, 0, 0.4) 0px 2px 3px -1px;"
+ }
+}));
+
+export const CloseIcon = styled(TimesSVG)(({ theme }) => ({
+ position: "absolute",
+ top: "0",
+ right: "8px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.1)"
+ }
+}));
+
+export const ProductImagesRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px"
+}));
+
+export const InputFieldCustomLabel = styled(InputLabel)(({ theme }) => ({
+ color: theme.palette.text.primary,
+ fontSize: "18px",
+ transformOrigin: "top center",
+ fontFamily: "Karla",
+ letterSpacing: 0,
+ "&.Mui-focused": {
+ color: theme.palette.secondary.main
+ }
+}));
+
+export const CustomSelect = styled(Select)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "17.5px",
+ letterSpacing: 0,
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.secondary.main
+ }
+}));
+
+export const CategoryRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "25px",
+ flexGrow: 1
+}));
+
+export const CustomMenuItem = styled(MenuItem)(({ theme }) => ({
+ fontFamily: "Karla",
+ letterSpacing: 0,
+ fontSize: "18px"
+}));
+
+export const AddButton = styled(Button)(({ theme }) => ({
+ backgroundColor: theme.palette.secondary.main,
+ color: "#fff",
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ padding: "5px 10px",
+ borderRadius: "8px",
+ border: "none",
+ transition: "all 0.3s ease-in-out",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)"
+ : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)"
+ : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
+ backgroundColor: theme.palette.secondary.dark
+ }
+}));
+
+export const MaximumImagesRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ padding: "0",
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ color: theme.palette.text.primary,
+ userSelect: "none"
+}));
diff --git a/src/pages/ProductManager/NewProduct/NewProduct.tsx b/src/pages/ProductManager/NewProduct/NewProduct.tsx
new file mode 100644
index 0000000..8db59a2
--- /dev/null
+++ b/src/pages/ProductManager/NewProduct/NewProduct.tsx
@@ -0,0 +1,217 @@
+import { useEffect } from "react";
+import { Box, Modal, useTheme } from "@mui/material";
+import ShortUniqueId from "short-unique-id";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import { ProductForm } from "../ProductForm/ProductForm";
+import { setProductsToSave } from "../../../state/features/globalSlice";
+import { Product } from "../../../state/features/storeSlice";
+import { CreateProductButton } from "./NewProduct-styles";
+import { AddSVG } from "../../../assets/svgs/AddSVG";
+import { ModalBody } from "../../../components/modals/CreateStoreModal-styles";
+import { PRODUCT_BASE, STORE_BASE } from "../../../constants/identifiers";
+
+const uid = new ShortUniqueId({ length: 10 });
+interface ProductPrice {
+ currency: string;
+ value: number;
+}
+export interface PublishProductParams {
+ title?: string;
+ description?: string;
+ type: string;
+ images: string[];
+ price: ProductPrice[];
+ mainImageIndex: number;
+ category: string;
+ status?: string;
+}
+interface NewMessageProps {
+ editProduct?: Product | null;
+ onClose: () => void;
+ openAddProduct: boolean;
+ setOpenAddProduct: (value: boolean) => void;
+}
+
+export const NewProduct = ({
+ editProduct,
+ onClose,
+ openAddProduct,
+ setOpenAddProduct,
+}: NewMessageProps) => {
+ const { user } = useSelector((state: RootState) => state.auth);
+
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const dataContainer = useSelector(
+ (state: RootState) => state.global.dataContainer
+ );
+ const theme = useTheme();
+
+ const dispatch = useDispatch();
+
+ const openModal = () => {
+ setOpenAddProduct(true);
+ };
+
+ const closeModal = () => {
+ setOpenAddProduct(false);
+ onClose();
+ };
+
+ useEffect(() => {
+ if (editProduct) {
+ setOpenAddProduct(true);
+ }
+ }, [editProduct]);
+
+ // Adding products to save (before publishing to QDN)
+ async function addProduct({
+ title,
+ description,
+ type,
+ images,
+ price,
+ mainImageIndex,
+ category,
+ status,
+ }: PublishProductParams) {
+ let address: string = "";
+ let name: string = "";
+ let errorMsg = "";
+
+ address = user?.address || "";
+ name = user?.name || "";
+
+ if (!address) {
+ errorMsg = "Cannot send: your address isn't available";
+ }
+ if (!name) {
+ errorMsg = "Cannot add or edit a product without a access to your name";
+ }
+ if (images.length === 0) {
+ errorMsg = "Missing images";
+ }
+ if (!currentStore) {
+ errorMsg = "Cannot create a product without having a store";
+ }
+ if (!dataContainer) {
+ errorMsg = "Cannot create a product without having a data-container";
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: "error",
+ })
+ );
+ return;
+ }
+
+ try {
+ if (!currentStore?.id) throw new Error("Cannot find store id");
+ if (!dataContainer?.products)
+ throw new Error("Cannot find data-container products");
+ const storeId: string = currentStore?.id;
+
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+ const productId = uid();
+ if (!currentStore) return;
+ // Edit Product
+ if (editProduct) {
+ const productObject: any = {
+ ...editProduct,
+ title,
+ description,
+ images,
+ mainImageIndex,
+ type,
+ price,
+ category,
+ isUpdate: true,
+ status,
+ user: name,
+ };
+
+ dispatch(setProductsToSave(productObject));
+ } else {
+ const id = `${PRODUCT_BASE}-${shortStoreId}-${productId}`;
+ const productObject: any = {
+ title,
+ description,
+ created: Date.now(),
+ version: 1,
+ images,
+ mainImageIndex,
+ type,
+ price,
+ storeId,
+ shortStoreId,
+ category,
+ id,
+ status: "AVAILABLE",
+ user: name,
+ };
+ // Add Product to productsToSave object in Global Slice Redux Store
+ dispatch(setProductsToSave(productObject));
+ }
+
+ closeModal();
+ } catch (error: any) {
+ let notificationObj = null;
+ if (typeof error === "string") {
+ notificationObj = {
+ msg: error || "Failed to send message",
+ alertType: "error",
+ };
+ } else if (typeof error?.error === "string") {
+ notificationObj = {
+ msg: error?.error || "Failed to send message",
+ alertType: "error",
+ };
+ } else {
+ notificationObj = {
+ msg: error?.message || "Failed to send message",
+ alertType: "error",
+ };
+ }
+ if (!notificationObj) return;
+ dispatch(setNotification(notificationObj));
+
+ throw new Error("Failed to send message");
+ }
+ }
+
+ return (
+
+
+
+ Add Product
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/pages/ProductManager/OrderTable/OrderTable-styles.tsx b/src/pages/ProductManager/OrderTable/OrderTable-styles.tsx
new file mode 100644
index 0000000..f5d3633
--- /dev/null
+++ b/src/pages/ProductManager/OrderTable/OrderTable-styles.tsx
@@ -0,0 +1,10 @@
+import { styled } from "@mui/system";
+import { TableRow } from "@mui/material";
+
+export const StyledTableRow = styled(TableRow)(({ theme }) => ({
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.background.paper
+ }
+}));
diff --git a/src/pages/ProductManager/OrderTable/OrderTable.tsx b/src/pages/ProductManager/OrderTable/OrderTable.tsx
new file mode 100644
index 0000000..6118141
--- /dev/null
+++ b/src/pages/ProductManager/OrderTable/OrderTable.tsx
@@ -0,0 +1,178 @@
+import * as React from "react";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Paper from "@mui/material/Paper";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import moment from "moment";
+import { Order } from "../../../state/features/orderSlice";
+import { CircularProgress } from "@mui/material";
+import { StyledTableRow } from "./OrderTable-styles";
+import { setNotification } from "../../../state/features/notificationsSlice";
+
+const tableCellFontSize = "16px";
+
+interface ColumnData {
+ dataKey: keyof Order;
+ label: string;
+ numeric?: boolean;
+ width?: number;
+ status?: string;
+}
+interface SimpleTableProps {
+ openOrder: (product: Order) => void;
+ data: Order[];
+ children?: React.ReactNode;
+ from: string;
+}
+
+export const OrderTable = ({
+ openOrder,
+ data,
+ from,
+ children
+}: SimpleTableProps) => {
+ const dispatch = useDispatch();
+
+ const hashMapOrders = useSelector(
+ (state: RootState) => state.order.hashMapOrders
+ );
+
+ const { isLoadingGlobal } = useSelector((state: RootState) => state.global);
+
+ const columns: ColumnData[] = [
+ {
+ label: from === "ProductManager" ? "Customer Name" : "Shop Name",
+ dataKey: from === "ProductManager" ? "user" : "storeName"
+ },
+ {
+ label: "Status",
+ dataKey: "status",
+ width: 120
+ },
+ {
+ label: "Total",
+ dataKey: "totalPrice",
+ width: 120
+ },
+ {
+ label: "Created",
+ dataKey: "created",
+ numeric: true,
+ width: 300
+ }
+ ];
+
+ const processedOrders = data.map((row, index) => {
+ let rowData = row;
+ if (hashMapOrders[row.id]) {
+ rowData = {
+ ...row,
+ ...hashMapOrders[row.id]
+ };
+ }
+ return { index, rowData };
+ });
+
+ function fixedHeaderContent() {
+ return (
+
+ {columns.map((column) => {
+ return (
+
+ {column.label}
+
+ );
+ })}
+
+ );
+ }
+
+ function rowContent(_index: number, rowData: Order, openOrder: any) {
+ return (
+
+ {columns.map((column) => {
+ return (
+ {
+ if (!hashMapOrders[rowData.id]) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Order Data Not Loaded Yet! Please Try Again or Refresh the Page."
+ })
+ );
+ return;
+ }
+ openOrder(rowData);
+ }}
+ key={column.dataKey}
+ align={column.numeric || false ? "right" : "left"}
+ style={{ width: column.width, padding: "15px 20px" }}
+ sx={{
+ fontSize: tableCellFontSize,
+ padding: "7px"
+ }}
+ >
+ <>
+ {column.dataKey === "created"
+ ? moment(rowData[column.dataKey]).format("llll")
+ : column.dataKey === "totalPrice"
+ ? rowData[column.dataKey]
+ : rowData[column.dataKey]}
+ >
+
+ );
+ })}
+
+ );
+ }
+
+ if (isLoadingGlobal) return ;
+
+ return (
+
+
+
+ {fixedHeaderContent()}
+
+ {processedOrders.length > 0 ? (
+ processedOrders.map(({ index, rowData }) => (
+
+ {rowContent(index, rowData, openOrder)}
+
+ ))
+ ) : (
+
+
+ You currently have no orders!
+
+
+ )}
+
+
+
+ {children}
+
+ );
+};
diff --git a/src/pages/ProductManager/ProductForm/ProductForm.tsx b/src/pages/ProductManager/ProductForm/ProductForm.tsx
new file mode 100644
index 0000000..a4bac79
--- /dev/null
+++ b/src/pages/ProductManager/ProductForm/ProductForm.tsx
@@ -0,0 +1,381 @@
+import React, { useEffect, useState } from "react";
+import { Box, FormControl, SelectChangeEvent, useTheme } from "@mui/material";
+import ImageUploader from "../../../components/common/ImageUploader";
+import { PublishProductParams } from "../NewProduct/NewProduct";
+import { Price, Product } from "../../../state/features/storeSlice";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import {
+ AddLogoButton,
+ AddLogoIcon,
+ ButtonRow,
+ CancelButton,
+ CreateButton,
+ CustomInputField,
+ CustomNumberField,
+ LogoPreviewRow,
+ StoreLogoPreview,
+} from "../../../components/modals/CreateStoreModal-styles";
+import {
+ AddButton,
+ CategoryRow,
+ CloseIcon,
+ CustomMenuItem,
+ CustomSelect,
+ InputFieldCustomLabel,
+ MaximumImagesRow,
+ ProductImagesRow,
+} from "../NewProduct/NewProduct-styles";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import { addProductsToSaveCategory } from "../../../state/features/globalSlice";
+import { Variant } from "../../../components/common/NumericTextFieldQshop";
+import { CoinFilter } from "../../Store/Store/Store";
+
+interface ProductFormProps {
+ onSubmit: (product: PublishProductParams) => void;
+ onClose?: () => void;
+ editProduct?: Product | null;
+}
+interface ProductObj {
+ title?: string;
+ description?: string;
+ price: number;
+ images: string[];
+ category?: string;
+ priceARRR?: number;
+ priceQORT?: number;
+}
+
+export const ProductForm: React.FC = ({
+ onClose,
+ onSubmit,
+ editProduct,
+}) => {
+ const theme = useTheme();
+ const dispatch = useDispatch();
+ const categories = useSelector(
+ (state: RootState) => state.global.listProducts.categories
+ );
+ const productsToSaveCategories = useSelector(
+ (state: RootState) => state.global.productsToSaveCategories
+ );
+
+ const [product, setProduct] = useState({
+ title: "",
+ description: "",
+ price: 0,
+ images: [],
+ });
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const [categoryList, setCategoryList] = useState([]);
+ const [selectedType, setSelectedType] = useState("digital");
+ const [images, setImages] = useState([]);
+ const [selectedStatus, setSelectedStatus] = useState("AVAILABLE");
+ const [newCategory, setNewCategory] = useState("");
+ const [selectedCategory, setSelectedCategory] = useState("");
+
+ const editProductQortPrice =
+ editProduct?.price?.find((item: Price) => item?.currency === "qort")
+ ?.value || product.price;
+ const editProductARRRPrice =
+ editProduct?.price?.find((item: Price) => item?.currency === CoinFilter.arrr)
+ ?.value || "";
+
+ const handleInputChange = (event: React.ChangeEvent) => {
+ setProduct({
+ ...product,
+ [event.target.name]: event.target.value as string | number,
+ });
+ };
+
+ const handleProductPriceChange = (value: string) => {
+ const price = Number(value);
+ setProduct({ ...product, price: price });
+ };
+ const handleProductPriceChangeForeign = (value: string, coin:string) => {
+ const price = Number(value);
+ setProduct({ ...product, [`price${coin}`]: price });
+ };
+
+ const handleSelectChange = (event: SelectChangeEvent) => {
+ const optionId = event.target.value;
+ const selectedOption = categoryList.find(option => option === optionId);
+ setSelectedCategory(selectedOption || null);
+ };
+
+ const handleNewCategory = (event: React.ChangeEvent) => {
+ setNewCategory(event.target.value);
+ };
+
+ const handleSubmit = () => {
+ if (!selectedType || !selectedCategory) {
+ dispatch(
+ setNotification({
+ msg: "Please select type and category",
+ alertType: "error",
+ })
+ );
+ return;
+ }
+
+ if (isNaN(product.price)) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Price must be a number!",
+ })
+ );
+ return;
+ }
+
+ let price = []
+ price.push({
+ currency: "qort",
+ value: product.price,
+ })
+
+ for (const value of Object.values(CoinFilter)) {
+ if(product[`price${value}`]){
+ price.push(
+ {
+ currency: value,
+ value: +(product[`price${value}`] || 0),
+ },
+ )
+ }
+ }
+
+ onSubmit({
+ title: product.title,
+ description: product.description,
+ type: selectedType,
+ images,
+ category: selectedCategory,
+ status: selectedStatus,
+ price: price,
+ mainImageIndex: 0,
+ });
+ };
+
+ const addNewCategoryToList = () => {
+ if (!newCategory) return;
+ setSelectedCategory(newCategory);
+ setCategoryList(prev => [...prev, newCategory]);
+ setNewCategory("");
+ dispatch(addProductsToSaveCategory(newCategory));
+ };
+
+ useEffect(() => {
+ if (categories || productsToSaveCategories) {
+ const existingCategories = [...categories, ...productsToSaveCategories];
+ setCategoryList(existingCategories);
+ }
+ }, [categories, productsToSaveCategories]);
+
+ useEffect(() => {
+ if (editProduct) {
+ try {
+ const { title, description, images, type, category, status } =
+ editProduct;
+
+ setProduct({
+ title,
+ description,
+ images: [],
+ price: editProductQortPrice,
+ });
+ if (images) {
+ setImages(images);
+ }
+ if (type) {
+ setSelectedType(type);
+ }
+ if (category) {
+ setSelectedCategory(category);
+ }
+ if (status) {
+ setSelectedStatus(status);
+ }
+ } catch (error) {
+ console.log({ error });
+ }
+ }
+ }, [editProduct]);
+
+ return (
+ <>
+ {images.length > 0 && (
+
+ {images.map((img, index) => (
+
+
+
+ {
+ setImages(prev => prev.filter(item => item !== img));
+ }}
+ height={"22"}
+ width={"22"}
+ >
+
+
+ ))}
+
+ )}
+ {(images.length === 0 || images.length < 3) && (
+ setImages(prev => [...prev, img])}
+ >
+
+ Add Product Image
+
+
+
+ )}
+ {images.length > 0 && (
+ *Maximum 3 images
+ )}
+
+
+
+ {currentStore?.supportedCoins?.includes(CoinFilter.arrr) && (
+ handleProductPriceChangeForeign(val, CoinFilter.arrr)}
+ required={false}
+ />
+ )}
+
+
+
+
+ Product Type
+
+ {
+ setSelectedType(event.target.value as string);
+ }}
+ required
+ fullWidth
+ >
+ Digital
+ Physical
+
+
+
+
+
+
+ Category
+
+ {
+ handleSelectChange(event as SelectChangeEvent);
+ }}
+ required
+ fullWidth
+ >
+
+ Add a Category
+
+ {categoryList.map(category => (
+ {category}
+ ))}
+
+
+
+
+
+ Add
+
+
+
+ {editProduct && (
+
+
+ Product Status
+
+ setSelectedStatus(event.target.value as string)}
+ required
+ fullWidth
+ >
+ Available
+ Retired
+ Out of stock
+
+
+ )}
+
+
+ Cancel
+
+
+ {editProduct ? "Edit Product" : "Add Product"}
+
+
+ >
+ );
+};
diff --git a/src/pages/ProductManager/ProductManager-styles.tsx b/src/pages/ProductManager/ProductManager-styles.tsx
new file mode 100644
index 0000000..255d224
--- /dev/null
+++ b/src/pages/ProductManager/ProductManager-styles.tsx
@@ -0,0 +1,208 @@
+import { styled } from "@mui/system";
+import { Box, Typography, Button, Grid } from "@mui/material";
+import Tabs from "@mui/material/Tabs";
+import Tab from "@mui/material/Tab";
+import { TimesSVG } from "../../assets/svgs/TimesSVG";
+import { MinimizeSVG } from "../../assets/svgs/MinimizeSVG";
+
+export const ProductManagerContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ width: "100%",
+ flexDirection: "column",
+ backgroundColor: "background.paper"
+}));
+
+export const TabsContainer = styled(Box)(({ theme }) => ({
+ borderBottom: 1,
+ borderColor: "divider",
+ display: "flex",
+ width: "100%",
+ alignItems: "flex-start",
+ justifyContent: "center",
+ flexDirection: "column",
+ padding: "15px",
+ gap: "4px"
+}));
+
+export const StyledTabs = styled(Tabs)(({ theme }) => ({
+ "& .MuiTabs-indicator": {
+ backgroundColor: theme.palette.secondary.main
+ }
+}));
+
+export const StyledTab = styled(Tab)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ "& .MuiTabs-indicator": {
+ backgroundColor: theme.palette.secondary.main
+ }
+}));
+
+export const ProductsToSaveCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "flex-start",
+ justifyContent: "space-between",
+ flexDirection: "column",
+ width: "85vw",
+ minHeight: "500px",
+ maxHeight: "90vh",
+ borderRadius: "8px",
+ backgroundColor: theme.palette.mode === "light" ? "#e8e8e8" : "#32333c",
+ position: "absolute",
+ left: "50%",
+ transform: "translateX(-50%)",
+ padding: "25px",
+ boxShadow:
+ theme.palette.mode === "light"
+ ? "rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;"
+ : "0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);",
+ zIndex: 2
+}));
+
+export const ProductToSaveCard = styled(Box)(({ theme }) => ({
+ position: "relative",
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "space-evenly",
+ maxWidth: "300px",
+ width: "auto",
+ height: "auto",
+ maxHeight: "200px",
+ minHeight: "100px",
+ padding: "10px",
+ borderRadius: "8px",
+ backgroundColor: "#f2f2f2",
+ flexGrow: 1
+}));
+
+export const ProductsCol = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexWrap: "wrap",
+ gap: "10px",
+ width: "100%",
+ overflowY: "auto",
+ flexDirection: "row",
+ padding: "0 10px 10px 10px",
+
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: theme.palette.mode === "light" ? "#e8e8e8" : "#32333c"
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#e8e8e8" : "#32333c"
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646"
+ }
+}));
+
+export const ProductToSaveImageRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-evenly",
+ gap: "4px"
+}));
+
+export const CardHeader = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans",
+ fontSize: "18px",
+ userSelect: "none",
+ color: "#000000",
+ letterSpacing: 0
+}));
+
+export const Bulletpoints = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "17px",
+ userSelect: "none",
+ color: "#000000",
+ letterSpacing: 0,
+ alignItems: "center",
+ gap: "5px",
+ display: "flex",
+ justifyContent: "flex-start",
+ width: "100%"
+}));
+
+export const TimesIcon = styled(TimesSVG)(({ theme }) => ({
+ position: "absolute",
+ top: "2px",
+ right: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.1)"
+ }
+}));
+
+export const AddMoreButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ backgroundColor: "#cfd432",
+ color: "black",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#afb428"
+ }
+}));
+
+export const CardButtonRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ width: "100%",
+ alignItems: "center",
+ justifyContent: "flex-end",
+ gap: "15px"
+}));
+
+export const MinimizeIcon = styled(MinimizeSVG)(({ theme }) => ({
+ position: "absolute",
+ top: "5px",
+ right: "5px",
+ padding: "3px",
+ backgroundColor: "transparent",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#ffffff73"
+ }
+}));
+
+export const DockedMinimizeIcon = styled(MinimizeSVG)(({ theme }) => ({
+ padding: "3px",
+ backgroundColor: "transparent",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#ffffff73"
+ }
+}));
+
+export const DockedProductsToSaveCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "8px 25px",
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ color: theme.palette.text.primary,
+ borderTopRightRadius: "8px",
+ borderTopLeftRadius: "8px",
+ borderBottomRightRadius: "0px",
+ borderBottomLeftRadius: "0px",
+ backgroundColor: theme.palette.mode === "light" ? "#e8e8e8" : "#32333c",
+ position: "fixed",
+ zIndex: 55,
+ bottom: 0,
+ right: "20px",
+ gap: "15px"
+}));
diff --git a/src/pages/ProductManager/ProductManager.tsx b/src/pages/ProductManager/ProductManager.tsx
new file mode 100644
index 0000000..63a83f2
--- /dev/null
+++ b/src/pages/ProductManager/ProductManager.tsx
@@ -0,0 +1,686 @@
+import React, { useMemo, useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../state/store";
+import { Box, Grid, useTheme } from "@mui/material";
+import LazyLoad from "../../components/common/LazyLoad";
+import { NewProduct } from "./NewProduct/NewProduct";
+import { ShowOrder } from "./ShowOrder/ShowOrder";
+import { SimpleTable } from "./ProductTable/ProductTable";
+import { setNotification } from "../../state/features/notificationsSlice";
+import { objectToBase64 } from "../../utils/toBase64";
+import ShortUniqueId from "short-unique-id";
+import {
+ Catalogue,
+ CatalogueDataContainer,
+ DataContainer,
+ clearAllProductsToSave,
+ removeFromProductsToSave,
+ setDataContainer,
+ setProducts,
+ updateCatalogueHashMap,
+} from "../../state/features/globalSlice";
+import { Price, Product } from "../../state/features/storeSlice";
+import { useFetchOrders } from "../../hooks/useFetchOrders";
+import { AVAILABLE } from "../../constants/product-status";
+import {
+ TabsContainer,
+ StyledTabs,
+ StyledTab,
+ ProductsToSaveCard,
+ ProductToSaveCard,
+ CardHeader,
+ Bulletpoints,
+ TimesIcon,
+ CardButtonRow,
+ AddMoreButton,
+ MinimizeIcon,
+ DockedMinimizeIcon,
+ DockedProductsToSaveCard,
+ ProductManagerContainer,
+ ProductsCol,
+} from "./ProductManager-styles";
+import { OrderTable } from "./OrderTable/OrderTable";
+import { BackToStorefrontButton } from "../Store/Store/Store-styles";
+import { QortalSVG } from "../../assets/svgs/QortalSVG";
+import { CategorySVG } from "../../assets/svgs/CategorySVG";
+import { LoyaltySVG } from "../../assets/svgs/LoyaltySVG";
+import useConfirmationModal from "../../hooks/useConfirmModal";
+import { CreateButton } from "../../components/modals/CreateStoreModal-styles";
+// import { useProductsToSaveLocalStorage } from "../../hooks/useProductsToSaveLocalStorage";
+import { ReusableModal } from "../../components/modals/ReusableModal";
+import { useParams } from "react-router-dom";
+import { useLocation } from "react-router-dom";
+import {
+ CATALOGUE_BASE,
+ DATA_CONTAINER_BASE,
+ STORE_BASE,
+} from "../../constants/identifiers";
+import { resetOrders } from "../../state/features/orderSlice";
+
+const uid = new ShortUniqueId({ length: 10 });
+
+export const ProductManager = () => {
+ const theme = useTheme();
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ // Get store id from url
+ const { store } = useParams();
+
+ const user = useSelector((state: RootState) => state.auth.user);
+ const productsToSave = useSelector(
+ (state: RootState) => state.global.productsToSave
+ );
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const dataContainer = useSelector(
+ (state: RootState) => state.global.dataContainer
+ );
+ const orders = useSelector((state: RootState) => state.order.orders);
+ const listProducts = useSelector(
+ (state: RootState) => state.global.listProducts
+ );
+ // Products to map
+ const products = useSelector((state: RootState) => state.global.products);
+
+ const { products: dataContainerProducts } = listProducts;
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [order, setOrder] = useState(null);
+ const [valueTab, setValueTab] = React.useState(0);
+ const [productToEdit, setProductToEdit] = useState(null);
+ const [openAddProduct, setOpenAddProduct] = useState(false);
+ const [dockProductsToSave, setDockProductsToSave] = useState(false);
+
+ // Get authenticated user's name
+ const userName = useMemo(() => {
+ if (!user?.name) return "";
+ return user.name;
+ }, [user]);
+
+ // get and set productsToSave from local storage
+ // const { productsToSaveLS, saveProductsToLocalStorage } =
+ // useProductsToSaveLocalStorage();
+
+ // Redirect to Store page if they're hot reloading
+
+ // Get productsToSave from Redux store and set them in local storage
+ // useEffect(() => {
+ // if (Object.keys(productsToSave).length > 0) {
+ // saveProductsToLocalStorage(productsToSave);
+ // }
+ // }, [productsToSave]);
+
+ // Publish productsToSave to QDN
+ async function publishQDNResource() {
+ let address: string = "";
+ let name: string = "";
+ let errorMsg = "";
+
+ address = user?.address || "";
+ name = user?.name || "";
+
+ // Validation
+ if (!address) {
+ errorMsg = "Cannot send: your address isn't available";
+ }
+ if (!name) {
+ errorMsg = "Cannot send a message without a access to your name";
+ }
+
+ if (!currentStore) {
+ errorMsg = "Cannot create a product without having a store";
+ }
+
+ if (!dataContainer) {
+ errorMsg = "Cannot create a product without having a data-container";
+ }
+
+ // Add validation to make sure the dataContainer has the same store id as the current store
+ if (currentStore?.id !== dataContainer?.storeId) {
+ errorMsg = "Cannot create a product without having a data-container";
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: "error",
+ })
+ );
+ throw new Error(errorMsg);
+ }
+
+ if (!currentStore?.id) throw new Error("Cannot find store id");
+ if (!dataContainer?.products)
+ throw new Error("Cannot find data-container products");
+
+ try {
+ const storeId: string = currentStore?.id;
+ if (!storeId) throw new Error("Could not find your store");
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+
+ if (!currentStore) throw new Error("Could not find your store");
+ // Get last index catalogue inside data container catalogues array
+ const lastCatalogue: CatalogueDataContainer | undefined =
+ dataContainer?.catalogues?.at(-1);
+ let catalogue = null;
+ const listOfCataloguesToPublish: Catalogue[] = [];
+ // Initialize dataContainer to publish
+ const dataContainerToPublish: DataContainer = {
+ ...dataContainer,
+ products: structuredClone(dataContainer.products),
+ catalogues: structuredClone(dataContainer.catalogues),
+ };
+
+ if (lastCatalogue && Object.keys(lastCatalogue?.products)?.length < 10) {
+ // fetch last catalogue on QDN
+ const catalogueResponse = await qortalRequest({
+ action: "FETCH_QDN_RESOURCE",
+ name: name,
+ service: "DOCUMENT",
+ identifier: lastCatalogue.id,
+ });
+ if (catalogueResponse && !catalogueResponse?.error)
+ catalogue = catalogueResponse;
+ }
+ // If catalogue was found on QDN, add it to the list of catalogues to publish when it has less than 10 products
+ if (catalogue) listOfCataloguesToPublish.push(catalogue);
+
+ // Loop through productsToSave and add them to the catalogue if it has less than 10 products, otherwise create a new catalogue. Also add new products to global redux store.
+ Object.keys(productsToSave)
+ .filter(item => !productsToSave[item]?.isUpdate)
+ .forEach(key => {
+ const product = productsToSave[key];
+ const priceInQort = product?.price?.find(
+ (item: Price) => item?.currency === "qort"
+ )?.value;
+ if (!priceInQort)
+ throw new Error("Cannot find price for one of your products");
+ const lastCatalogueInList = listOfCataloguesToPublish.at(-1);
+ if (
+ lastCatalogueInList &&
+ Object.keys(lastCatalogueInList?.products)?.length < 10
+ ) {
+ const copyLastCatalogue = { ...lastCatalogueInList };
+ // Add catalogueId to the product here (!important)
+ copyLastCatalogue.products[key] = {
+ ...product,
+ catalogueId: copyLastCatalogue.id,
+ };
+ dataContainerToPublish.products[key] = {
+ created: product.created,
+ priceQort: priceInQort,
+ category: product?.category || "",
+ catalogueId: copyLastCatalogue.id,
+ status: AVAILABLE,
+ };
+ if (!dataContainerToPublish.catalogues)
+ dataContainerToPublish.catalogues = [];
+ // Determine if data container's catalogue has products
+ const findCatalogueInDataContainer =
+ dataContainerToPublish.catalogues.findIndex(
+ item => item.id === copyLastCatalogue.id
+ );
+ if (findCatalogueInDataContainer >= 0) {
+ dataContainerToPublish.catalogues[
+ findCatalogueInDataContainer
+ ].products[key] = true;
+ } else {
+ dataContainerToPublish.catalogues = [
+ ...dataContainerToPublish.catalogues,
+ {
+ id: copyLastCatalogue.id,
+ products: {
+ [key]: true,
+ },
+ },
+ ];
+ }
+ } else {
+ // Create new catalogue
+ const uidGenerator = uid();
+ const catalogueId = `${CATALOGUE_BASE}-${shortStoreId}-${uidGenerator}`;
+ // Add catalogueId to the product here (!important)
+ listOfCataloguesToPublish.push({
+ id: catalogueId,
+ products: {
+ [key]: { ...product, catalogueId: catalogueId },
+ },
+ });
+ try {
+ dataContainerToPublish.products[key] = {
+ created: product.created,
+ priceQort: priceInQort,
+ category: product?.category || "",
+ catalogueId,
+ status: AVAILABLE,
+ };
+ } catch (error) {
+ console.error(error);
+ }
+
+ if (!dataContainerToPublish.catalogues)
+ dataContainerToPublish.catalogues = [];
+
+ const findCatalogueInDataContainer =
+ dataContainerToPublish.catalogues.findIndex(
+ item => item.id === catalogueId
+ );
+ // Determine if data container's catalogue has products
+ if (findCatalogueInDataContainer >= 0) {
+ dataContainerToPublish.catalogues[
+ findCatalogueInDataContainer
+ ].products[key] = true;
+ } else {
+ dataContainerToPublish.catalogues = [
+ ...dataContainerToPublish.catalogues,
+ {
+ id: catalogueId,
+ products: {
+ [key]: true,
+ },
+ },
+ ];
+ }
+ }
+ });
+ // Update products when sending productsToSave inside existing data container
+ const productsToUpdate = Object.keys(productsToSave)
+ .filter(item => !!productsToSave[item]?.isUpdate)
+ .map(key => productsToSave[key]);
+ for (const product of productsToUpdate) {
+ const priceInQort = product?.price?.find(
+ (item: Price) => item?.currency === "qort"
+ )?.value;
+ if (!priceInQort)
+ throw new Error("Cannot find price for one of your products");
+ if (priceInQort <= 0)
+ throw new Error("Price cannot be less than or equal to 0");
+ dataContainerToPublish.products[product.id] = {
+ created: product.created,
+ priceQort: priceInQort,
+ category: product?.category || "",
+ catalogueId: product.catalogueId,
+ status: product?.status || "",
+ };
+ // Replace product from listOfCataloguesToPublish with updated product
+ const findCatalogueFromExistingList =
+ listOfCataloguesToPublish.findIndex(
+ cat => cat.id === product.catalogueId
+ );
+ if (findCatalogueFromExistingList >= 0) {
+ listOfCataloguesToPublish[findCatalogueFromExistingList].products[
+ product.id
+ ] = product;
+ } else {
+ // Otherwise fetch catalogue from QDN and add product to it
+ const catalogueResponse = await qortalRequest({
+ action: "FETCH_QDN_RESOURCE",
+ name: name,
+ service: "DOCUMENT",
+ identifier: product.catalogueId,
+ });
+ if (catalogueResponse && !catalogueResponse?.error) {
+ const copiedCatalogue = structuredClone(catalogueResponse);
+ copiedCatalogue.products[product.id] = product;
+ listOfCataloguesToPublish.push(copiedCatalogue);
+ }
+ }
+ }
+
+ if (!currentStore) return;
+ let publishMultipleCatalogues = [];
+ // Loop through listOfCataloguesToPublish and publish the base64 converted object to QDN
+ for (const catalogue of listOfCataloguesToPublish) {
+ const catalogueToBase64 = await objectToBase64(catalogue);
+ const publish = {
+ name,
+ service: "DOCUMENT",
+ identifier: catalogue.id,
+ filename: "catalogue.json",
+ data64: catalogueToBase64,
+ };
+ publishMultipleCatalogues.push(publish);
+ }
+ // Convert dataContainer being published to base64
+ const dataContainerToBase64 = await objectToBase64(
+ dataContainerToPublish
+ );
+ const publishDataContainer = {
+ name,
+ service: "DOCUMENT",
+ identifier: dataContainerToPublish.id,
+ filename: "datacontainer.json",
+ data64: dataContainerToBase64,
+ };
+ // Publish the catalogues and the data container to QDN. Remember that there can be multiple catalogues because each catalogue holds a maximum of 10 products. Therefore, if you're publishing multiple products, you will possibly fill up the last catalogue, before then creating a new one.
+ const multiplePublish = {
+ action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
+ resources: [...publishMultipleCatalogues, publishDataContainer],
+ };
+ await qortalRequest(multiplePublish);
+
+ // Clear productsToSave from Redux store
+ dispatch(clearAllProductsToSave());
+
+ // Replace dataContainer in the store
+ dispatch(
+ setDataContainer({
+ ...dataContainerToPublish,
+ id: `${storeId}-${DATA_CONTAINER_BASE}`,
+ })
+ );
+
+ const newProductsArray = Object.keys(dataContainerToPublish.products).map(
+ key => {
+ const product = dataContainerToPublish.products[key];
+ return {
+ ...product,
+ id: key,
+ };
+ }
+ );
+
+ // Reset products to first 20 in the array of listProducts
+ dispatch(setProducts(newProductsArray));
+
+ const newCatalogueHashMapFunc = () => {
+ let newCatalogueHashMap: Record = {};
+
+ listOfCataloguesToPublish.forEach(catalogue => {
+ newCatalogueHashMap[catalogue.id] = catalogue;
+ });
+ return newCatalogueHashMap;
+ };
+
+ const catalogueHashMapToDispatch = newCatalogueHashMapFunc();
+ dispatch(updateCatalogueHashMap(catalogueHashMapToDispatch));
+
+ // Toast
+ dispatch(
+ setNotification({
+ msg: "Products saved",
+ alertType: "success",
+ })
+ );
+
+ // Error handling
+ } catch (error: any) {
+ let notificationObj = null;
+ if (typeof error === "string") {
+ notificationObj = {
+ msg: error || "Failed to send message",
+ alertType: "error",
+ };
+ } else if (typeof error?.error === "string") {
+ notificationObj = {
+ msg: error?.error || "Failed to send message",
+ alertType: "error",
+ };
+ } else {
+ notificationObj = {
+ msg: error?.message || "Failed to send message",
+ alertType: "error",
+ };
+ }
+ if (!notificationObj) return;
+ dispatch(setNotification(notificationObj));
+
+ throw new Error("Failed to send message");
+ }
+ }
+
+ const handleChange = (event: React.SyntheticEvent, newValue: number) => {
+ setValueTab(newValue);
+ };
+
+ // Confirmation to delete product from productsToSave
+ const { Modal, showModal } = useConfirmationModal({
+ title: "Remove Product from List To Save to the Shop",
+ message: "Are you sure you want to proceed?",
+ });
+
+ const handleRemoveConfirmation = async (key: string) => {
+ const userConfirmed = await showModal();
+ if (userConfirmed) {
+ // User confirmed action
+ dispatch(removeFromProductsToSave(key));
+ }
+ };
+
+ // Get products & orders from Redux
+ const { getOrders, getProducts } = useFetchOrders();
+
+ const handleGetOrders = React.useCallback(async () => {
+ await getOrders();
+ }, [getOrders]);
+
+ const handleGetProducts = React.useCallback(async () => {
+ await getProducts();
+ }, [getProducts]);
+
+ // Fetch products from hashMap if listProducts changes
+ useEffect(() => {
+ handleGetProducts();
+ }, [dataContainerProducts]);
+
+ useEffect(() => {
+ if (
+ (!dataContainer || Object.keys(dataContainer).length === 0) &&
+ userName
+ ) {
+ navigate(`/${userName}/${store}`);
+ } else {
+ return;
+ }
+ }, [userName, dataContainer]);
+
+ // Cleanup orders when they leave ProductManager
+ useEffect(() => {
+ return () => {
+ dispatch(resetOrders());
+ }
+ }, [])
+
+ return (
+
+
+ {
+ navigate(`/${userName}/${currentStore?.id}`);
+ }}
+ >
+ Back To Storefront
+
+
+
+
+
+
+
+ {/* productsToSave card inside Product Manager */}
+ {dockProductsToSave ? (
+
+ Products Ready To Be Listed
+ {
+ setDockProductsToSave(false);
+ }}
+ />
+
+ ) : (
+ 0}
+ >
+
+
+ {Object.keys(productsToSave).map((key: string) => {
+ const product = productsToSave[key];
+ const { id } = product;
+ return (
+
+
+ {product?.title}
+
+ {" "}
+ Price: {product?.price && product?.price[0].value} QORT
+
+
+
+ Type: {product?.type}
+
+
+
+ Category: {product?.category}
+
+ handleRemoveConfirmation(id)}
+ color={"#000000"}
+ height={"22"}
+ width={"22"}
+ />
+
+
+ );
+ })}
+
+
+ setOpenAddProduct(true)}>
+ Add Another Product
+
+
+ Save Products
+
+
+ {
+ setDockProductsToSave(true);
+ }}
+ />
+
+
+ )}
+
+
+ {
+ setProductToEdit(null);
+ }}
+ openAddProduct={openAddProduct}
+ setOpenAddProduct={(val: boolean) => setOpenAddProduct(val)}
+ />
+
+ {
+ setProductToEdit(product);
+ }}
+ data={products}
+ >
+
+
+
+
+ {
+ setOrder(order);
+ setIsOpen(true);
+ }}
+ data={orders}
+ from="ProductManager"
+ >
+
+
+
+ {/* Confirm Remove Product from productsToSave in global state */}
+
+
+ );
+};
+
+interface TabPanelProps {
+ children?: React.ReactNode;
+ index: number;
+ value: number;
+}
+
+export function TabPanel(props: TabPanelProps) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && children}
+
+ );
+}
diff --git a/src/pages/ProductManager/ProductTable/ProductTable-styles.tsx b/src/pages/ProductManager/ProductTable/ProductTable-styles.tsx
new file mode 100644
index 0000000..387f102
--- /dev/null
+++ b/src/pages/ProductManager/ProductTable/ProductTable-styles.tsx
@@ -0,0 +1,10 @@
+import { styled } from "@mui/system";
+import { TableRow } from "@mui/material";
+
+export const StyledTableRow = styled(TableRow)(({ theme }) => ({
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.background.default
+ }
+}));
diff --git a/src/pages/ProductManager/ProductTable/ProductTable.tsx b/src/pages/ProductManager/ProductTable/ProductTable.tsx
new file mode 100644
index 0000000..7d45d6a
--- /dev/null
+++ b/src/pages/ProductManager/ProductTable/ProductTable.tsx
@@ -0,0 +1,179 @@
+import React, { useState, useEffect } from "react";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Paper from "@mui/material/Paper";
+import { Avatar, CircularProgress } from "@mui/material";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import { Product } from "../../../state/features/storeSlice";
+import moment from "moment";
+import { StyledTableRow } from "./ProductTable-styles";
+import { setNotification } from "../../../state/features/notificationsSlice";
+
+const tableCellFontSize = "16px";
+
+interface Data {
+ title: string;
+ description: string;
+ created: number;
+ user: string;
+ id: string;
+ tags: string[];
+ status: string;
+}
+
+interface ColumnData {
+ dataKey: keyof Data;
+ label: string;
+ numeric?: boolean;
+ width?: number;
+}
+
+const columns: ColumnData[] = [
+ {
+ label: "Title",
+ dataKey: "title" // Obtained from the catalogueHashMap
+ },
+ {
+ label: "Status",
+ dataKey: "status",
+ width: 120
+ },
+ {
+ label: "Created",
+ dataKey: "created",
+ numeric: true,
+ width: 300
+ }
+];
+
+interface SimpleTableProps {
+ openProduct: (product: Product) => void;
+ data: Product[];
+ children?: React.ReactNode;
+}
+
+export const SimpleTable = ({
+ openProduct,
+ data,
+ children
+}: SimpleTableProps) => {
+ const dispatch = useDispatch();
+
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+ const { isLoadingGlobal } = useSelector((state: RootState) => state.global);
+
+ // Rest of the product data for editProduct, as what comes from the ProductManager only contains id, status, created, user & catalogueId
+ const processedData = data.map((row, index) => {
+ let rowData = row;
+
+ if (
+ catalogueHashMap[row?.catalogueId] &&
+ catalogueHashMap[row.catalogueId].products[row?.id]
+ ) {
+ rowData = {
+ ...row,
+ ...catalogueHashMap[row?.catalogueId].products[row?.id],
+ catalogueId: row?.catalogueId || ""
+ };
+ }
+
+ return { index, rowData };
+ });
+
+ function rowContent(_index: number, rowData: Product, openProduct: any) {
+ return (
+ <>
+ {columns.map((column) => {
+ return (
+ {
+ if (!catalogueHashMap[rowData.catalogueId]) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Product Data Not Loaded Yet! Please Try Again or Refresh the Page."
+ })
+ );
+ return;
+ }
+ openProduct(rowData);
+ }}
+ key={column.dataKey}
+ align={column.numeric || false ? "right" : "left"}
+ style={{ width: column.width, padding: "15px 20px" }}
+ sx={{
+ fontSize: tableCellFontSize,
+ padding: "7px"
+ }}
+ >
+ <>
+ {column.dataKey === "created"
+ ? moment(rowData[column.dataKey]).format("llll")
+ : column.dataKey === "status"
+ ? rowData[column.dataKey] === "OUT_OF_STOCK"
+ ? "OUT OF STOCK"
+ : rowData[column.dataKey] || "unknown"
+ : rowData[column.dataKey]}
+ >
+
+ );
+ })}
+ >
+ );
+ }
+
+ function fixedHeaderContent() {
+ return (
+
+ {columns.map((column) => {
+ const { label } = column;
+ return (
+
+ {column.label}
+
+ );
+ })}
+
+ );
+ }
+
+ if (isLoadingGlobal) return ;
+
+ return (
+
+
+
+ {fixedHeaderContent()}
+
+ {processedData
+ .sort((a, b) => a.rowData.created - b.rowData.created)
+ .map(({ index, rowData }) => (
+
+ {rowContent(index, rowData, openProduct)}
+
+ ))}
+
+
+
+ {children}
+
+ );
+};
diff --git a/src/pages/ProductManager/ShowOrder/ShowOrder-styles.tsx b/src/pages/ProductManager/ShowOrder/ShowOrder-styles.tsx
new file mode 100644
index 0000000..8e27f1f
--- /dev/null
+++ b/src/pages/ProductManager/ShowOrder/ShowOrder-styles.tsx
@@ -0,0 +1,363 @@
+import { styled } from "@mui/system";
+import { Box, Button, Select, TextField, Typography } from "@mui/material";
+import { TimesSVG } from "../../../assets/svgs/TimesSVG";
+
+export const ShowOrderHeader = styled(Box)(({ theme }) => ({
+ display: "flex",
+ justifyContent: "space-between",
+ width: "100%",
+ alignItems: "flex-start",
+}));
+
+export const ShowOrderImages = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+}));
+
+export const ShowOrderCol = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ flexDirection: "column",
+ gap: "5px",
+}));
+
+export const ShowOrderProductImage = styled("img")(({ theme }) => ({
+ width: "60px",
+ height: "60px",
+ borderRadius: "3px",
+ objectFit: "cover",
+}));
+
+export const ShowOrderTitle = styled("a")(({ theme }) => ({
+ display: "flex",
+ gap: "5px",
+ alignItems: "center",
+ fontFamily: "Merriweather Sans, sans-serif",
+ letterSpacing: "0px",
+ fontSize: "21px",
+}));
+
+export const CustomSelect = styled(Select)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ letterSpacing: 0,
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.secondary.main,
+ },
+ "&.MuiInputBase-input": {
+ padding: "15px 12px 8px",
+ },
+}));
+
+export const UpdateStatusButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "16px",
+ letterSpacing: 0,
+ fontWeight: 300,
+ padding: "5px 10px",
+ color: "white",
+ backgroundColor: theme.palette.secondary.main,
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.secondary.dark,
+ },
+}));
+
+export const ShowOrderDateCreated = styled(Box)(({ theme }) => ({
+ fontSize: "16px",
+ fontFamily: "Raleway",
+ fontWeight: "400",
+ lineHeight: "1.4",
+ letterSpacing: "0.2px",
+ color: theme.palette.text.primary,
+ opacity: 0.8,
+}));
+
+export const ShowOrderContent = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "flex-start",
+ flexDirection: "column",
+ gap: 1,
+ flexGrow: 1,
+ overflow: "auto",
+ width: "100%",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+}));
+
+export const SellerOrderStatusRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: "10px",
+}));
+
+export const OrderStatusRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ margin: "10px 0",
+ width: "100%",
+ padding: "0 10px",
+}));
+
+export const OrderStatusCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ width: "auto",
+ padding: "5px 10px",
+ borderRadius: "5px",
+ color: "black",
+ fontFamily: "Raleway",
+ fontSize: "18px",
+ userSelect: "none",
+}));
+
+export const OrderStatusNote = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ fontFamily: "Raleway",
+ fontSize: "18px",
+ maxWidth: "300px",
+ maxHeight: "500px",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+}));
+
+export const OrderDetails = styled(Box)(({ theme }) => ({
+ margin: "20px 0",
+ gap: "15px",
+ width: "100%",
+ "& > :not(:first-child)": {
+ borderTop: `1px solid ${theme.palette.text.primary}`,
+ },
+}));
+
+export const OrderDetailsContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "15px",
+ width: "100%",
+ padding: "8px 10px 8px 0",
+}));
+
+export const OrderDetailsCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ backgroundColor: theme.palette.background.paper,
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ borderRadius: "8px",
+ width: "100%",
+}));
+
+export const OrderTitleCol = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-start",
+ gap: "3px",
+}));
+
+export const OrderTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ letterSpacing: "0px",
+ fontSize: "20px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ "& span": {
+ fontWeight: 500,
+ },
+}));
+
+export const OrderId = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ letterSpacing: "0px",
+ fontSize: "15px",
+ opacity: 0.9,
+ color: theme.palette.text.primary,
+}));
+
+export const OrderQuantityRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-end",
+ gap: "5px",
+ width: "100%",
+}));
+
+export const TotalPriceRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: "5px",
+ width: "100%",
+}));
+
+export const TotalCostContainer = styled(Box)(({ theme }) => ({
+ position: "relative",
+ display: "flex",
+ gap: "25px",
+}));
+
+export const TotalCostCol = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: "10px",
+}));
+
+export const TotalCostFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Montserrat",
+ fontSize: "18px",
+ fontWeight: 500,
+}));
+
+export const DetailsFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ fontWeight: 400,
+}));
+
+export const Divider = styled("div")(({ theme }) => ({
+ width: "30px",
+ height: "3px",
+ backgroundColor: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const PriceRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+}));
+
+export const DetailsRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ filter:
+ theme.palette.mode === "dark" ? "brightness(1.2)" : "brightness(0.9)",
+ },
+}));
+
+export const DetailsCard = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ top: "5px",
+ right: "-150px",
+ display: "flex",
+ filter: "brightness(1.3)",
+ alignItems: "flex-start",
+ flexDirection: "column",
+ padding: "15px 25px",
+ borderRadius: "10px",
+ boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.1)",
+ backgroundColor: theme.palette.background.paper,
+ fontFamily: "Karla",
+ fontSize: "19px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ gap: "5px",
+ maxWidth: "400px",
+ maxHeight: "400px",
+ overflowY: "auto",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+ "& span": {
+ fontWeight: 500,
+ },
+}));
+
+export const CloseDetailsCardIcon = styled(TimesSVG)(({ theme }) => ({
+ position: "absolute",
+ top: "5px",
+ right: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.1)",
+ },
+}));
+
+export const DeliveryInfoCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-start",
+ padding: "15px 15px",
+ borderRadius: "6px",
+ gap: "5px",
+ backgroundColor: theme.palette.mode === "dark" ? "#343434" : "#e8e8e8",
+ margin: "15px",
+}));
+
+export const CloseButtonRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ gap: 1,
+ justifyContent: "flex-end",
+}));
+
+export const CloseButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+}));
diff --git a/src/pages/ProductManager/ShowOrder/ShowOrder.tsx b/src/pages/ProductManager/ShowOrder/ShowOrder.tsx
new file mode 100644
index 0000000..80f245c
--- /dev/null
+++ b/src/pages/ProductManager/ShowOrder/ShowOrder.tsx
@@ -0,0 +1,563 @@
+import { FC, useEffect, useState, useMemo } from "react";
+import { ReusableModal } from "../../../components/modals/ReusableModal";
+import { Box, CircularProgress, useTheme } from "@mui/material";
+import EmailIcon from "@mui/icons-material/Email";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import {
+ base64ToUint8Array,
+ objectToBase64,
+ uint8ArrayToObject,
+} from "../../../utils/toBase64";
+import {
+ Divider,
+ OrderDetailsCard,
+ OrderDetailsContainer,
+ OrderId,
+ OrderQuantityRow,
+ OrderStatusCard,
+ OrderStatusNote,
+ OrderStatusRow,
+ OrderTitle,
+ OrderTitleCol,
+ PriceRow,
+ ShowOrderCol,
+ ShowOrderContent,
+ ShowOrderDateCreated,
+ ShowOrderHeader,
+ ShowOrderImages,
+ ShowOrderProductImage,
+ ShowOrderTitle,
+ TotalCostContainer,
+ TotalCostCol,
+ TotalCostFont,
+ OrderDetails,
+ DetailsFont,
+ DetailsRow,
+ DetailsCard,
+ CloseDetailsCardIcon,
+ DeliveryInfoCard,
+ CloseButton,
+ CloseButtonRow,
+ SellerOrderStatusRow,
+ CustomSelect,
+ UpdateStatusButton,
+ TotalPriceRow,
+} from "./ShowOrder-styles";
+import moment from "moment";
+import { DialogsSVG } from "../../../assets/svgs/DialogsSVG";
+import { Order, addToHashMap } from "../../../state/features/orderSlice";
+import { QortalSVG } from "../../../assets/svgs/QortalSVG";
+import { ExpandMoreSVG } from "../../../assets/svgs/ExpandMoreSVG";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import { CustomInputField } from "../../../components/modals/CreateStoreModal-styles";
+import { CustomMenuItem } from "../NewProduct/NewProduct-styles";
+import { CoinFilter } from "../../Store/Store/Store";
+import { ARRRSVG } from "../../../assets/svgs/ARRRSVG";
+
+/* must be replaced by everywhere here depending on the payment info */
+
+interface ShowOrderProps {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ order: Order;
+ from: string;
+}
+
+export const ShowOrder: FC = ({
+ isOpen,
+ setIsOpen,
+ order,
+ from,
+}) => {
+ const theme = useTheme();
+ const username = useSelector((state: RootState) => state.auth.user?.name);
+ const usernamePublicKey = useSelector(
+ (state: RootState) => state.auth.user?.publicKey
+ );
+ const hashMapOrders = useSelector(
+ (state: RootState) => state.order.hashMapOrders
+ );
+
+ const dispatch = useDispatch();
+
+ const [note, setNote] = useState("");
+ const [selectedStatus, setSelectedStatus] = useState("Received");
+ const [paymentInfo, setPaymentInfo] = useState(null);
+ const [paymentInfoForeign, setPaymentInfoForeign] = useState(false);
+
+ const [statusLoader, setStatusLoader] = useState(false);
+
+ const closeModal = () => {
+ setIsOpen(false);
+ };
+
+ const getPaymentInfo = async (signature: string) => {
+ try {
+ const url = `/transactions/signature/${signature}`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ // Coin payment info must be added to responseData so we can display it to the user
+ const responseData = await response.json();
+ if (responseData && !responseData.error) {
+ setPaymentInfo(responseData);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const updateStatus = async () => {
+ try {
+ const orderStateObject: any = {
+ status: selectedStatus,
+ note,
+ };
+ const orderStatusToBase64 = await objectToBase64(orderStateObject);
+
+ let res = await qortalRequest({
+ action: "GET_NAME_DATA",
+ name: order?.user,
+ });
+ const address = res.owner;
+ const resAddress = await qortalRequest({
+ action: "GET_ACCOUNT_DATA",
+ address: address,
+ });
+ if (!resAddress?.publicKey) throw new Error("Cannot find store owner");
+ const string = order?.id;
+
+ const identifier = string.replace(/(q-store)(-order)/, "$1-status$2");
+ const productRequestBody = {
+ action: "PUBLISH_QDN_RESOURCE",
+ identifier: identifier,
+ name: username,
+ service: "DOCUMENT_PRIVATE",
+ filename: `${order?.id}_status.json`,
+ data64: orderStatusToBase64,
+ encrypt: true,
+ publicKeys: [resAddress.publicKey, usernamePublicKey],
+ };
+ await qortalRequest(productRequestBody);
+ dispatch(
+ setNotification({
+ alertType: "success",
+ msg: "Order status updated successfully!",
+ })
+ );
+ } catch (error) {
+ console.log({ error });
+ }
+ };
+
+ const verifyIfOrderStatusUpdated = async () => {
+ if (!order?.id) return;
+ // Find the index of "order-" in the string
+ const extractedOrderIdEnd = order?.id.slice(
+ order?.id.indexOf("order-") + "order-".length
+ );
+ try {
+ setStatusLoader(true);
+ const query = `q-store-status-order-${extractedOrderIdEnd}`;
+ // Check if resource exists
+ const url = `/arbitrary/resources/search?service=DOCUMENT_PRIVATE&query=${query}&limit=1&includemetadata=false&mode=ALL&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
+ if (responseData.length === 0) return;
+ // Get the raw data if the resource exists
+ else {
+ let orderRawData = await qortalRequest({
+ action: "FETCH_QDN_RESOURCE",
+ name: from === "ProductManager" ? username : order?.sellerName,
+ service: "DOCUMENT_PRIVATE",
+ identifier: query,
+ encoding: "base64",
+ });
+ const base64 = orderRawData;
+ let orderUserName = await qortalRequest({
+ action: "GET_NAME_DATA",
+ name: order?.user,
+ });
+ const address = orderUserName.owner;
+ const resAddress = await qortalRequest({
+ action: "GET_ACCOUNT_DATA",
+ address: address,
+ });
+ if (!resAddress?.publicKey) throw new Error("Cannot find order owner");
+ const recipientPublicKey = resAddress.publicKey;
+ // Decrypt the raw data since it was encrypted when the status was changed
+ let requestEncryptBody: any = {
+ action: "DECRYPT_DATA",
+ encryptedData: base64,
+ publicKey: recipientPublicKey,
+ };
+ const resDecrypt = await qortalRequest(requestEncryptBody);
+
+ if (!resDecrypt) return;
+ const decryptToUnit8Array = base64ToUint8Array(resDecrypt);
+ const orderStatus = uint8ArrayToObject(decryptToUnit8Array);
+ setNote(orderStatus?.note || "");
+ setSelectedStatus(orderStatus?.status || "");
+ dispatch(
+ addToHashMap({
+ ...order,
+ status: orderStatus?.status,
+ note: orderStatus?.note,
+ })
+ );
+ return;
+ }
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setStatusLoader(false);
+ }
+ };
+
+ // Check to see if any of the products in the order are digital and that there are none which are physical. Hide delivery details in the order if that's the case.
+ const isDigitalOrder = useMemo(() => {
+ if (order && order?.details) {
+ if (!order) return false;
+ return Object.keys(order?.details || {})
+ .filter(key => key !== "totalPrice")
+ .every(key => {
+ const product = order?.details?.[key]?.product;
+ if (!product) return false;
+ return product?.type === "digital";
+ });
+ } else {
+ return false;
+ }
+ }, [order]);
+
+ // Check to see if product status has been updated when opening from either ProductManager.tsx or from MyOrders.tsx
+ useEffect(() => {
+ if (isOpen && order?.id) {
+ verifyIfOrderStatusUpdated();
+ }
+ }, [isOpen, order?.id]);
+
+
+ const coinToUse = order?.payment?.currency
+
+
+ return (
+
+
+
+
+ {Object.keys(order?.details || {})
+ .filter(key => key !== "totalPrice")
+ .map((key, index) => {
+ return (
+
+ );
+ })}
+
+
+
+
+ {from === "ProductManager"
+ ? `Message ${order?.delivery?.customerName} on Q-Mail`
+ : `Message ${order?.sellerName} on Q-Mail`}
+
+
+ {moment(order?.created).format("llll")}
+
+
+
+
+ <>
+ {statusLoader ? (
+
+ ) : from === "ProductManager" ? (
+
+ Order Status
+ {
+ setSelectedStatus(event.target.value as string);
+ }}
+ variant="filled"
+ required
+ >
+ Received
+ Shipped
+ Refunded
+
+ setNote(e.target.value)}
+ size="small"
+ fullWidth
+ />
+
+ Update Status
+
+
+ ) : (
+
+
+ Order Status: {hashMapOrders[order?.id]?.status}
+
+
+ {hashMapOrders[order?.id]?.note}
+
+
+ )}
+ >
+ <>
+ {order?.details && (
+ <>
+
+ {Object.keys(order?.details || {})
+ .filter(key => key !== "totalPrice")
+ .map(key => {
+ const product = order?.details?.[key];
+ return (
+
+
+
+
+ {product?.product?.title}
+
+ Product Id: {product?.product?.id}
+
+
+
+
+
+ {`x ${product?.quantity}`}
+ {product?.pricePerUnit}
+
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+
+
+ Total: {product?.totalProductPrice}
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+
+
+
+
+ );
+ })}
+
+
+
+
+
+ {coinToUse === CoinFilter.qort && (
+
+ )}
+ {coinToUse === CoinFilter.arrr && (
+
+ )}
+
+
+ {order?.details?.totalPrice}
+
+
+
+
+ {
+ if(coinToUse === CoinFilter.qort){
+ getPaymentInfo(
+ order?.payment?.transactionSignature as string
+ )
+ } else {
+ setPaymentInfoForeign(true)
+ }
+ }
+
+
+ }
+ >
+ Payment Details
+
+
+
+
+ {paymentInfoForeign && (
+
+ {coinToUse === CoinFilter.arrr && (
+ <>
+ Currency:{" "}
+
+ {coinToUse}
+
+ Payment sent to{" "}
+
+ {order?.payment?.arrrAddressUsed}
+
+ >
+ )}
+ setPaymentInfoForeign(false)}
+ />
+
+ )}
+
+
+ {paymentInfo && (
+
+ {Object.keys(paymentInfo || {}).map(key => {
+ return (
+ <>
+ {key}:{" "}
+
+ {paymentInfo[key]}
+
+ >
+ );
+ })}
+ setPaymentInfo(null)}
+ />
+
+ )}
+
+ >
+ )}
+ >
+ {!isDigitalOrder && (
+
+ Delivery Information
+
+ Shop Name: {order?.storeName}
+
+
+ Seller Name: {order?.sellerName}
+
+
+ Customer name: {order?.delivery?.customerName}
+
+ {order?.delivery?.shippingAddress && (
+ <>
+ {Object.entries(order?.delivery?.shippingAddress).map(
+ ([key, value]) => (
+
+ {key}: {value}
+
+ )
+ )}
+ >
+ )}
+
+ )}
+
+
+
+ Close
+
+
+
+
+ );
+};
diff --git a/src/pages/Store/ProductCard/ProductCard-styles.tsx b/src/pages/Store/ProductCard/ProductCard-styles.tsx
new file mode 100644
index 0000000..dda4425
--- /dev/null
+++ b/src/pages/Store/ProductCard/ProductCard-styles.tsx
@@ -0,0 +1,63 @@
+import { styled } from "@mui/system";
+import { Box, Button, Card, CardContent } from "@mui/material";
+
+export const ProductTitle = styled(Box)(({ theme }) => ({
+ displayq: "flex",
+ alignItems: "center",
+ gap: "5px",
+ fontFamily: "Merriweather Sans",
+ fontSize: "18px",
+ wordBreak: "break-word",
+ color: theme.palette.text.primary,
+ userSelect: "none"
+}));
+
+export const ProductDescription = styled(Box)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "16px",
+ color: theme.palette.text.primary,
+ opacity: 0.95,
+ wordBreak: "break-word",
+ maxHeight: "75px",
+ userSelect: "none"
+}));
+
+export const AddToCartButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontWeight: 300,
+ fontSize: "16.5px",
+ borderRadius: "7px",
+ padding: "5px 10px",
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ backgroundColor: "transparent",
+ color: theme.palette.text.primary,
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ filter:
+ theme.palette.mode === "dark" ? "brightness(1.8)" : "brightness(0.2)"
+ }
+}));
+
+export const StyledCard = styled(Card)(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ color: theme.palette.text.primary,
+ overflow: "hidden",
+ boxShadow: "none",
+ borderRadius: "8px",
+ transition: "all 0.3s ease-in-out 0s",
+ minHeight: ["auto", "400px"],
+ display: "flex",
+ flexDirection: "column"
+}));
+
+export const StyledCardContent = styled(CardContent)(({ theme }) => ({
+ height: "100%",
+ padding: "8px 16px",
+ gap: "10px",
+ display: "flex",
+ flexDirection: "column",
+ flexGrow: 1
+}));
diff --git a/src/pages/Store/ProductCard/ProductCard.tsx b/src/pages/Store/ProductCard/ProductCard.tsx
new file mode 100644
index 0000000..6b62a6c
--- /dev/null
+++ b/src/pages/Store/ProductCard/ProductCard.tsx
@@ -0,0 +1,183 @@
+import { useMemo } from "react";
+import { Card, CardContent, CardMedia, useTheme } from "@mui/material";
+import { RootState } from "../../../state/store";
+import { Product } from "../../../state/features/storeSlice";
+import { useDispatch, useSelector } from "react-redux";
+import { setProductToCart } from "../../../state/features/cartSlice";
+import { QortalSVG } from "../../../assets/svgs/QortalSVG";
+import {
+ AddToCartButton,
+ ProductDescription,
+ ProductTitle,
+ StyledCard,
+ StyledCardContent,
+} from "./ProductCard-styles";
+import { CartSVG } from "../../../assets/svgs/CartSVG";
+import { useNavigate } from "react-router-dom";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import { AcceptedCoinRow } from "../Store/Store-styles";
+import { ARRRSVG } from "../../../assets/svgs/ARRRSVG";
+import { CoinFilter } from "../Store/Store";
+
+function addEllipsis(str: string, limit: number) {
+ if (str.length > limit) {
+ return str.substring(0, limit - 3) + "...";
+ } else {
+ return str;
+ }
+}
+interface ProductCardProps {
+ product: Product;
+ exchangeRate: number | null;
+ filterCoin: string;
+}
+
+export const ProductCard: React.FC = ({ product, exchangeRate, filterCoin }) => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const theme = useTheme();
+
+ const storeId = useSelector((state: RootState) => state.store.storeId);
+
+ const storeOwner = useSelector((state: RootState) => state.store.storeOwner);
+
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+
+ const userName = useMemo(() => {
+ if (!user?.name) return "";
+ return user.name;
+ }, [user]);
+
+ const profileImg = product?.images?.[0];
+
+ const goToProductPage = () => {
+ if (!product || !product?.id || !product?.catalogueId || !storeId) {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Unable to load product! Please try again!",
+ })
+ );
+ return;
+ }
+ navigate(
+ `/${
+ product?.user || catalogueHashMap[product?.catalogueId]?.user
+ }/${storeId}/${product?.id}/${product.catalogueId}`
+ );
+ };
+
+ let price = product?.price?.find(item => item?.currency === "qort")?.value;
+ const priceArrr = product?.price?.find(item => item?.currency === CoinFilter.arrr)?.value;
+ if(filterCoin === CoinFilter.arrr && priceArrr) {
+ price = +priceArrr
+ }
+ else if(price && exchangeRate && filterCoin !== CoinFilter.qort){
+ price = +price * exchangeRate
+ }
+
+ return (
+
+
+
+ {addEllipsis(product?.title || "", 39)}
+
+ {addEllipsis(product?.description || "", 58)}
+
+
+ {filterCoin === CoinFilter.qort && (
+
+ {" "}
+ {price}
+
+ )}
+ {filterCoin === CoinFilter.arrr && (
+
+ {" "}
+ {price}
+
+ )}
+
+
+
+
+ {storeOwner !== userName && (
+
{
+ if (product.status !== "AVAILABLE") {
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Product is not available!",
+ })
+ );
+ return;
+ }
+ dispatch(
+ setProductToCart({
+ productId: product.id,
+ catalogueId: product.catalogueId,
+ storeId,
+ storeOwner,
+ })
+ );
+ }}
+ >
+ {product.status === "AVAILABLE" ? (
+ <>
+ {" "}
+ Add to Cart
+ >
+ ) : product.status === "OUT_OF_STOCK" ? (
+ "Out of Stock"
+ ) : (
+ "Retired"
+ )}
+
+ )}
+
+
+ );
+};
diff --git a/src/pages/Store/Store/Store-styles.tsx b/src/pages/Store/Store/Store-styles.tsx
new file mode 100644
index 0000000..7212227
--- /dev/null
+++ b/src/pages/Store/Store/Store-styles.tsx
@@ -0,0 +1,348 @@
+import { styled } from "@mui/system";
+import {
+ Box,
+ Button,
+ Typography,
+ Grid,
+ Checkbox,
+ InputLabel,
+ Autocomplete,
+ TextField,
+ Chip,
+} from "@mui/material";
+import { ReusableModal } from "../../../components/modals/ReusableModal";
+
+interface StoreProps {
+ fixedCartPosition: boolean;
+}
+
+export const FiltersCol = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ height: "100%",
+ padding: "20px 15px",
+ backgroundColor: theme.palette.background.default,
+ borderTop: `1px solid ${theme.palette.background.paper}`,
+ borderRight: `1px solid ${theme.palette.background.paper}`,
+}));
+
+export const FiltersContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ justifyContent: "space-between",
+}));
+
+export const FiltersRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ width: "100%",
+ padding: "0 15px",
+ fontSize: "16px",
+ userSelect: "none",
+}));
+
+export const FiltersTitle = styled(Typography)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ margin: "20px 0",
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const FiltersCheckbox = styled(Checkbox)(({ theme }) => ({
+ color: "#c0d4ff",
+ "&.Mui-checked": {
+ color: "#6596ff",
+ },
+}));
+
+export const FiltersOption = styled("li")(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ paddingTop: "2px !important",
+ paddingBottom: "2px !important",
+}));
+
+export const FiltersChip = styled(Chip)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "13px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const FilterSelect = styled(Autocomplete)(({ theme }) => ({
+ "& #categories-select": {
+ padding: "7px",
+ },
+ "& [class*='MuiInputBase-root-MuiOutlinedInput-root']": {
+ gap: "5px",
+ },
+ '& [class*="MuiFormLabel-root-MuiInputLabel-root"]': {
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ height: "25px",
+ },
+}));
+
+export const FilterSelectMenuItems = styled(TextField)(({ theme }) => ({
+ '& [class*="MuiInputBase-input"]': {
+ fontFamily: "Raleway",
+ fontSize: "17px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ },
+}));
+
+export const ProductManagerRow = styled(Box)(({ theme }) => ({
+ display: "grid",
+ gridTemplateColumns: "1fr auto",
+ alignItems: "center",
+ justifyContent: "flex-end",
+ width: "100%",
+ padding: "15px 18px",
+}));
+
+export const StoreTitleCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifySelf: "center",
+ width: "fit-content",
+ borderRadius: "8px",
+ padding: "10px 15px",
+ backgroundColor: theme.palette.background.paper,
+ gap: "10px",
+ transition: "all 0.3s ease-in-out",
+}));
+
+export const StoreTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "20px",
+ color: theme.palette.text.primary,
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ textDecoration: "underline",
+ },
+}));
+
+export const StoreLogo = styled("img")(({ theme }) => ({
+ width: "70px",
+ height: "70px",
+ borderRadius: "3px",
+ objectFit: "cover",
+}));
+
+export const FiltersSubContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+ gap: "5px",
+}));
+
+export const FilterDropdownLabel = styled(InputLabel)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ color: theme.palette.text.primary,
+}));
+
+export const StoreControlsRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+}));
+
+export const EditStoreButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "3px 12px",
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ color: "#ffffff",
+ backgroundColor: "#D4417E",
+ border: "none",
+ borderRadius: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#a13562",
+ },
+}));
+
+export const ProductManagerButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "3px 12px",
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ color: "#ffffff",
+ backgroundColor: theme.palette.secondary.main,
+ border: "none",
+ borderRadius: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.secondary.dark,
+ },
+}));
+
+export const BackToStorefrontButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "3px 12px",
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ color: "#ffffff",
+ backgroundColor: theme.palette.mode === "dark" ? "#bdba02" : "#e1dd04",
+ border: "none",
+ borderRadius: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.mode === "dark" ? "#a5a201" : "#c7c402",
+ },
+}));
+
+export const ProductsContainer = styled(Grid)({
+ display: "flex",
+ flexWrap: "wrap",
+ padding: "5px 35px 15px 35px",
+});
+
+export const NoProductsContainer = styled(Box)({
+ textAlign: "center",
+ width: "100%",
+ marginTop: "70px",
+});
+
+export const NoProductsText = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans",
+ fontSize: "24px",
+ letterSpacing: "0.7px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const CartIconContainer = styled(Box)(
+ ({ theme, fixedCartPosition }) => ({
+ position: fixedCartPosition ? "fixed" : "relative",
+ top: fixedCartPosition ? "90px" : 0,
+ right: fixedCartPosition ? "17px" : 0,
+ zIndex: fixedCartPosition ? "55" : 0,
+ padding: fixedCartPosition ? "20px" : 0,
+ backgroundColor: fixedCartPosition
+ ? `${theme.palette.background.paper}`
+ : "none",
+ borderRadius: fixedCartPosition ? "50%" : 0,
+ display: fixedCartPosition ? "flex" : "block",
+ alignItems: fixedCartPosition ? "center" : "center",
+ justifyContent: fixedCartPosition ? "center" : "center",
+ cursor: "pointer",
+ })
+);
+
+export const NotificationBadge = styled(Box)(
+ ({ theme, fixedCartPosition }) => ({
+ position: "absolute",
+ top: fixedCartPosition ? "6px" : "-7px",
+ right: fixedCartPosition ? "9px" : "-7px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ width: "22px",
+ height: "22px",
+ borderRadius: "50%",
+ backgroundColor: theme.palette.mode === "dark" ? "#bdba02" : "#e1dd04",
+ color: "#000000",
+ fontFamily: "Karla",
+ fontSize: "14px",
+ fontWeight: "bold",
+ userSelect: "none",
+ })
+);
+
+export const ProductCardCol = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ gap: 1,
+ alignItems: "center",
+ width: "auto",
+ position: "relative",
+ maxWidth: "100%",
+ flexGrow: 1,
+ [theme.breakpoints.down("sm")]: {
+ width: "100%",
+ },
+}));
+
+export const StoreTitleCol = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+});
+
+export const RatingContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "1px 5px",
+ borderRadius: "5px",
+ fontFamily: "Karla",
+ letterSpacing: 0,
+ fontWeight: 300,
+ fontSize: "21px",
+ backgroundColor: "transparent",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: "#e4ddddac",
+ },
+}));
+
+export const ReusableModalStyled = styled(ReusableModal)(({ theme }) => ({
+ "store-details": {
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "8px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+ },
+}));
+
+export const OfferedCoinsRow = styled(Box)(() => ({
+ fontSize: "21px",
+ display: "flex",
+ alignItems: "center",
+ marginTop: "5px",
+ gap: "7px",
+ fontFamily: "Karla",
+ letterSpacing: 0,
+ fontWeight: 300,
+ userSelect: "none",
+}));
+
+export const AcceptedCoinRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ gap: "7px",
+});
diff --git a/src/pages/Store/Store/Store.tsx b/src/pages/Store/Store/Store.tsx
new file mode 100644
index 0000000..8b96ffa
--- /dev/null
+++ b/src/pages/Store/Store/Store.tsx
@@ -0,0 +1,1140 @@
+import React, {
+ useState,
+ useEffect,
+ useCallback,
+ useMemo,
+ useRef,
+} from "react";
+import { useNavigate } from "react-router-dom";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+import { useParams } from "react-router-dom";
+import {
+ useTheme,
+ Grid,
+ CircularProgress,
+ Chip,
+ FormControl,
+ Rating,
+ Typography,
+ Skeleton,
+} from "@mui/material";
+import {
+ Catalogue,
+ resetListProducts,
+ resetProducts,
+ setIsLoadingGlobal,
+ updateRecentlyVisitedStoreId,
+} from "../../../state/features/globalSlice";
+import {
+ Product,
+ clearReviews,
+ clearViewedStoreDataContainer,
+ setCurrentViewedStore,
+ setPreferredCoin,
+ setViewedStoreDataContainer,
+} from "../../../state/features/storeSlice";
+import LazyLoad from "../../../components/common/LazyLoad";
+import ContextMenuResource from "../../../components/common/ContextMenu/ContextMenuResource";
+import { setStoreId, setStoreOwner } from "../../../state/features/storeSlice";
+import { ProductCard } from "../ProductCard/ProductCard";
+import { ProductDataContainer } from "../../../state/features/globalSlice";
+import { useFetchOrders } from "../../../hooks/useFetchOrders";
+import {
+ ProductManagerRow,
+ ProductManagerButton,
+ BackToStorefrontButton,
+ ProductsContainer,
+ NoProductsContainer,
+ NoProductsText,
+ StoreControlsRow,
+ EditStoreButton,
+ CartIconContainer,
+ NotificationBadge,
+ FiltersCol,
+ FiltersContainer,
+ FiltersTitle,
+ FiltersCheckbox,
+ FiltersRow,
+ FiltersSubContainer,
+ FilterSelect,
+ FilterSelectMenuItems,
+ ProductCardCol,
+ StoreTitleCard,
+ StoreLogo,
+ StoreTitleCol,
+ StoreTitle,
+ RatingContainer,
+ ReusableModalStyled,
+ FiltersOption,
+ FiltersChip,
+ AcceptedCoinRow,
+ OfferedCoinsRow,
+} from "./Store-styles";
+import { toggleEditStoreModal } from "../../../state/features/globalSlice";
+import { CartIcon } from "../../../components/layout/Navbar/Navbar-styles";
+import { setIsOpen } from "../../../state/features/cartSlice";
+import { Cart as CartInterface } from "../../../state/features/cartSlice";
+import { ExpandMoreSVG } from "../../../assets/svgs/ExpandMoreSVG";
+import { StoreDetails } from "../StoreDetails/StoreDetails";
+import { StoreReviews } from "../StoreReviews/StoreReviews";
+import { setNotification } from "../../../state/features/notificationsSlice";
+import {
+ DATA_CONTAINER_BASE,
+ REVIEW_BASE,
+ STORE_BASE,
+} from "../../../constants/identifiers";
+import QORT from "../../../assets/img/qort.png";
+import ARRR from "../../../assets/img/arrr.png";
+import {
+ AcceptedCoin,
+ ExchangeRateCard,
+ ExchangeRateRow,
+ ExchangeRateSubTitle,
+ ExchangeRateTitle,
+} from "../../StoreList/StoreList-styles";
+import { CompareArrowsSVG } from "../../../assets/svgs/CompareArrowsSVG";
+
+interface IListProducts {
+ sort: string;
+ products: ProductDataContainer[];
+ categories: { label: string }[];
+}
+
+enum PriceFilter {
+ highest = "Highest",
+ lowest = "Lowest",
+}
+
+enum DateFilter {
+ newest = "Newest",
+ oldest = "Oldest",
+}
+
+export enum CoinFilter {
+ qort = "QORT",
+ arrr = "ARRR",
+}
+
+export const Store = () => {
+ const navigate = useNavigate();
+ const theme = useTheme();
+ const dispatch = useDispatch();
+
+ const isFirstMountRef = useRef(false);
+
+ const user = useSelector((state: RootState) => state.auth.user);
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const isLoadingGlobal = useSelector(
+ (state: RootState) => state.global.isLoadingGlobal
+ );
+ const catalogueHashMap = useSelector(
+ (state: RootState) => state.global.catalogueHashMap
+ );
+ // Fetch all carts from Redux
+ const carts = useSelector((state: RootState) => state.cart.carts);
+ // Get storeId from Redux
+ const storeId = useSelector((state: RootState) => state.store.storeId);
+ // Get storeOwner from Redux
+ const storeOwner = useSelector((state: RootState) => state.store.storeOwner);
+ const preferredCoin = useSelector((state: RootState) => state.store.preferredCoin);
+
+ // Get current viewed store from Redux
+ const currentViewedStore = useSelector(
+ (state: RootState) => state.store.currentViewedStore
+ );
+ // List products from store's data container
+ const viewedStoreListProducts = useSelector(
+ (state: RootState) => state.store.viewedStoreListProducts
+ );
+ // List products from store's data container when you own the store
+ const ownStoreListProducts = useSelector(
+ (state: RootState) => state.global.listProducts
+ );
+
+ // Your own data container
+ const userOwnDataContainer = useSelector(
+ (state: RootState) => state.global.dataContainer
+ );
+
+ const { checkAndUpdateResourceCatalogue, getCatalogue } = useFetchOrders();
+
+ const { store, user: username } = useParams();
+
+ const [products, setProducts] = React.useState([]);
+ const [totalCartQuantity, setTotalCartQuantity] = useState(0);
+ const [filterPrice, setFilterPrice] = useState(null);
+ const [filterDate, setFilterDate] = useState(
+ DateFilter.newest
+ );
+ const [filterCoin, setFilterCoin] = useState(CoinFilter.qort);
+ const [categoryChips, setCategoryChips] = useState([]);
+ const [openStoreDetails, setOpenStoreDetails] = useState(false);
+ const [openStoreReviews, setOpenStoreReviews] = useState(false);
+ const [averageStoreRating, setAverageStoreRating] = useState(
+ null
+ );
+ const [averageRatingLoader, setAverageRatingLoader] =
+ useState(false);
+ const [hasFetched, setHasFetched] = useState(false);
+ const [exchangeRate, setExchangeRate] = useState(
+ null
+ );
+
+ const storeToUse = useMemo(()=> {
+ return username === user?.name
+ ? currentStore
+ : currentViewedStore
+ }, [username, user?.name, currentStore, currentViewedStore])
+
+ const switchCoin = async ()=> {
+ dispatch(setIsLoadingGlobal(true));
+
+ await calculateARRRExchangeRate()
+ dispatch(setIsLoadingGlobal(false));
+
+
+ }
+
+ useEffect(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ switchCoin()
+ }
+ }, [preferredCoin, storeToUse])
+
+ const coinToUse = useMemo(()=> {
+ if(preferredCoin === CoinFilter.arrr && storeToUse?.supportedCoins?.includes(CoinFilter.arrr)){
+ return CoinFilter.arrr
+ } else {
+ return CoinFilter.qort
+ }
+ }, [preferredCoin, storeToUse])
+ const getProducts = useCallback(async () => {
+ if (!store) return;
+ try {
+ const offset = products.length;
+ // Get products from store's data container, with ternary depending on whether you own the store or not
+ const productList =
+ username !== user?.name
+ ? viewedStoreListProducts.products
+ : ownStoreListProducts.products;
+ const responseData = productList.slice(offset, offset + 20);
+ const structureData = responseData.map(
+ (product: ProductDataContainer): Product => {
+ return {
+ created: product?.created,
+ catalogueId: product.catalogueId,
+ id: product?.productId || "",
+ user: product?.user || "",
+ status: product?.status || "",
+ };
+ }
+ );
+ isFirstMountRef.current = true;
+ setHasFetched(true);
+ const copiedProducts = [...products];
+ structureData.forEach((product: Product) => {
+ const index = copiedProducts.findIndex(p => p.id === product.id);
+ if (index !== -1) {
+ copiedProducts[index] = product;
+ } else {
+ copiedProducts.push(product);
+ }
+ });
+ setProducts(copiedProducts);
+ // First check in catalogueHashMap if the product raw data is available. If it is, don't do anything as that data will already be available to use in the filterProducts useMemo() function.
+ // If it isn't in catalogueHashMap, loop through the products and add the catalogue found so that you don't fetch other catalogues that you don't need, as each one contains 10 products. This goes too fast on first mount, therefore you can't rely on redux to verify if the catalogue is available or not.
+ let localCatalogue: Record = {};
+ for (const content of structureData) {
+ if (
+ (catalogueHashMap[content.catalogueId] &&
+ catalogueHashMap[content.catalogueId].products[content.id]) ||
+ localCatalogue[content.catalogueId]
+ ) {
+ continue;
+ }
+ if (content.user && content.id) {
+ const res = checkAndUpdateResourceCatalogue({
+ id: content.catalogueId,
+ });
+ if (res) {
+ const fetchedCatalogue: Catalogue = await getCatalogue(
+ content.user,
+ content.catalogueId
+ );
+ if (fetchedCatalogue) {
+ localCatalogue = {
+ ...localCatalogue,
+ [content.catalogueId]: fetchedCatalogue,
+ };
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }, [
+ products,
+ viewedStoreListProducts.products,
+ ownStoreListProducts.products,
+ store,
+ username,
+ user?.name,
+ ]);
+
+ // Get products on mount
+ useEffect(() => {
+ // Get products from store's data container, with ternary depending on whether you own the store or not
+ if (!isFirstMountRef.current && store) {
+ const productList =
+ username && username !== user?.name
+ ? viewedStoreListProducts.products
+ : ownStoreListProducts.products;
+ if (productList.length === 0) return;
+ // This gets the product raw data
+ getProducts();
+ }
+ }, [
+ getProducts,
+ username,
+ user?.name,
+ store,
+ viewedStoreListProducts?.products,
+ ownStoreListProducts?.products,
+ ]);
+
+ // Get store on mount & dipatch setCurrentViewedStore to redux when it's not your store. If it is your store, this is handled already in the global wrapper, so we do nothing as well.
+ const getStore = useCallback(async () => {
+ let name = username;
+ if (!name) return;
+ if (!store) return;
+
+ try {
+ dispatch(setIsLoadingGlobal(true));
+ // Have access to the storeId, store owner and recently viewed store id in global state for when you are in cart for example
+ dispatch(setStoreId(store));
+ dispatch(updateRecentlyVisitedStoreId(store));
+ dispatch(setStoreOwner(name));
+ // Check if store data is not already inside redux, and that if it is, that it's not from another store. This is to avoid unnecessary QDN calls. getProducts() will get its data from the existing data container in Redux if the store hasn't changed, hence why we clear the products array here as well as the datacontainer only if the currentViewedStore is not the same as the store in the url. The logic below is only for other's people's stores, not your own. Your own store is handled in the global wrapper.
+ if (
+ (!currentViewedStore && name !== user?.name) ||
+ (currentViewedStore?.id !== store && name !== user?.name)
+ ) {
+ setProducts([]);
+ dispatch(clearReviews());
+ dispatch(clearViewedStoreDataContainer());
+ let myStore;
+ const url = `/arbitrary/resources/search?service=STORE&identifier=${store}&exactmatchnames=true&mode=ALL&name=${name}&includemetadata=false`;
+ const info = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ const responseDataStore = await info.json();
+ const filterOut = responseDataStore.filter((store: any) =>
+ store.identifier.startsWith(`${STORE_BASE}-`)
+ );
+ if (filterOut.length === 0) return;
+ if (filterOut.length !== 0) {
+ // Get first element since it returns an array of stores
+ myStore = filterOut[0];
+ }
+
+ const urlStore = `/arbitrary/STORE/${name}/${store}`;
+ const resource = await fetch(urlStore, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await resource.json();
+ // Set shop data to redux now that you have the info/metadata & resource
+ dispatch(
+ setCurrentViewedStore({
+ created: responseData?.created || "",
+ id: myStore.identifier,
+ title: responseData?.title || "",
+ location: responseData?.location,
+ shipsTo: responseData?.shipsTo,
+ description: responseData?.description || "",
+ category: myStore.metadata?.category,
+ tags: myStore.metadata?.tags || [],
+ logo: responseData?.logo || "",
+ shortStoreId: responseData?.shortStoreId,
+ supportedCoins: responseData?.supportedCoins || [],
+ foreignCoins: responseData?.foreignCoins || {}
+ })
+ );
+
+ const urlDatacontainer = `/arbitrary/DOCUMENT/${name}/${store}-${DATA_CONTAINER_BASE}`;
+ const responseContainer = await fetch(urlDatacontainer, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseDataContainer = await responseContainer.json();
+
+ // Call to see if the datacontainer actually exists
+ const dataContainerExists = await qortalRequest({
+ action: "SEARCH_QDN_RESOURCES",
+ service: "DOCUMENT",
+ identifier: `${myStore.identifier}-${DATA_CONTAINER_BASE}`,
+ name: name,
+ prefix: false,
+ exactMatchNames: true,
+ limit: 0,
+ offset: 0,
+ reverse: true,
+ mode: "ALL"
+ });
+
+ // Set dataContainer in redux if it is found. This is to do filtering in the future since it cannot be done on QDN at the moment.
+ if (
+ responseDataContainer &&
+ !("error" in responseDataContainer) &&
+ Object.keys(responseDataContainer).length > 0
+ ) {
+ dispatch(
+ setViewedStoreDataContainer({
+ ...responseDataContainer,
+ id: `${store}-${DATA_CONTAINER_BASE}`,
+ })
+ );
+ // If they haven't published a datacontainer, by querying "/arbitrary/resources/search", and it comes back as []
+ // If you can't find the data container, aka response code of 404, and it's not your own store, redirect to home page.
+ } else if (
+ responseContainer.status === 404 &&
+ dataContainerExists.length === 0
+ ) {
+ navigate("/");
+ dispatch(
+ setNotification({
+ msg: "Error getting store data container.",
+ alertType: "error",
+ })
+ );
+ // If they haven't published a datacontainer, by querying "/arbitrary/resources/search", and it comes back as []
+ // If you can't find the data container, aka response code of 404, and it's your own store, prompt to create the data container.
+ } else if (
+ !responseContainer.ok &&
+ responseContainer.status !== 404 &&
+ dataContainerExists.length > 0
+ ) {
+ navigate("/");
+ dispatch(
+ setNotification({
+ msg: "Server Error",
+ alertType: "error",
+ })
+ );
+ // If the datacontainer exists and you cannot find it, but it's a response of 500, throw general error and redirect to home page.
+ } else {
+ navigate("/");
+ dispatch(
+ setNotification({
+ msg: "Server Error",
+ alertType: "error",
+ })
+ );
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ } finally {
+ dispatch(setIsLoadingGlobal(false));
+ }
+ }, [username, store, currentViewedStore]);
+
+ // Get 100 store reviews from QDN and calculate the average review.
+ const getStoreAverageReview = async () => {
+ if (!storeId) return;
+ try {
+ setAverageRatingLoader(true);
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+ const query = `${REVIEW_BASE}-${shortStoreId}`;
+ // Since it the url includes /resources, you know you're fetching the resources and not the raw data
+ const url = `/arbitrary/resources/search?service=DOCUMENT&query=${query}&limit=100&includemetadata=false&mode=ALL&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
+ if (responseData.length === 0) {
+ setAverageStoreRating(null);
+ return;
+ }
+ // Modify resource into data that is more easily used on the front end
+ const storeRatingsArray = responseData.map((review: any) => {
+ const splitIdentifier = review.identifier.split("-");
+ const rating = Number(splitIdentifier[splitIdentifier.length - 1]) / 10;
+ return rating;
+ });
+
+ // Calculate average rating of the store
+ let averageRating =
+ storeRatingsArray.reduce((acc: number, curr: number) => {
+ return acc + curr;
+ }, 0) / storeRatingsArray.length;
+
+ averageRating = Math.ceil(averageRating * 2) / 2;
+
+ setAverageStoreRating(averageRating);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setAverageRatingLoader(false);
+ }
+ };
+
+ // Get Store and set it in redux global state
+ useEffect(() => {
+ getStore();
+ }, [username, store]);
+
+ // Get average store rating when storeId is available, and only if the storeId is different from the currentViewedStore when it's not your store, or if storeId is different from currentStore when it is your store. Do this to avoid unnecessary QDN calls.
+ useEffect(() => {
+ if (
+ storeId &&
+ ((currentViewedStore?.id !== storeId &&
+ username !== user?.name &&
+ storeOwner !== user?.name) ||
+ (currentStore?.id !== storeId && username === user?.name))
+ ) {
+ getStoreAverageReview();
+ }
+ }, [storeId, currentViewedStore, currentStore, username, storeOwner, user]);
+
+ // Set cart notifications when cart changes
+ useEffect(() => {
+ if (user?.name && storeId) {
+ const shopCart: CartInterface = carts[storeId];
+ // Get the orders of this cart
+ const orders = shopCart?.orders || {};
+ let totalQuantity = 0;
+ Object.keys(orders).forEach(key => {
+ const order = orders[key];
+ const { quantity } = order;
+ totalQuantity += quantity;
+ });
+ setTotalCartQuantity(totalQuantity);
+ }
+ }, [carts, user, storeId]);
+
+ const getProductsHandler = useCallback(async () => {
+ if (!isFirstMountRef.current) return;
+ await getProducts();
+ }, [getProducts]);
+
+ // Filter products
+ const filteredProducts = useMemo(() => {
+ const newArray: any = products
+ .map((product: Product, index) => {
+ if (
+ catalogueHashMap[product?.catalogueId] &&
+ catalogueHashMap[product.catalogueId].products[product?.id]
+ ) {
+ return {
+ ...product,
+ ...catalogueHashMap[product.catalogueId].products[product?.id],
+ catalogueId: product?.catalogueId || "",
+ };
+ } else {
+ return product;
+ }
+ })
+ .filter((product: any) => {
+ const condition =
+ product?.status === "AVAILABLE" || product?.status === "OUT_OF_STOCK";
+ return condition;
+ });
+ const newArray2 = newArray.sort((a: Product, b: Product) => {
+ if (
+ filterPrice === PriceFilter.highest &&
+ a?.price &&
+ b?.price &&
+ a?.price[0]?.value &&
+ b?.price[0]?.value
+ ) {
+ return b?.price[0]?.value - a?.price[0]?.value;
+ } else if (
+ filterPrice === PriceFilter.lowest &&
+ a?.price &&
+ b?.price &&
+ a?.price[0]?.value &&
+ b?.price[0]?.value
+ ) {
+ return a?.price[0]?.value - Number(b?.price[0]?.value);
+ } else return newArray;
+ });
+ const newArray3: any = newArray2.filter((product: Product) => {
+ const condition =
+ categoryChips.length > 0
+ ? product?.category &&
+ categoryChips.some(chip => chip === product.category)
+ : true;
+ return condition;
+ });
+ const newArray4: any = newArray3.sort((a: Product, b: Product) => {
+ if (filterDate === DateFilter.newest) {
+ return b?.created - a?.created;
+ } else if (filterDate === DateFilter.oldest) {
+ return a?.created - b?.created;
+ } else {
+ return newArray3;
+ }
+ });
+ return newArray4;
+ }, [filterDate, filterPrice, products, categoryChips, catalogueHashMap]);
+
+ // Filtering by categories
+
+ const handleChipSelect = (value: string[]) => {
+ setCategoryChips(value);
+ };
+
+ const handleChipRemove = (chip: string) => {
+ setCategoryChips(prevChips => prevChips.filter(c => c !== chip));
+ };
+
+ if (isLoadingGlobal) return;
+
+ if (!currentViewedStore && username !== user?.name && !isLoadingGlobal)
+ return (
+
+ Store Not Found!
+
+ );
+
+
+
+
+ const calculateARRRExchangeRate = async()=> {
+ try {
+ const url = '/crosschain/price/PIRATECHAIN?maxtrades=10&inverse=true'
+ const info = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseDataStore = await info.text();
+
+ const ratio = +responseDataStore /100000000
+ if(isNaN(ratio)) throw new Error('Cannot get exchange rate')
+ setExchangeRate(ratio)
+ } catch (error) {
+ dispatch(setPreferredCoin(CoinFilter.qort))
+ dispatch(
+ setNotification({
+ alertType: "error",
+ msg: "Cannot get exchange rate- reverted to QORT",
+ })
+ );
+ }
+
+ }
+
+
+ return (
+
+
+ {
+ navigate("/");
+ }}
+ >
+ Back To All Shops
+
+
+
+ Categories
+
+
+
+
+
+ handleChipSelect(value as string[])
+ }
+ renderTags={(values: any) =>
+ values.map((value: string) => {
+ return (
+ {
+ handleChipRemove(value);
+ }}
+ />
+ );
+ })
+ }
+ renderOption={(props, option: any) => (
+
+ chip === option)}
+ />
+ {option}
+
+ )}
+ renderInput={params => (
+
+ )}
+ />
+
+
+
+ Price
+
+
+
+
+ Highest
+ {
+ setFilterPrice(PriceFilter.highest);
+ setFilterDate(null);
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+
+ Lowest
+ {
+ setFilterPrice(PriceFilter.lowest);
+ setFilterDate(null);
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+
+
+ Prices In
+
+
+
+
+
+ QORT
+
+
+ {
+
+ if (coinToUse !== CoinFilter.qort) {
+ dispatch(setPreferredCoin(CoinFilter.qort))
+
+ }
+
+
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+ {storeToUse?.foreignCoins?.ARRR && storeToUse?.supportedCoins?.includes('ARRR') && (
+
+
+ ARRR
+
+
+ {
+ if (coinToUse !== CoinFilter.arrr) {
+ dispatch(setPreferredCoin(CoinFilter.arrr))
+ }
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+ )}
+
+
+
+ Date Product Added
+
+
+
+
+ Most Recent
+ {
+ setFilterDate(DateFilter.newest);
+ setFilterPrice(null);
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+
+ Oldest
+ {
+ setFilterDate(DateFilter.oldest);
+ setFilterPrice(null);
+ }}
+ inputProps={{ "aria-label": "controlled" }}
+ />
+
+
+ {coinToUse === CoinFilter.arrr && exchangeRate && (
+
+
+
+ 1 QORT = {exchangeRate} ARRR
+
+
+
+ {`Rate calculated by recent trade portal trades`}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {
+ setOpenStoreDetails(true);
+ }}
+ >
+ {username === user?.name
+ ? currentStore?.title
+ : currentViewedStore?.title}
+
+ {averageRatingLoader ? (
+
+ ) : (
+ {
+ setOpenStoreReviews(true);
+ }}
+ >
+ {!averageStoreRating ? (
+ "No reviews yet"
+ ) : (
+
+ )}
+
+ )}
+
+ Accepted Coins
+
+
+ {storeToUse?.foreignCoins?.ARRR && storeToUse?.supportedCoins?.includes('ARRR') && (
+
+ )}
+
+
+
+
+
+ {username === user?.name ? (
+
+ {
+ dispatch(toggleEditStoreModal(true));
+ }}
+ >
+ Edit Shop
+
+ {
+ navigate(`/product-manager/${store}`);
+ }}
+ >
+ Product Manager
+
+
+ ) : user?.name ? (
+ 0 ? true : false}
+ onClick={() => {
+ dispatch(setIsOpen(true));
+ }}
+ >
+
+ {totalCartQuantity > 0 && (
+ 0 ? true : false}
+ >
+ {totalCartQuantity}
+
+ )}
+
+ ) : null}
+
+
+ {filteredProducts.length > 0 && hasFetched ? (
+ filteredProducts.map((product: Product) => {
+ const storeId: string = currentStore?.id || "";
+ let productItem = product;
+ let hasHash = false;
+ const existingProduct =
+ catalogueHashMap[product?.catalogueId]?.products[product?.id];
+ if (existingProduct) {
+ productItem = existingProduct;
+ hasHash = true;
+ }
+ if (!hasHash) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+
+
+ );
+ }
+ })
+ ) : filteredProducts.length === 0 &&
+ username === user?.name &&
+ hasFetched ? (
+
+
+ You currently have no products! Add some in the Product Manager.
+
+
+ ) : (
+
+
+ There are currently no products for sale!
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/pages/Store/StoreCard/StoreCard.tsx b/src/pages/Store/StoreCard/StoreCard.tsx
new file mode 100644
index 0000000..e303290
--- /dev/null
+++ b/src/pages/Store/StoreCard/StoreCard.tsx
@@ -0,0 +1,158 @@
+import { FC, useEffect, useState } from "react";
+import {
+ AcceptedCoin,
+ AcceptedCoinsRow,
+ ExpandDescriptionIcon,
+ OpenStoreCard,
+ StoreCardDescription,
+ StoreCardImage,
+ StoreCardImageContainer,
+ StoreCardInfo,
+ StoreCardOwner,
+ StoreCardTitle,
+ StoresRow,
+ StyledStoreCard,
+ StyledTooltip,
+ YouOwnIcon,
+} from "../../StoreList/StoreList-styles";
+import ContextMenuResource from "../../../components/common/ContextMenu/ContextMenuResource";
+import { useNavigate } from "react-router-dom";
+import { useTheme } from "@mui/material";
+import { BriefcaseSVG } from "../../../assets/svgs/BriefcaseSVG";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ resetListProducts,
+ resetProducts,
+} from "../../../state/features/globalSlice";
+import { RootState } from "../../../state/store";
+import { clearViewedStoreDataContainer } from "../../../state/features/storeSlice";
+import QORT from "../../../assets/img/qort.png";
+import ARRR from "../../../assets/img/arrr.png";
+
+interface StoreCardProps {
+ storeTitle: string;
+ storeLogo: string;
+ storeDescription: string;
+ storeId: string;
+ storeOwner: string;
+ userName: string;
+ supportedCoins: string[];
+}
+
+export const StoreCard: FC = ({
+ storeTitle,
+ storeLogo,
+ storeDescription,
+ storeId,
+ storeOwner,
+ userName,
+ supportedCoins
+}) => {
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const theme = useTheme();
+
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ const currentViewedStore = useSelector(
+ (state: RootState) => state.store.currentViewedStore
+ );
+
+ const [isEllipsisActive, setIsEllipsisActive] = useState(false);
+ const [showCompleteStoreDescription, setShowCompleteStoreDescription] =
+ useState(false);
+
+ const handleStoreCardClick = (
+ e: React.MouseEvent
+ ) => {
+ if ((e.target as HTMLElement)?.id === `expand-icon-${storeId}`) {
+ return;
+ }
+ // When visiting a new story, reset the products and listProducts state to prevent the previous store's products from showing. We do this depending on whether the user is the store owner or not. We don't need to worry about them coming directly from a url link because the redux store will be empty in that case
+ if (userName === storeOwner && currentStore?.id !== storeId) {
+ dispatch(resetProducts());
+ dispatch(resetListProducts());
+ } else if (userName !== storeOwner && currentViewedStore?.id !== storeId) {
+ dispatch(resetProducts());
+ dispatch(clearViewedStoreDataContainer());
+ }
+ // Setting storeOwner and storeId into the url params which can then be used in the Store component
+ navigate(`/${storeOwner}/${storeId}`);
+ };
+
+ const limitCharFunc = (str: string, limit = 50) => {
+ return str.length > limit ? `${str.slice(0, limit)}...` : str;
+ };
+
+ useEffect(() => {
+ if (storeDescription.length >= 50) {
+ setIsEllipsisActive(true);
+ }
+ }, [storeDescription]);
+
+ return (
+
+
+
+
+
+ Open
+
+
+ {storeTitle}
+
+ {showCompleteStoreDescription
+ ? storeDescription
+ : limitCharFunc(storeDescription)}
+
+ {isEllipsisActive && (
+ ) => {
+ e.stopPropagation();
+ setShowCompleteStoreDescription(prevState => !prevState);
+ }}
+ showCompleteStoreDescription={
+ showCompleteStoreDescription ? true : false
+ }
+ />
+ )}
+
+
+
+ {supportedCoins?.includes('ARRR') && (
+
+ )}
+
+ {storeOwner}
+ {storeOwner === userName && (
+
+
+
+
+
+ )}
+
+
+
+ );
+};
diff --git a/src/pages/Store/StoreDetails/StoreDetails-styles.tsx b/src/pages/Store/StoreDetails/StoreDetails-styles.tsx
new file mode 100644
index 0000000..cb9e169
--- /dev/null
+++ b/src/pages/Store/StoreDetails/StoreDetails-styles.tsx
@@ -0,0 +1,116 @@
+import { styled } from "@mui/system";
+import { Box, Button } from "@mui/material";
+
+export const EmailUser = styled("a")(({ theme }) => ({
+ display: "flex",
+ gap: "5px",
+ alignItems: "center",
+ fontFamily: "Karla",
+ letterSpacing: "0px",
+ fontSize: "21px",
+}));
+
+export const CloseButtonRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ gap: 1,
+ justifyContent: "flex-end",
+}));
+
+export const CloseButton = styled(Button)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "15px",
+}));
+
+export const StoreTitleCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifySelf: "center",
+ width: "fit-content",
+ borderRadius: "8px",
+ padding: "10px 15px",
+ fontFamily: "Merriweather Sans",
+ fontWeight: 500,
+ fontSize: "20px",
+ color: theme.palette.text.primary,
+ gap: "10px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)"
+ : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
+ },
+}));
+
+export const Divider = styled(Box)(({ theme }) => ({
+ width: "100%",
+ height: "2px",
+ backgroundColor: theme.palette.text.primary,
+ padding: "0 10px",
+ divider: 0.7,
+}));
+
+export const CardRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ width: "100%",
+ fontFamily: "Karla",
+ fontWeight: 300,
+ fontSize: "20px",
+ letterSpacing: "0px",
+});
+
+export const IconsRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ gap: "5px",
+ opacity: 0.9,
+ fontSize: "22px",
+ fontWeight: "bold",
+ whiteSpace: "nowrap",
+});
+
+export const HeaderRow = styled(Box)(({ theme }) => ({
+ display: "grid",
+ gridTemplateColumns: "auto 1fr",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ width: "100%",
+ padding: "10px 15px",
+ fontFamily: "Merriweather Sans, sans-serif",
+ fontSize: "23px",
+ color: theme.palette.text.primary,
+}));
+
+export const StoreLogo = styled("img")({
+ width: "90px",
+ height: "90px",
+ borderRadius: "3px",
+ objectFit: "contain",
+});
+
+export const StoreTitle = styled(Box)({
+ display: "flex",
+ justifySelf: "center",
+ userSelect: "none",
+});
+
+export const CardDetailsContainer = styled(Box)({
+ display: "flex",
+ flexDirection: "column",
+ flexGrow: 1,
+});
+
+export const StoreDescription = styled(Box)({
+ display: "flex",
+ flexDirection: "column",
+ maxWidth: "60%",
+});
+
+export const CurrencyRow = styled(Box)({
+ display: "flex",
+ alignItems: "center",
+ gap: "10px",
+});
diff --git a/src/pages/Store/StoreDetails/StoreDetails.tsx b/src/pages/Store/StoreDetails/StoreDetails.tsx
new file mode 100644
index 0000000..78c60b3
--- /dev/null
+++ b/src/pages/Store/StoreDetails/StoreDetails.tsx
@@ -0,0 +1,171 @@
+import { FC } from "react";
+import moment from "moment";
+import EmailIcon from "@mui/icons-material/Email";
+import {
+ CardRow,
+ Divider,
+ EmailUser,
+ StoreLogo,
+ IconsRow,
+ HeaderRow,
+ CardDetailsContainer,
+ StoreTitle,
+ StoreDescription,
+ CurrencyRow,
+} from "./StoreDetails-styles";
+import { OwnerSVG } from "../../../assets/svgs/OwnerSVG";
+import { Box, useTheme } from "@mui/material";
+import { CalendarSVG } from "../../../assets/svgs/CalendarSVG";
+import { CloseIconModal } from "../StoreReviews/StoreReviews-styles";
+import { DescriptionSVG } from "../../../assets/svgs/DescriptionSVG";
+import { LocationSVG } from "../../../assets/svgs/LocationSVG";
+import { ShippingSVG } from "../../../assets/svgs/ShippingSVG";
+import { CurrencySVG } from "../../../assets/svgs/CurrencySVG";
+import { QortalSVG } from "../../../assets/svgs/QortalSVG";
+import { ARRRSVG } from "../../../assets/svgs/ARRRSVG";
+import { ForeignCoins } from "../../../components/modals/CreateStoreModal";
+
+interface StoreDetailsProps {
+ storeTitle: string;
+ storeImage: string;
+ storeOwner: string;
+ storeDescription: string;
+ dateCreated: number;
+ location: string;
+ shipsTo: string;
+ setOpenStoreDetails: (open: boolean) => void;
+ supportedCoins: string[];
+ foreignCoins: ForeignCoins
+}
+
+export const StoreDetails: FC = ({
+ storeTitle,
+ storeImage,
+ storeDescription,
+ storeOwner,
+ dateCreated,
+ location,
+ shipsTo,
+ setOpenStoreDetails,
+ supportedCoins,
+ foreignCoins
+}) => {
+ const theme = useTheme();
+ return (
+ <>
+
+
+ {storeTitle}
+ setOpenStoreDetails(false)}
+ color={theme.palette.text.primary}
+ height={"24"}
+ width={"24"}
+ />
+
+
+
+
+
+
+
+ Store Owner
+
+ {storeOwner}
+
+
+
+
+ Store Description
+
+ {storeDescription}
+
+
+
+
+ Date Created
+
+ {moment(dateCreated).format("llll")}
+
+
+
+
+ Shop Location
+
+ {location}
+
+
+
+
+ Ships To
+
+ {shipsTo}
+
+
+
+
+ Accepted Coins
+
+
+
+ {foreignCoins?.ARRR && (
+
+ )}
+
+
+
+
+
+
+ Email
+
+
+ Message {storeOwner} on Q-Mail
+
+
+
+
+ >
+ );
+};
diff --git a/src/pages/Store/StoreReviews/AddReview/AddReview-styles.tsx b/src/pages/Store/StoreReviews/AddReview/AddReview-styles.tsx
new file mode 100644
index 0000000..32eae30
--- /dev/null
+++ b/src/pages/Store/StoreReviews/AddReview/AddReview-styles.tsx
@@ -0,0 +1,44 @@
+import { styled } from "@mui/system";
+import { Box, TextareaAutosize } from "@mui/material";
+
+export const AddReviewHeader = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center"
+}));
+
+export const AddReviewContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ padding: "25px",
+ justifyContent: "flex-start",
+ flexGrow: 1,
+ width: "100%"
+}));
+
+export const AddReviewDescription = styled(TextareaAutosize)(({ theme }) => ({
+ width: "100%",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ fontWeight: 300,
+ lineHeight: "1.5",
+ padding: "12px",
+ borderRadius: "12px 12px 0 12px",
+ color: theme.palette.text.primary,
+ background: theme.palette.background.default,
+ resize: "none",
+ "& placeholder": {
+ color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
+ fontFamily: "Karla",
+ fontSize: "18px",
+ letterSpacing: "0px"
+ },
+ border: `1px solid ${theme.palette.background.paper}`,
+ "&:hover": {
+ borderColor: theme.palette.secondary.main
+ },
+ "&:focus": {
+ borderColor: theme.palette.secondary.main
+ }
+}));
diff --git a/src/pages/Store/StoreReviews/AddReview/AddReview.tsx b/src/pages/Store/StoreReviews/AddReview/AddReview.tsx
new file mode 100644
index 0000000..cad62a0
--- /dev/null
+++ b/src/pages/Store/StoreReviews/AddReview/AddReview.tsx
@@ -0,0 +1,265 @@
+import { FC, useState } from "react";
+import {
+ AddReviewContainer,
+ AddReviewDescription,
+ AddReviewHeader
+} from "./AddReview-styles";
+import { Rating } from "@mui/material";
+import {
+ CreateButton,
+ CustomInputField
+} from "../../../../components/modals/CreateStoreModal-styles";
+import {
+ CloseButton,
+ CloseButtonRow,
+ Divider,
+ StoreTitle
+} from "../../StoreDetails/StoreDetails-styles";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState, store } from "../../../../state/store";
+import ShortUniqueId from "short-unique-id";
+import { setNotification } from "../../../../state/features/notificationsSlice";
+import { objectToBase64 } from "../../../../utils/toBase64";
+import {
+ addToHashMapStoreReviews,
+ addToReviews
+} from "../../../../state/features/storeSlice";
+import { REVIEW_BASE, STORE_BASE } from "../../../../constants/identifiers";
+
+/* Reviews notes
+ Prevent them from adding a review to their own store
+ Filter their own review
+ Make sure user has at least one store order before being able to leave a review
+ Get first 100 reviews for the average (without metadata)
+*/
+
+interface AddReviewProps {
+ storeId: string;
+ storeTitle: string;
+ setOpenLeaveReview: (open: boolean) => void;
+}
+
+const uid = new ShortUniqueId({ length: 10 });
+
+export const AddReview: FC = ({
+ storeId,
+ storeTitle,
+ setOpenLeaveReview
+}) => {
+ const dispatch = useDispatch();
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const [rating, setRating] = useState(null);
+ const [reviewTitle, setReviewTitle] = useState("");
+ const [reviewDescription, setReviewDescription] = useState("");
+
+ // Verify if review identifier already exists
+
+ const verifyIfReviewIdExists = async (
+ username: string,
+ identifier: string
+ ) => {
+ try {
+ const response = await qortalRequest({
+ action: "LIST_QDN_RESOURCES",
+ service: "DOCUMENT",
+ name: username,
+ identifier: identifier,
+ includeMetadata: true,
+ limit: 1
+ });
+ if (response?.resources?.length > 0) {
+ return true;
+ }
+ return false;
+ } catch (err) {
+ console.log(err);
+ return false;
+ }
+ };
+
+ // Add review to QDN
+ const addReviewFunc = async () => {
+ let address: string = "";
+ let name: string = "";
+ let errorMsg = "";
+
+ address = user?.address || "";
+ name = user?.name || "";
+
+ // Validation
+ if (!address) {
+ errorMsg = "Cannot send: your address isn't available";
+ }
+
+ if (!name) {
+ errorMsg = "Cannot send a message without a access to your name";
+ }
+
+ if (!storeId) {
+ errorMsg = "Cannot add a review without having a store ID";
+ }
+
+ if (!rating || !reviewTitle || !reviewDescription) {
+ errorMsg = "Cannot add a review without a rating, title, and description";
+ }
+
+ if (errorMsg) {
+ dispatch(
+ setNotification({
+ msg: errorMsg,
+ alertType: "error"
+ })
+ );
+ throw new Error(errorMsg);
+ }
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+ const uidGenerator = uid();
+ // Create identifier for the review
+ const reviewId = `${REVIEW_BASE}-${shortStoreId}-${uidGenerator}-${
+ Number(rating) * 10
+ }`;
+ // Check if review identifier already exists
+ const doesExist = await verifyIfReviewIdExists(name, reviewId);
+ if (doesExist) {
+ throw new Error(
+ "The review identifier already exists! Try changing your review's title"
+ );
+ }
+
+ // Resource raw data
+ const reviewObj = {
+ title: reviewTitle,
+ description: reviewDescription,
+ rating: rating,
+ created: Date.now()
+ };
+
+ const reviewToBase64 = await objectToBase64(reviewObj);
+ try {
+ // Publish Review to QDN
+ const resourceResponse = await qortalRequest({
+ action: "PUBLISH_QDN_RESOURCE",
+ name: name,
+ service: "DOCUMENT",
+ identifier: reviewId,
+ data64: reviewToBase64,
+ filename: "review.json",
+ // Resource metadata down here
+ title: reviewTitle.slice(0, 60),
+ description: reviewDescription.slice(0, 150)
+ });
+ setOpenLeaveReview(false);
+ dispatch(
+ addToReviews({
+ id: reviewId,
+ name: name,
+ created: Date.now(),
+ updated: Date.now(),
+ title: reviewTitle.slice(0, 60),
+ description: reviewDescription.slice(0, 150),
+ rating: rating
+ })
+ );
+ dispatch(
+ addToHashMapStoreReviews({
+ id: reviewId,
+ name: name,
+ created: Date.now(),
+ updated: Date.now(),
+ title: reviewTitle,
+ description: reviewDescription,
+ rating: rating,
+ isValid: true
+ })
+ );
+ dispatch(
+ setNotification({
+ alertType: "success",
+ msg: "Added Review Successfully!"
+ })
+ );
+ } catch (error: any) {
+ let notificationObj: any = null;
+ if (typeof error === "string") {
+ notificationObj = {
+ msg: error || "Failed to create review",
+ alertType: "error"
+ };
+ } else if (typeof error?.error === "string") {
+ notificationObj = {
+ msg: error?.error || "Failed to create review",
+ alertType: "error"
+ };
+ } else {
+ notificationObj = {
+ msg: error?.message || "Failed to create review",
+ alertType: "error"
+ };
+ }
+ if (!notificationObj) return;
+ dispatch(setNotification(notificationObj));
+ if (error instanceof Error) {
+ throw new Error(error.message);
+ } else {
+ throw new Error("An unknown error occurred");
+ }
+ }
+ };
+
+ return (
+
+
+ {`Leave a review for ${storeTitle}`}
+
+
+
+
+ , newValue: number | null) => {
+ setRating(newValue);
+ }}
+ precision={0.5}
+ value={rating}
+ style={{ fontSize: "55px" }}
+ />
+ setReviewTitle(e.target.value as string)}
+ inputProps={{ maxLength: 180 }}
+ required
+ style={{ width: "100%" }}
+ />
+ setReviewDescription(e.target.value as string)}
+ required
+ />
+
+
+ {
+ setOpenLeaveReview(false);
+ }}
+ >
+ Close
+
+
+ Add Review
+
+
+
+
+ );
+};
diff --git a/src/pages/Store/StoreReviews/StoreReviewCard.tsx b/src/pages/Store/StoreReviews/StoreReviewCard.tsx
new file mode 100644
index 0000000..98d27d9
--- /dev/null
+++ b/src/pages/Store/StoreReviews/StoreReviewCard.tsx
@@ -0,0 +1,82 @@
+import { useState, FC, useEffect } from "react";
+import { Rating } from "@mui/material";
+import {
+ ReviewContainer,
+ ReviewDateFont,
+ ReviewDescriptionFont,
+ ReviewHeader,
+ ReviewTitleFont,
+ ReviewTitleRow,
+ ReviewUsernameFont
+} from "./StoreReviews-styles";
+import moment from "moment";
+import { StoreReview } from "../../../state/features/storeSlice";
+import { useFetchStoreReviews } from "../../../hooks/useFetchStoreReviews";
+import { useSelector } from "react-redux";
+import { RootState } from "../../../state/store";
+
+interface StoreReviewCardProps {
+ review: StoreReview;
+}
+
+export const StoreReviewCard: FC = ({ review }) => {
+ const [showCompleteReview, setShowCompleteReview] = useState(false);
+ const [fullStoreTitle, setFullStoreTitle] = useState("");
+ const [fullStoreDescription, setFullStoreDescription] = useState("");
+
+ const hashMapStoreReviews = useSelector(
+ (state: RootState) => state.store.hashMapStoreReviews
+ );
+
+ const { created, name, title, rating, description } = review;
+
+ const { getReview, checkAndUpdateResource } = useFetchStoreReviews();
+
+ const handleFetchReviewRawData = async () => {
+ try {
+ if (review.name && review.id) {
+ const res = checkAndUpdateResource({
+ id: review?.id,
+ updated: review?.updated
+ });
+ if (res) {
+ getReview(review?.name, review?.id, review);
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ useEffect(() => {
+ Object.keys(hashMapStoreReviews).find((key) => {
+ if (key === review.id) {
+ setShowCompleteReview(true);
+ setFullStoreTitle(hashMapStoreReviews[key].title);
+ setFullStoreDescription(hashMapStoreReviews[key].description);
+ }
+ });
+ }, [hashMapStoreReviews]);
+
+ return (
+ {
+ setShowCompleteReview(true);
+ handleFetchReviewRawData();
+ }}
+ showCompleteReview={showCompleteReview ? true : false}
+ >
+
+ {name}
+
+ {fullStoreTitle || title}
+
+
+ {moment(created).format("llll")}
+
+
+ {fullStoreDescription || description}
+
+
+ );
+};
diff --git a/src/pages/Store/StoreReviews/StoreReviews-styles.tsx b/src/pages/Store/StoreReviews/StoreReviews-styles.tsx
new file mode 100644
index 0000000..6ef4311
--- /dev/null
+++ b/src/pages/Store/StoreReviews/StoreReviews-styles.tsx
@@ -0,0 +1,176 @@
+import { styled } from "@mui/system";
+import { Box, Button, Typography } from "@mui/material";
+import { TimesSVG } from "../../../assets/svgs/TimesSVG";
+
+interface StoreReviewsProps {
+ showCompleteReview: boolean;
+}
+
+export const AddReviewButton = styled(Button)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ padding: "4px 15px",
+ gap: "10px",
+ fontFamily: "Livvic",
+ fontSize: "16px",
+ width: "auto",
+ color: theme.palette.mode === "dark" ? "#000000" : "#ffffff",
+ backgroundColor: theme.palette.mode === "dark" ? "#ffffff" : "#000000",
+ border: "none",
+ borderRadius: "5px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor: theme.palette.mode === "dark" ? "#ffffff" : "#000000",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)"
+ : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;"
+ }
+}));
+
+export const AverageReviewContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ maxHeight: "200px",
+ width: "100%"
+}));
+
+export const ReviewsFont = styled(Typography)(({ theme }) => ({
+ textAlign: "center",
+ fontFamily: "Raleway",
+ fontSize: "19px",
+ fontWeight: 600,
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ marginBottom: "5px"
+}));
+
+export const AverageReviewNumber = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "60px",
+ fontWeight: 600,
+ letterSpacing: "2px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ lineHeight: "35px",
+ marginBottom: "25px"
+}));
+
+export const TotalReviewsFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "15px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ opacity: 0.8
+}));
+
+export const ReviewContainer = styled(Box)(
+ ({ theme, showCompleteReview }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-start",
+ justifyContent: "flex-start",
+ gap: "10px",
+ padding: "5px",
+ borderRadius: "5px",
+ width: "100%",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: showCompleteReview ? "auto" : "pointer",
+ backgroundColor: showCompleteReview
+ ? "transparent"
+ : theme.palette.mode === "light"
+ ? "#d3d3d3ac"
+ : "#aeabab1e"
+ }
+ })
+);
+
+export const ReviewHeader = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ gap: "1px"
+}));
+
+export const ReviewTitleRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ gap: "15px"
+}));
+
+export const ReviewUsernameFont = styled(Box)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "16px",
+ fontWeight: 400,
+ color: theme.palette.text.primary
+}));
+
+export const ReviewTitleFont = styled(Box)(({ theme }) => ({
+ fontFamily: "Merriweather Sans, sans-serif",
+ fontSize: "18px",
+ fontWeight: 400,
+ letterSpacing: "0.5px",
+ color: theme.palette.text.primary
+}));
+
+export const ReviewDateFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "15px",
+ fontWeight: 300,
+ letterSpacing: "0px",
+ color: theme.palette.text.primary,
+ opacity: 0.8
+}));
+
+export const ReviewDescriptionFont = styled(Box)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "17px",
+ fontWeight: 300,
+ letterSpacing: "0px",
+ color: theme.palette.text.primary
+}));
+
+export const StoreReviewsContainer = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ flexGrow: 1,
+ gap: "30px",
+ overflowY: "auto",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar": {
+ width: "8px",
+ height: "10px",
+ backgroundColor: "transparent"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent"
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f"
+ }
+}));
+
+export const CloseIconModal = styled(TimesSVG)(({ theme }) => ({
+ position: "absolute",
+ top: "15px",
+ right: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ transform: "scale(1.1)"
+ }
+}));
diff --git a/src/pages/Store/StoreReviews/StoreReviews.tsx b/src/pages/Store/StoreReviews/StoreReviews.tsx
new file mode 100644
index 0000000..277895e
--- /dev/null
+++ b/src/pages/Store/StoreReviews/StoreReviews.tsx
@@ -0,0 +1,253 @@
+import { FC, useState, useCallback, useEffect } from "react";
+import { CircularProgress, Grid, Rating, useTheme } from "@mui/material";
+import {
+ CardDetailsContainer,
+ Divider,
+ HeaderRow,
+ StoreLogo,
+ StoreTitle
+} from "../StoreDetails/StoreDetails-styles";
+import { StoreTitleCol } from "../Store/Store-styles";
+import { StarSVG } from "../../../assets/svgs/StarSVG";
+import {
+ AddReviewButton,
+ AverageReviewContainer,
+ AverageReviewNumber,
+ CloseIconModal,
+ ReviewsFont,
+ StoreReviewsContainer,
+ TotalReviewsFont
+} from "./StoreReviews-styles";
+import { StoreReviewCard } from "./StoreReviewCard";
+import { ReusableModal } from "../../../components/modals/ReusableModal";
+import { AddReview } from "./AddReview/AddReview";
+import { useDispatch, useSelector } from "react-redux";
+import LazyLoad from "../../../components/common/LazyLoad";
+import { StoreReview, upsertReviews } from "../../../state/features/storeSlice";
+import { RootState } from "../../../state/store";
+import {
+ ORDER_BASE,
+ REVIEW_BASE,
+ STORE_BASE
+} from "../../../constants/identifiers";
+
+interface StoreReviewsProps {
+ storeId: string;
+ storeTitle: string;
+ storeImage: string;
+ averageStoreRating: number | null;
+ setOpenStoreReviews: (open: boolean) => void;
+}
+
+export const StoreReviews: FC = ({
+ storeId,
+ storeTitle,
+ storeImage,
+ averageStoreRating,
+ setOpenStoreReviews
+}) => {
+ const theme = useTheme();
+ const dispatch = useDispatch();
+ const storeReviews = useSelector(
+ (state: RootState) => state.store.storeReviews
+ );
+ const storeOwner = useSelector((state: RootState) => state.store.storeOwner);
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const [openLeaveReview, setOpenLeaveReview] = useState(false);
+ const [userHasStoreOrder, setUserHasStoreOrder] = useState(false);
+ const [hasFetched, setHasFetched] = useState(false);
+
+ // Determine whether user can leave a review
+ const doesUserHaveOrderFunc = async () => {
+ if (!user?.name) return;
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+
+ try {
+ const query = `${ORDER_BASE}-${shortStoreId}`;
+ const url = `/arbitrary/resources/search?service=DOCUMENT_PRIVATE&name=${user?.name}&exactmatchnames=true&query=${query}&limit=1&includemetadata=false&mode=ALL&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ const responseData = await response.json();
+ if (responseData.length > 0) {
+ setUserHasStoreOrder(true);
+ return;
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ // Fetch all the store review resources from QDN
+ const getStoreReviews = useCallback(async () => {
+ if (!storeId) return;
+ try {
+ const offset = storeReviews.length;
+ const parts = storeId.split(`${STORE_BASE}-`);
+ const shortStoreId = parts[1];
+ const query = `${REVIEW_BASE}-${shortStoreId}`;
+ // Since it the url includes /resources, you know you're fetching the resources and not the raw data
+ const url = `/arbitrary/resources/search?service=DOCUMENT&query=${query}&limit=10&includemetadata=true&mode=ALL&offset=${offset}&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ const responseData = await response.json();
+ // Modify resource into data that is more easily used on the front end
+ const structuredReviewData = responseData.map(
+ (review: any): StoreReview => {
+ const splitIdentifier = review.identifier.split("-");
+ return {
+ id: review?.identifier,
+ name: review?.name,
+ created: review?.created,
+ updated: review?.updated,
+ title: review?.metadata?.title,
+ description: review?.metadata?.description,
+ rating: Number(splitIdentifier[splitIdentifier.length - 1]) / 10
+ };
+ }
+ );
+ setHasFetched(true);
+
+ // Filter out duplicates by checking if the review id already exists in storeReviews in global redux store
+ const copiedStoreReviews: StoreReview[] = [...storeReviews];
+
+ structuredReviewData.forEach((review: StoreReview) => {
+ const index = storeReviews.findIndex(
+ (storeReview: StoreReview) => storeReview.id === review.id
+ );
+ if (index !== -1) {
+ copiedStoreReviews[index] = review;
+ } else {
+ copiedStoreReviews.push(review);
+ }
+ });
+
+ dispatch(upsertReviews(copiedStoreReviews));
+ } catch (error) {
+ console.error(error);
+ }
+ }, [storeReviews, storeId]);
+
+ // Pass this function down to lazy loader
+ const handleGetReviews = useCallback(async () => {
+ await getStoreReviews();
+ }, [getStoreReviews]);
+
+ useEffect(() => {
+ if (user?.name) {
+ doesUserHaveOrderFunc();
+ }
+ }, [user]);
+ return (
+ <>
+
+
+
+ {storeTitle}
+ {userHasStoreOrder ? (
+ setOpenLeaveReview(true)}>
+ {" "}
+ Add Review
+
+ ) : (
+
+ You must have an order with this shop before being able to leave a
+ review
+
+ )}
+
+ setOpenStoreReviews(false)}
+ color={theme.palette.text.primary}
+ height={"26"}
+ width={"26"}
+ />
+
+
+
+
+ {averageStoreRating && (
+
+
+ Average Review
+
+ {averageStoreRating || null}
+
+
+ {`${storeReviews.length} review${
+ storeReviews.length === 1 ? "" : "s"
+ }`}
+
+
+ )}
+
+
+ {storeReviews.length === 0 && hasFetched ? (
+ No reviews yet
+ ) : (
+ storeReviews
+ .filter((review: StoreReview) => {
+ return review.name !== storeOwner;
+ })
+ .map((review: StoreReview) => {
+ return ;
+ })
+ )}
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/pages/StoreList/StoreList-styles.tsx b/src/pages/StoreList/StoreList-styles.tsx
new file mode 100644
index 0000000..717c594
--- /dev/null
+++ b/src/pages/StoreList/StoreList-styles.tsx
@@ -0,0 +1,338 @@
+import { styled } from "@mui/system";
+
+interface StoreListProps {
+ showCompleteStoreDescription?: boolean;
+}
+
+import {
+ Box,
+ Grid,
+ Typography,
+ Checkbox,
+ IconButton,
+ Tooltip,
+} from "@mui/material";
+import { DoubleArrowDownSVG } from "../../assets/svgs/DoubleArrowDownSVG";
+
+export const StoresContainer = styled(Grid)(({ theme }) => ({
+ position: "relative",
+ padding: "10px 55px 30px 55px",
+ flexDirection: "column",
+}));
+
+export const WelcomeRow = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ width: "100%",
+ gap: "10px",
+ padding: "0 15px 45px 0px",
+ [theme.breakpoints.down("sm")]: {
+ flexDirection: "column",
+ },
+}));
+
+export const WelcomeFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Cairo",
+ fontSize: "40px",
+ userSelect: "none",
+ color: theme.palette.text.primary,
+}));
+
+export const WelcomeSubFont = styled(Typography)(({ theme }) => ({
+ fontFamily: "Raleway",
+ fontSize: "24px",
+ userSelect: "none",
+ color: theme.palette.text.primary,
+ opacity: 0.8,
+}));
+
+export const WelcomeCol = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-start",
+}));
+
+export const StoresRow = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ width: "auto",
+ position: "relative",
+ "@media (max-width: 450px)": {
+ width: "100%",
+ },
+}));
+
+export const StyledStoreCard = styled(Grid)(
+ ({ theme, showCompleteStoreDescription }) => ({
+ boxSizing: "border-box",
+ position: "relative",
+ display: "flex",
+ flexFlow: "column",
+ width: "fit-content",
+ minWidth: "100%",
+ maxWidth: "100%",
+ height: "100%",
+ maxHeight: showCompleteStoreDescription ? "100%" : "500px",
+ backgroundColor: "transparent",
+ borderRadius: "8px",
+ paddingBottom: "60px",
+ justifyContent: "space-between",
+ border:
+ theme.palette.mode === "dark"
+ ? "1px solid #e2e2e20d"
+ : "1px solid #1b1a1a14",
+ transition: "all 0.3s ease-in-out 0s",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)"
+ : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
+ "& div div": {
+ visibility: "visible",
+ },
+ },
+ })
+);
+
+export const StoreCardInfo = styled(Grid)(({ theme }) => ({
+ position: "relative",
+ display: "flex",
+ flexDirection: "column",
+ gap: "5px",
+ padding: "0 20px 10px 15px",
+}));
+
+export const StoreCardImageContainer = styled(Box)(({ theme }) => ({}));
+
+export const OpenStoreCard = styled(Box)(({ theme }) => ({
+ position: "absolute",
+ visibility: "hidden",
+ top: "5px",
+ right: "10px",
+ backgroundColor: theme.palette.mode === "dark" ? "#aaa1a1e8" : "#f0f0f0",
+ color: theme.palette.text.primary,
+ fontSize: "18px",
+ fontFamily: "Karla",
+ letterSpacing: "0px",
+ fontWeight: 400,
+ padding: "2px 8px",
+ userSelect: "none",
+ borderRadius: "20px",
+ transition: "all 0.2s ease-in",
+}));
+
+export const StoreCardImage = styled("img")(({ theme }) => ({
+ width: "100%",
+ objectFit: "contain",
+ maxHeight: "250px",
+ height: "250px",
+ borderRadius: "10px",
+}));
+
+export const StoreCardTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans, sans-serif",
+ fontWeight: 400,
+ fontSize: "24px",
+ letterSpacing: "0.4px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const StoreCardDescription = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "20px",
+ fontWeight: 300,
+ letterSpacing: "0px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ overflowY: "auto",
+ "&::-webkit-scrollbar-track": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-track:hover": {
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar": {
+ width: "8px",
+ height: "10px",
+ backgroundColor: "transparent",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#414763",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#40455f",
+ },
+}));
+
+export const AcceptedCoinsRow = styled(Grid)({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ gap: "5px",
+ width: "100%",
+ position: "absolute",
+ bottom: "5px",
+ left: "10px",
+});
+
+export const AcceptedCoin = styled("img")({
+ width: "30px",
+ height: "30px",
+ objectFit: "contain",
+ userSelect: "none",
+});
+
+export const StoreCardOwner = styled(Typography)(({ theme }) => ({
+ fontFamily: "Livvic",
+ color: theme.palette.text.primary,
+ fontSize: "15px",
+ position: "absolute",
+ bottom: "5px",
+ right: "10px",
+ maxWidth: "180px",
+ userSelect: "none",
+}));
+
+export const StyledTooltip = styled(Tooltip)(({ theme }) => ({
+ "& .MuiTooltip-tooltip": {
+ fontFamily: "Karla",
+ fontSize: "15px",
+ letterSpacing: "0px",
+ fontWeight: 300,
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.background.paper,
+ zIndex: 10,
+ },
+}));
+
+export const YouOwnIcon = styled(IconButton)(({ theme }) => ({
+ height: "40px",
+ width: "40px",
+ position: "absolute",
+ bottom: "70px",
+ right: "0px",
+ backgroundColor: "transparent",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ backgroundColor:
+ theme.palette.mode === "dark"
+ ? "rgba(255, 255, 255, 0.08);"
+ : "rgba(255, 255, 255, 0.06);",
+ },
+}));
+
+export const MyStoresRow = styled(Grid)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "flex-end",
+ width: "100%",
+}));
+
+export const MyStoresCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ flexDirection: "row",
+ WebkitBoxAlign: "center",
+ alignItems: "center",
+ width: "auto",
+ borderRadius: "4px",
+ backgroundColor: theme.palette.background.paper,
+ padding: "6px 10px",
+ fontFamily: "Raleway",
+ fontSize: "15px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+ gap: "8px",
+}));
+
+export const MyStoresCheckbox = styled(Checkbox)(({ theme }) => ({
+ color: "#c0d4ff",
+ padding: 0,
+ "&.Mui-checked": {
+ color: "#6596ff",
+ },
+}));
+
+export const ExpandDescriptionIcon = styled(DoubleArrowDownSVG)(
+ ({ theme, showCompleteStoreDescription }) => ({
+ position: "absolute",
+ top: "30px",
+ right: "5px",
+ zIndex: 10,
+ transition: "all 0.3s ease-in-out",
+ "& svg": {
+ transform: showCompleteStoreDescription
+ ? "rotate(180deg)"
+ : "rotate(0deg)",
+ },
+ "&:hover": {
+ cursor: "pointer",
+ transform: showCompleteStoreDescription
+ ? "translateY(-2px)"
+ : "translateY(2px)",
+ },
+ })
+);
+
+export const LogoRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "row",
+ gap: "5px",
+}));
+
+export const QShopLogo = styled("img")(({ theme }) => ({
+ width: "200px",
+ height: "100%",
+ objectFit: "contain",
+ userSelect: "none",
+}));
+
+export const ExchangeRateCard = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ flexDirection: "column",
+ gap: "10px",
+ padding: "10px 15px",
+ width: "100%",
+ height: "200px",
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: "10px",
+ marginTop: "10px",
+}));
+
+export const ExchangeRateRow = styled(Box)(({ theme }) => ({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ width: "100%",
+}));
+
+export const ExchangeRateTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Merriweather Sans, sans-serif",
+ fontSize: "20px",
+ fontWeight: 400,
+ letterSpacing: "0.3px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
+
+export const ExchangeRateSubTitle = styled(Typography)(({ theme }) => ({
+ fontFamily: "Karla",
+ fontSize: "17px",
+ fontWeight: 300,
+ letterSpacing: "0px",
+ color: theme.palette.text.primary,
+ userSelect: "none",
+}));
diff --git a/src/pages/StoreList/StoreList.tsx b/src/pages/StoreList/StoreList.tsx
new file mode 100644
index 0000000..97bbc30
--- /dev/null
+++ b/src/pages/StoreList/StoreList.tsx
@@ -0,0 +1,223 @@
+import { useState, useCallback, useMemo } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../../state/store";
+import LazyLoad from "../../components/common/LazyLoad";
+import { Store, upsertStores } from "../../state/features/storeSlice";
+import { useFetchStores } from "../../hooks/useFetchStores";
+import {
+ StoresContainer,
+ MyStoresCard,
+ MyStoresCheckbox,
+ WelcomeRow,
+ WelcomeFont,
+ WelcomeSubFont,
+ WelcomeCol,
+ QShopLogo,
+ LogoRow,
+ StoresRow,
+} from "./StoreList-styles";
+import { Grid, Skeleton, useTheme } from "@mui/material";
+import { StoreCard } from "../Store/StoreCard/StoreCard";
+import QShopLogoLight from "../../assets/img/QShopLogoLight.webp";
+import QShopLogoDark from "../../assets/img/QShopLogo.webp";
+import DefaultStoreImage from "../../assets/img/Q-AppsLogo.webp";
+import { STORE_BASE } from "../../constants/identifiers";
+
+export const StoreList = () => {
+ const dispatch = useDispatch();
+ const theme = useTheme();
+
+ const user = useSelector((state: RootState) => state.auth.user);
+
+ const [filterUserStores, setFilterUserStores] = useState(false);
+
+ // TODO: Need skeleton at first while the data is being fetched
+ // Will rerender and replace if the hashmap wasn't found initially
+ const hashMapStores = useSelector(
+ (state: RootState) => state.store.hashMapStores
+ );
+
+ // Fetch My Stores from Redux
+ const myStores = useSelector((state: RootState) => state.store.myStores);
+ const stores = useSelector((state: RootState) => state.store.stores);
+
+ const { getStore, checkAndUpdateResource } = useFetchStores();
+
+ const getUserStores = useCallback(async () => {
+ try {
+ const offset = stores.length;
+ const query = STORE_BASE;
+ // Fetch list of user stores' resources from Qortal blockchain
+ const url = `/arbitrary/resources/search?service=STORE&query=${query}&limit=20&mode=ALL&prefix=true&includemetadata=false&offset=${offset}&reverse=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
+ // Data returned from that endpoint of the API
+ // tags, category, categoryName are not being used at the moment
+ const structureData = responseData.map((storeItem: any): Store => {
+ return {
+ title: storeItem?.metadata?.title,
+ category: storeItem?.metadata?.category,
+ categoryName: storeItem?.metadata?.categoryName,
+ tags: storeItem?.metadata?.tags || [],
+ description: storeItem?.metadata?.description,
+ created: storeItem.created,
+ updated: storeItem.updated,
+ owner: storeItem.name,
+ id: storeItem.identifier,
+ };
+ });
+ // Add stores to localstate & guard against duplicates
+ const copiedStores: Store[] = [...stores];
+ structureData.forEach((storeItem: Store) => {
+ const index = stores.findIndex((p: Store) => p.id === storeItem.id);
+ if (index !== -1) {
+ copiedStores[index] = storeItem;
+ } else {
+ copiedStores.push(storeItem);
+ }
+ });
+ dispatch(upsertStores(copiedStores));
+ // Get the store raw data from getStore API Call only if the hashmapStore doesn't have the store or if the store is more recently updated than the existing store
+ for (const content of structureData) {
+ if (content.owner && content.id) {
+ const res = checkAndUpdateResource({
+ id: content.id,
+ updated: content.updated,
+ });
+ // If the store is not already inside the hashmap, fetch the store raw data. We wrap this function in a timeout util function because stores with errors will hang the app and take a long time to load. With this, the max load time will be of 5 seconds for an error store.
+ if (res) {
+ getStore(content.owner, content.id, content);
+ }
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }, [stores]);
+
+ // Get all stores on mount or if user changes
+ const getStores = useCallback(async () => {
+ await getUserStores();
+ }, [getUserStores, user?.name]);
+
+ // Filter to show only the user's stores
+
+ const handleFilterUserStores = (
+ event: React.ChangeEvent
+ ) => {
+ setFilterUserStores(event.target.checked);
+ };
+
+ // Memoize the filtered stores to prevent rerenders
+ const filteredStores = useMemo(() => {
+ if (filterUserStores) {
+ return myStores;
+ } else {
+ return stores;
+ }
+ }, [filterUserStores, stores, myStores, user?.name]);
+
+ return (
+ <>
+
+
+
+
+
+ Welcome to Q-Shop 👋
+
+ Explore the latest of what the Qortal community has for sale.
+
+
+
+
+ {user && (
+
+
+ See My Stores
+
+ )}
+
+
+
+
+ {filteredStores.length > 0 &&
+ filteredStores
+ // Get rid of the Bester shop (test shop)
+ // .filter((store: Store) => store.owner !== "Bester")
+ .map((store: Store) => {
+ let storeItem = store;
+ let hasHash = false;
+ const existingStore = hashMapStores[store.id];
+
+ // Check in case hashmap data isn't there yet due to async API calls.
+ // If it's not there, component will rerender once it receives the metadata
+ if (existingStore) {
+ storeItem = existingStore;
+ hasHash = true;
+ }
+ const storeId = storeItem?.id || "";
+ const storeOwner = storeItem?.owner || "";
+ const storeTitle = storeItem?.title || "Invalid Shop";
+ const storeLogo = storeItem?.logo || DefaultStoreImage;
+ const storeDescription = storeItem?.description || "";
+ const supportedCoins = storeItem?.supportedCoins || ['QORT'];
+ if (!hasHash) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ })}
+
+
+
+
+ >
+ );
+};
diff --git a/src/state/features/authSlice.ts b/src/state/features/authSlice.ts
new file mode 100644
index 0000000..cd73e3a
--- /dev/null
+++ b/src/state/features/authSlice.ts
@@ -0,0 +1,27 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+
+
+interface AuthState {
+ user: {
+ address: string;
+ publicKey: string;
+ name?: string;
+ } | null;
+}
+const initialState: AuthState = {
+ user: null
+};
+
+export const authSlice = createSlice({
+ name: 'auth',
+ initialState,
+ reducers: {
+ addUser: (state, action) => {
+ state.user = action.payload;
+ },
+ },
+});
+
+export const { addUser } = authSlice.actions;
+
+export default authSlice.reducer;
\ No newline at end of file
diff --git a/src/state/features/cartSlice.ts b/src/state/features/cartSlice.ts
new file mode 100644
index 0000000..99f4947
--- /dev/null
+++ b/src/state/features/cartSlice.ts
@@ -0,0 +1,98 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { RootState } from "../store";
+
+interface CartState {
+ // hashMapCarts: Record
+ carts: Record;
+ isOpen: boolean;
+}
+const initialState: CartState = {
+ // hashMapCarts: {},
+ carts: {},
+ isOpen: false
+};
+
+export interface Order {
+ productId: string;
+ quantity: number;
+ catalogueId: string;
+}
+
+export interface Cart {
+ orders: Record;
+ lastUpdated: number;
+ storeId: string | null;
+ storeOwner: string | null;
+}
+
+export const cartSlice = createSlice({
+ name: "cart",
+ initialState,
+ reducers: {
+ setProductToCart: (state, action) => {
+ const { storeId, storeOwner, productId, catalogueId } = action.payload;
+
+ if (state.carts[storeId]) {
+ // If the cart already exists, check if the order exists.
+ if (state.carts[storeId].orders[productId]) {
+ // If an order already exists, increment its quantity.
+ state.carts[storeId].orders[productId].quantity += 1;
+ } else {
+ // If no order exists, create a new one with quantity 1.
+ state.carts[storeId].orders[productId] = {
+ quantity: 1,
+ productId,
+ catalogueId
+ };
+ }
+ } else {
+ // If the cart doesn't exist yet, create a new one with the order.
+ state.carts[storeId] = {
+ orders: {
+ [productId]: {
+ quantity: 1,
+ productId,
+ catalogueId
+ }
+ },
+ lastUpdated: Date.now(),
+ storeId,
+ storeOwner
+ };
+ }
+ },
+ setIsOpen: (state, action) => {
+ state.isOpen = action.payload;
+ },
+ addQuantityToCart: (state, action) => {
+ const { storeId, productId } = action.payload;
+ state.carts[storeId].orders[productId].quantity += 1;
+ },
+ subtractQuantityFromCart: (state, action) => {
+ const { storeId, productId } = action.payload;
+ state.carts[storeId].orders[productId].quantity -= 1;
+ if (state.carts[storeId].orders[productId].quantity === 0) {
+ delete state.carts[storeId].orders[productId];
+ }
+ },
+ removeProductFromCart: (state, action) => {
+ const { storeId, productId } = action.payload;
+ delete state.carts[storeId].orders[productId];
+ },
+ removeCartFromCarts: (state, action) => {
+ const { storeId } = action.payload;
+ delete state.carts[storeId];
+ }
+ }
+});
+
+export const {
+ setProductToCart,
+ setIsOpen,
+ addQuantityToCart,
+ subtractQuantityFromCart,
+ removeCartFromCarts,
+ removeProductFromCart
+} = cartSlice.actions;
+
+export default cartSlice.reducer;
diff --git a/src/state/features/globalSlice.ts b/src/state/features/globalSlice.ts
new file mode 100644
index 0000000..d8aa90f
--- /dev/null
+++ b/src/state/features/globalSlice.ts
@@ -0,0 +1,247 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { Product } from "./storeSlice";
+import { Order } from "./orderSlice";
+
+export interface ProductDataContainer {
+ created: number;
+ priceQort: number;
+ category: string;
+ catalogueId: string;
+ productId?: string;
+ user?: string;
+ status: string;
+}
+
+interface ForeignCoins {
+ [key: string]: string;
+}
+export interface CurrentStore {
+ created: number;
+ id: string;
+ title: string;
+ description: string;
+ owner: string;
+ shortStoreId: string;
+ logo?: string;
+ location?: string;
+ shipsTo?: string;
+ foreignCoins: ForeignCoins;
+ supportedCoins: string[];
+}
+
+export interface DataContainer {
+ storeId: string;
+ shortStoreId: string;
+ owner: string;
+ products: Record;
+ catalogues: CatalogueDataContainer[];
+ id: string;
+}
+
+export interface CatalogueDataContainer {
+ id: string;
+ products: Record;
+}
+export interface Catalogue {
+ id: string;
+ products: Record;
+ user?: string;
+}
+interface GlobalState {
+ isOpenCreateStoreModal: boolean;
+ isLoadingCurrentBlog: boolean;
+ isLoadingGlobal: boolean;
+ isOpenEditStoreModal: boolean;
+ currentStore: CurrentStore | null;
+ downloads: any;
+ userAvatarHash: Record;
+ dataContainer: DataContainer | null;
+ listProducts: {
+ sort: string;
+ products: ProductDataContainer[];
+ categories: string[];
+ };
+ productsToSave: Record;
+ catalogueHashMap: Record;
+ products: Product[];
+ myOrders: Order[];
+ recentlyVisitedStoreId: string;
+ productsToSaveCategories: string[];
+}
+
+const initialState: GlobalState = {
+ isOpenCreateStoreModal: false,
+ isLoadingCurrentBlog: true,
+ isLoadingGlobal: false,
+ currentStore: null, // user owns this shop
+ isOpenEditStoreModal: false,
+ downloads: {},
+ userAvatarHash: {},
+ dataContainer: null,
+ listProducts: {
+ sort: "created",
+ products: [],
+ categories: []
+ },
+ products: [],
+ productsToSave: {},
+ catalogueHashMap: {},
+ myOrders: [],
+ recentlyVisitedStoreId: "",
+ productsToSaveCategories: []
+};
+
+export const globalSlice = createSlice({
+ name: "global",
+ initialState,
+ reducers: {
+ toggleCreateStoreModal: (state, action) => {
+ state.isOpenCreateStoreModal = action.payload;
+ },
+ toggleEditStoreModal: (state, action) => {
+ state.isOpenEditStoreModal = action.payload;
+ },
+ setCurrentStore: (state, action) => {
+ state.currentStore = action.payload;
+ state.isLoadingCurrentBlog = false;
+ },
+
+ setDataContainer: (state, action) => {
+ let categories: any = {};
+ state.dataContainer = action.payload;
+ const mappedProducts = Object.keys(action.payload.products)
+ .map((key) => {
+ const category = action.payload?.products[key]?.category;
+ if (category) {
+ categories[category] = true;
+ }
+ return {
+ ...action.payload.products[key],
+ productId: key,
+ user: action.payload.owner
+ };
+ })
+ .sort((a, b) => b.created - a.created);
+ state.listProducts.sort = "created";
+ state.listProducts.products = mappedProducts;
+ state.listProducts.categories = Object.keys(categories).map((cat) => cat);
+ },
+ setProducts: (state, action) => {
+ state.products = action.payload;
+ },
+ setIsLoadingGlobal: (state, action) => {
+ state.isLoadingGlobal = action.payload;
+ },
+ setAddToDownloads: (state, action) => {
+ const download = action.payload;
+ state.downloads[download.identifier] = download;
+ },
+ clearAllProductsToSave: (state) => {
+ state.productsToSave = {};
+ },
+ removeFromProductsToSave: (state, action) => {
+ const productId = action.payload;
+ // Create a copy of the productsToSave object
+ const updatedProductsToSave = { ...state.productsToSave };
+ // Remove the nested object based on the ID
+ delete updatedProductsToSave[productId];
+ // Return the updated state with the removed object
+ state.productsToSave = updatedProductsToSave;
+ },
+ setProductsToSave: (state, action) => {
+ const product = action.payload;
+ state.productsToSave[product.id] = product;
+ },
+ updateDownloads: (state, action) => {
+ const { identifier } = action.payload;
+ const download = action.payload;
+ state.downloads[identifier] = {
+ ...state.downloads[identifier],
+ ...download
+ };
+ },
+ setUserAvatarHash: (state, action) => {
+ const avatar = action.payload;
+ if (avatar?.name && avatar?.url) {
+ state.userAvatarHash[avatar?.name] = avatar?.url;
+ }
+ },
+ updateCatalogueHashMap: (state, action) => {
+ const catalogue = action.payload;
+ Object.keys(catalogue).forEach((key) => {
+ state.catalogueHashMap[key] = catalogue[key];
+ });
+ },
+ setCatalogueHashMap: (state, action) => {
+ const catalogue = action.payload;
+ state.catalogueHashMap[catalogue.id] = catalogue;
+ },
+ upsertProducts: (state, action) => {
+ action.payload.forEach((product: Product) => {
+ const index = state.products.findIndex((p) => p.id === product.id);
+ if (index !== -1) {
+ state.products[index] = product;
+ } else {
+ state.products.push(product);
+ }
+ });
+ },
+ upsertMyOrders: (state, action) => {
+ action.payload.forEach((order: Order) => {
+ const index = state.myOrders.findIndex((p) => p.id === order.id);
+ if (index !== -1) {
+ state.myOrders[index] = order;
+ } else {
+ state.myOrders.push(order);
+ }
+ });
+ },
+ updateRecentlyVisitedStoreId: (state, action) => {
+ state.recentlyVisitedStoreId = action.payload;
+ },
+ addProductsToSaveCategory: (state, action) => {
+ const newCategory = action.payload;
+ state.productsToSaveCategories.push(newCategory);
+ },
+ resetProducts: (state) => {
+ state.products = [];
+ state.productsToSave = {};
+ },
+ resetListProducts: (state) => {
+ state.listProducts = {
+ sort: "created",
+ products: [],
+ categories: []
+ };
+ },
+ clearDataCotainer: (state) => {
+ state.dataContainer = null;
+ }
+ }
+});
+
+export const {
+ toggleCreateStoreModal,
+ setCurrentStore,
+ setDataContainer,
+ setIsLoadingGlobal,
+ toggleEditStoreModal,
+ setAddToDownloads,
+ updateRecentlyVisitedStoreId,
+ updateDownloads,
+ setUserAvatarHash,
+ removeFromProductsToSave,
+ setProductsToSave,
+ setCatalogueHashMap,
+ updateCatalogueHashMap,
+ upsertProducts,
+ upsertMyOrders,
+ resetProducts,
+ clearAllProductsToSave,
+ resetListProducts,
+ setProducts,
+ addProductsToSaveCategory,
+ clearDataCotainer
+} = globalSlice.actions;
+
+export default globalSlice.reducer;
diff --git a/src/state/features/notificationsSlice.ts b/src/state/features/notificationsSlice.ts
new file mode 100644
index 0000000..af074e2
--- /dev/null
+++ b/src/state/features/notificationsSlice.ts
@@ -0,0 +1,73 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+interface AlertTypes {
+ alertSuccess: string
+ alertError: string
+ alertInfo: string
+}
+
+interface InitialState {
+ alertTypes: AlertTypes
+}
+
+const initialState: InitialState = {
+ alertTypes: {
+ alertSuccess: '',
+ alertError: '',
+ alertInfo: ''
+ }
+}
+
+export const notificationsSlice = createSlice({
+ name: "notifications",
+ initialState,
+ reducers: {
+ setNotification: (
+ state: InitialState,
+ action: PayloadAction<{ alertType: string; msg: string }>
+ ) => {
+ if (action.payload.alertType === "success") {
+ return {
+ ...state,
+ alertTypes: {
+ ...state.alertTypes,
+ alertSuccess: action.payload.msg,
+ },
+ };
+ } else if (action.payload.alertType === "error") {
+ return {
+ ...state,
+ alertTypes: {
+ ...state.alertTypes,
+ alertError: action.payload.msg,
+ },
+ };
+ } else if (action.payload.alertType === "info") {
+ return {
+ ...state,
+ alertTypes: {
+ ...state.alertTypes,
+ alertInfo: action.payload.msg,
+ },
+ };
+ }
+ return state;
+ },
+ removeNotification: (state: InitialState) => {
+ return {
+ ...state,
+ alertTypes: {
+ ...state.alertTypes,
+ alertSuccess: '',
+ alertError: '',
+ alertInfo: ''
+ }
+ }
+ },
+ },
+});
+
+export const { setNotification, removeNotification } =
+ notificationsSlice.actions;
+
+export default notificationsSlice.reducer;
diff --git a/src/state/features/orderSlice.ts b/src/state/features/orderSlice.ts
new file mode 100644
index 0000000..32cc3c9
--- /dev/null
+++ b/src/state/features/orderSlice.ts
@@ -0,0 +1,100 @@
+import { createSlice } from '@reduxjs/toolkit'
+import { RootState } from '../store'
+import { Product } from './storeSlice'
+
+interface OrderState {
+ hashMapOrders: Record
+ orders: Order[]
+ isOpen: boolean
+}
+const initialState: OrderState = {
+ hashMapOrders: {},
+ orders: [],
+ isOpen: false
+}
+export interface TotalPriceDetails {
+ totalPrice: number;
+}
+
+export interface ProductDetails {
+ product: Product;
+ catalogueId: string;
+ quantity: number;
+ pricePerUnit: number;
+ totalProductPrice: number;
+}
+
+export type Details = TotalPriceDetails & Record;
+
+interface Delivery {
+ customerName: string
+ shippingAddress: {
+ streetAddress: string
+ city: string
+ region?: string
+ state?: string
+ country: string
+ zipCode: string
+ }
+}
+
+interface Payment {
+ total: number
+ currency: string
+ transactionSignature: string
+ arrrAddressUsed?:string
+}
+enum CommunicationMethod {
+ QMail = 'Q-Mail'
+}
+export interface Order {
+ created: number
+ updated: number
+ version?: number
+ details?: Details
+ delivery?: Delivery
+ payment?: Payment
+ communicationMethod?: CommunicationMethod[]
+ user: string
+ sellerName?: string
+ storeName?: string
+ id: string
+ totalPrice?: number
+ status?: string
+ note?: string
+}
+
+export interface Status {
+ status: string
+}
+
+export const orderSlice = createSlice({
+ name: 'order',
+ initialState,
+ reducers: {
+ upsertOrders: (state, action) => {
+ action.payload.forEach((order: Order) => {
+ const index = state.orders.findIndex((p) => p.id === order.id)
+ if (index !== -1) {
+ state.orders[index] = order
+ } else {
+ state.orders.push(order)
+ }
+ })
+ },
+ addToHashMap: (state, action) => {
+ const order = action.payload
+ state.hashMapOrders[order.id] = {
+ ...order,
+ totalPrice: order.details.totalPrice
+ }
+ },
+ resetOrders: (state) => {
+ state.orders = []
+ },
+ }
+})
+
+export const { upsertOrders, addToHashMap, resetOrders } = orderSlice.actions
+
+export default orderSlice.reducer
diff --git a/src/state/features/storeSlice.ts b/src/state/features/storeSlice.ts
new file mode 100644
index 0000000..553b0c0
--- /dev/null
+++ b/src/state/features/storeSlice.ts
@@ -0,0 +1,281 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { RootState } from "../store";
+import { CurrentStore, DataContainer, ProductDataContainer } from "./globalSlice";
+import { ForeignCoins } from "../../components/modals/CreateStoreModal";
+import { CoinFilter } from "../../pages/Store/Store/Store";
+
+interface GlobalState {
+ products: Product[];
+ filteredProducts: Product[];
+ myStores: Store[];
+ hashMapProducts: Record;
+ isFiltering: boolean;
+ filterValue: string;
+ hashMapStores: Record;
+ storeId: string | null;
+ storeOwner: string | null;
+ stores: Store[];
+ storeReviews: StoreReview[];
+ hashMapStoreReviews: Record;
+ currentViewedStore: CurrentStore | null;
+ viewedStoreDataContainer: DataContainer | null;
+ viewedStoreListProducts: {
+ sort: string;
+ products: ProductDataContainer[];
+ categories: string[];
+ };
+ preferredCoin: CoinFilter
+}
+
+const initialState: GlobalState = {
+ products: [],
+ filteredProducts: [],
+ myStores: [],
+ hashMapProducts: {},
+ isFiltering: false,
+ filterValue: "",
+ hashMapStores: {},
+ storeId: null,
+ storeOwner: null,
+ stores: [],
+ storeReviews: [],
+ hashMapStoreReviews: {},
+ // user is viewing this shop, doesn't own it
+ currentViewedStore: null,
+ viewedStoreDataContainer: null,
+ viewedStoreListProducts: {
+ sort: "created",
+ products: [],
+ categories: []
+ },
+ preferredCoin: CoinFilter.qort
+};
+
+export interface Price {
+ currency: string;
+ value: number;
+}
+export interface Product {
+ title?: string;
+ description?: string;
+ created: number;
+ user: string;
+ id: string;
+ category?: string;
+ categoryName?: string;
+ tags?: string[];
+ updated?: number;
+ isValid?: boolean;
+ price?: Price[];
+ images?: string[];
+ type?: string;
+ catalogueId: string;
+ status?: string;
+ mainImageIndex?: number;
+ isUpdate?: boolean;
+}
+
+export interface Store {
+ title: string;
+ description: string;
+ created: number;
+ owner: string;
+ id: string;
+ category?: string;
+ categoryName?: string;
+ tags?: string[];
+ updated?: number;
+ isValid?: boolean;
+ logo?: string;
+ location?: string;
+ shipsTo?: string;
+ shortStoreId?: string;
+ foreignCoins?: ForeignCoins;
+ supportedCoins?: string[];
+}
+
+export interface StoreReview {
+ id: string;
+ name: string;
+ title: string;
+ description: string;
+ created: number;
+ rating: number;
+ updated?: number;
+}
+
+export const storeSlice = createSlice({
+ name: "store",
+ initialState,
+ reducers: {
+ setAllMyStores: (state, action) => {
+ state.myStores = action.payload;
+ },
+ addToAllMyStores: (state, action) => {
+ state.myStores.push(action.payload);
+ },
+ setViewedStoreDataContainer: (state, action) => {
+ let categories: any = {};
+ state.viewedStoreDataContainer = action.payload;
+ const mappedProducts = Object.keys(action.payload.products)
+ .map((key) => {
+ const category = action.payload?.products[key]?.category;
+ if (category) {
+ categories[category] = true;
+ }
+ return {
+ ...action.payload.products[key],
+ productId: key,
+ user: action.payload.owner
+ };
+ })
+ .sort((a, b) => b.created - a.created);
+ state.viewedStoreListProducts.sort = "created";
+ state.viewedStoreListProducts.products = mappedProducts;
+ state.viewedStoreListProducts.categories = Object.keys(categories).map((cat) => cat);
+ },
+ clearViewedStoreDataContainer: (state) => {
+ state.viewedStoreDataContainer = null;
+ state.viewedStoreListProducts = {
+ sort: "created",
+ products: [],
+ categories: []
+ };
+ },
+ addToHashMap: (state, action) => {
+ const post = action.payload;
+ state.hashMapProducts[post.id] = post;
+ },
+ addToHashMapStores: (state, action) => {
+ const store = action.payload;
+ state.hashMapStores[store?.id] = store;
+ },
+ addToHashMapStoreReviews: (state, action) => {
+ const review = action.payload;
+ state.hashMapStoreReviews[review.id] = review;
+ },
+ updateInHashMap: (state, action) => {
+ const { id } = action.payload;
+ const post = action.payload;
+ state.hashMapProducts[id] = { ...post };
+ },
+ removeFromHashMap: (state, action) => {
+ const idToDelete = action.payload;
+ delete state.hashMapProducts[idToDelete];
+ },
+ addArrayToHashMap: (state, action) => {
+ const products = action.payload;
+ products.forEach((post: Product) => {
+ state.hashMapProducts[post.id] = post;
+ });
+ },
+ setStoreId: (state, action) => {
+ state.storeId = action.payload;
+ },
+ setStoreOwner: (state, action) => {
+ state.storeOwner = action.payload;
+ },
+ setCurrentViewedStore: (state, action) => {
+ state.currentViewedStore = action.payload;
+ },
+ upsertPosts: (state, action) => {
+ action.payload.forEach((post: Product) => {
+ const index = state.products.findIndex((p) => p.id === post.id);
+ if (index !== -1) {
+ state.products[index] = post;
+ } else {
+ state.products.push(post);
+ }
+ });
+ },
+ upsertStores: (state, action) => {
+ action.payload.forEach((store: Store) => {
+ const index = state.stores.findIndex((p) => p.id === store.id);
+ if (index !== -1) {
+ state.stores[index] = store;
+ } else {
+ state.stores.push(store);
+ }
+ });
+ },
+ upsertReviews: (state, action) => {
+ action.payload.forEach((review: StoreReview) => {
+ const index = state.storeReviews.findIndex((p) => p.id === review.id);
+ if (index !== -1) {
+ state.storeReviews[index] = review;
+ } else {
+ state.storeReviews.push(review);
+ }
+ });
+ },
+ addToStores: (state, action) => {
+ const newStore = action.payload;
+ state.stores.unshift(newStore);
+ },
+ addToReviews: (state, action) => {
+ const newReview = action.payload;
+ state.storeReviews.unshift(newReview);
+ },
+ upsertFilteredPosts: (state, action) => {
+ action.payload.forEach((post: Product) => {
+ const index = state.filteredProducts.findIndex((p) => p.id === post.id);
+ if (index !== -1) {
+ state.filteredProducts[index] = post;
+ } else {
+ state.filteredProducts.push(post);
+ }
+ });
+ },
+ upsertPostsBeginning: (state, action) => {
+ action.payload.reverse().forEach((post: Product) => {
+ const index = state.products.findIndex((p) => p.id === post.id);
+ if (index !== -1) {
+ state.products[index] = post;
+ } else {
+ state.products.unshift(post);
+ }
+ });
+ },
+ clearReviews: (state) => {
+ state.storeReviews = [];
+ },
+ blockUser: (state, action) => {
+ const username = action.payload;
+ state.products = state.products.filter((item) => item.user !== username);
+ state.filteredProducts = state.filteredProducts.filter(
+ (item) => item.user !== username
+ );
+ },
+ setPreferredCoin: (state, action) => {
+ const coin: CoinFilter = action.payload;
+ state.preferredCoin = coin
+ },
+ }
+});
+
+export const {
+ addToAllMyStores,
+ setAllMyStores,
+ setCurrentViewedStore,
+ setViewedStoreDataContainer,
+ clearViewedStoreDataContainer,
+ addToHashMap,
+ updateInHashMap,
+ removeFromHashMap,
+ upsertPosts,
+ blockUser,
+ upsertPostsBeginning,
+ upsertFilteredPosts,
+ addToHashMapStores,
+ setStoreId,
+ setStoreOwner,
+ upsertStores,
+ upsertReviews,
+ clearReviews,
+ addToStores,
+ addToHashMapStoreReviews,
+ addToReviews,
+ setPreferredCoin
+} = storeSlice.actions;
+
+export default storeSlice.reducer;
diff --git a/src/state/store.ts b/src/state/store.ts
new file mode 100644
index 0000000..77d91eb
--- /dev/null
+++ b/src/state/store.ts
@@ -0,0 +1,31 @@
+import { configureStore } from '@reduxjs/toolkit'
+import notificationsReducer from './features/notificationsSlice'
+import authReducer from './features/authSlice'
+import globalReducer from './features/globalSlice'
+import storeReducer from './features/storeSlice'
+import cartReducer from './features/cartSlice'
+import orderReducer from './features/orderSlice'
+
+export const store = configureStore({
+ reducer: {
+ notifications: notificationsReducer,
+ auth: authReducer,
+ global: globalReducer,
+ store: storeReducer,
+ cart: cartReducer,
+ order: orderReducer
+ },
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ serializableCheck: false
+ }),
+ preloadedState: undefined // optional, can be any valid state object
+})
+
+// Define the RootState type, which is the type of the entire Redux state tree.
+// This is useful when you need to access the state in a component or elsewhere.
+export type RootState = ReturnType
+
+// Define the AppDispatch type, which is the type of the Redux store's dispatch function.
+// This is useful when you need to dispatch an action in a component or elsewhere.
+export type AppDispatch = typeof store.dispatch
diff --git a/src/styles/fonts/Cairo.ttf b/src/styles/fonts/Cairo.ttf
new file mode 100644
index 0000000..5bdb2a9
Binary files /dev/null and b/src/styles/fonts/Cairo.ttf differ
diff --git a/src/styles/fonts/Cambon-Light.ttf b/src/styles/fonts/Cambon-Light.ttf
new file mode 100644
index 0000000..e65b385
Binary files /dev/null and b/src/styles/fonts/Cambon-Light.ttf differ
diff --git a/src/styles/fonts/Catamaran.ttf b/src/styles/fonts/Catamaran.ttf
new file mode 100644
index 0000000..9053c6d
Binary files /dev/null and b/src/styles/fonts/Catamaran.ttf differ
diff --git a/src/styles/fonts/Karla.ttf b/src/styles/fonts/Karla.ttf
new file mode 100644
index 0000000..1e1fc9d
Binary files /dev/null and b/src/styles/fonts/Karla.ttf differ
diff --git a/src/styles/fonts/Livvic.ttf b/src/styles/fonts/Livvic.ttf
new file mode 100644
index 0000000..f477b28
Binary files /dev/null and b/src/styles/fonts/Livvic.ttf differ
diff --git a/src/styles/fonts/Merriweather Sans.ttf b/src/styles/fonts/Merriweather Sans.ttf
new file mode 100644
index 0000000..dea514c
Binary files /dev/null and b/src/styles/fonts/Merriweather Sans.ttf differ
diff --git a/src/styles/fonts/Montserrat.ttf b/src/styles/fonts/Montserrat.ttf
new file mode 100644
index 0000000..656db66
Binary files /dev/null and b/src/styles/fonts/Montserrat.ttf differ
diff --git a/src/styles/fonts/Oxygen.ttf b/src/styles/fonts/Oxygen.ttf
new file mode 100644
index 0000000..d03f43a
Binary files /dev/null and b/src/styles/fonts/Oxygen.ttf differ
diff --git a/src/styles/fonts/ProximaNova.otf b/src/styles/fonts/ProximaNova.otf
new file mode 100644
index 0000000..27c8d8f
Binary files /dev/null and b/src/styles/fonts/ProximaNova.otf differ
diff --git a/src/styles/fonts/Raleway.ttf b/src/styles/fonts/Raleway.ttf
new file mode 100644
index 0000000..424fb0e
Binary files /dev/null and b/src/styles/fonts/Raleway.ttf differ
diff --git a/src/styles/theme.tsx b/src/styles/theme.tsx
new file mode 100644
index 0000000..126eebf
--- /dev/null
+++ b/src/styles/theme.tsx
@@ -0,0 +1,269 @@
+import { createTheme } from "@mui/material/styles";
+
+const commonThemeOptions = {
+ typography: {
+ fontFamily: [
+ "Cambon Light",
+ "Raleway, sans-serif",
+ "Karla",
+ "Merriweather Sans",
+ "Montserrat",
+ "Proxima Nova",
+ "Oxygen",
+ "Catamaran",
+ "Cairo",
+ "Arial",
+ ].join(","),
+ h1: {
+ fontSize: "2rem",
+ fontWeight: 600,
+ },
+ h2: {
+ fontSize: "1.75rem",
+ fontWeight: 500,
+ },
+ h3: {
+ fontSize: "1.5rem",
+ fontWeight: 500,
+ },
+ h4: {
+ fontSize: "1.25rem",
+ fontWeight: 500,
+ },
+ h5: {
+ fontSize: "1rem",
+ fontWeight: 500,
+ },
+ h6: {
+ fontSize: "0.875rem",
+ fontWeight: 500,
+ },
+ body1: {
+ fontSize: "23px",
+ fontFamily: "Raleway",
+ fontWeight: 400,
+ lineHeight: 1.5,
+ letterSpacing: "0.5px",
+ },
+
+ body2: {
+ fontSize: "18px",
+ fontFamily: "Raleway, Arial",
+ fontWeight: 400,
+ lineHeight: 1.4,
+ letterSpacing: "0.2px",
+ },
+ },
+ spacing: 8,
+ shape: {
+ borderRadius: 4,
+ },
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 900,
+ lg: 1200,
+ xl: 1536,
+ },
+ },
+ components: {
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ backgroundColor: "inherit",
+ transition: "filter 0.3s ease-in-out",
+ "&:hover": {
+ filter: "brightness(1.1)",
+ },
+ },
+ },
+ defaultProps: {
+ disableElevation: true,
+ disableRipple: true,
+ },
+ },
+ },
+};
+
+const lightTheme = createTheme({
+ ...commonThemeOptions,
+ palette: {
+ mode: "light",
+ primary: {
+ main: "#ffffff",
+ dark: "#F5F5F5",
+ light: "#FCFCFC",
+ },
+ secondary: {
+ main: "#417Ed4",
+ dark: "#3e74c1",
+ },
+ background: {
+ default: "#fcfcfc",
+ paper: "#F5F5F5",
+ },
+ text: {
+ primary: "#000000",
+ secondary: "#525252",
+ },
+ },
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ "body::-webkit-scrollbar-track": {
+ backgroundColor: "#fcfcfc",
+ },
+ "body::-webkit-scrollbar-track:hover": {
+ backgroundColor: "#fcfcfc",
+ },
+ "body::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: "#fcfcfc",
+ },
+ "body::-webkit-scrollbar-thumb": {
+ backgroundColor: "#417Ed4",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ transition: "all 0.3s ease-in-out",
+ },
+ "body::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#3e74c1",
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ boxShadow:
+ "rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;",
+ borderRadius: "8px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;",
+ },
+ },
+ },
+ },
+ MuiIcon: {
+ defaultProps: {
+ style: {
+ color: "#000000",
+ },
+ },
+ },
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: {
+ fontFamily: "Karla",
+ fontSize: "17px",
+ letterSpacing: "0px",
+ fontWeight: 300,
+ backgroundColor: "#F5F5F5",
+ color: "#000000",
+ zIndex: 10,
+ opacity: 1,
+ },
+ arrow: {
+ color: "#F5F5F5",
+ },
+ },
+ },
+ },
+});
+
+const darkTheme = createTheme({
+ ...commonThemeOptions,
+ palette: {
+ mode: "dark",
+ primary: {
+ main: "#2e3d60",
+ dark: "#1a2744",
+ light: "#353535",
+ },
+ secondary: {
+ main: "#417Ed4",
+ dark: "#3e74c1",
+ },
+
+ background: {
+ default: "#111111",
+ paper: "#1A1C1E",
+ },
+ text: {
+ primary: "#ffffff",
+ secondary: "#b3b3b3",
+ },
+ },
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ "body::-webkit-scrollbar-track": {
+ backgroundColor: "#111111",
+ },
+ "body::-webkit-scrollbar-track:hover": {
+ backgroundColor: "#111111",
+ },
+ "body::-webkit-scrollbar": {
+ width: "16px",
+ height: "10px",
+ backgroundColor: "#111111",
+ },
+ "body::-webkit-scrollbar-thumb": {
+ backgroundColor: "#2e3d60",
+ borderRadius: "8px",
+ backgroundClip: "content-box",
+ border: "4px solid transparent",
+ transition: "all 0.3s ease-in-out",
+ },
+ "body::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#1a2744",
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ boxShadow: "none",
+ borderRadius: "8px",
+ transition: "all 0.3s ease-in-out",
+ "&:hover": {
+ cursor: "pointer",
+ boxShadow:
+ " 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);",
+ },
+ },
+ },
+ },
+ MuiIcon: {
+ defaultProps: {
+ style: {
+ color: "#ffffff",
+ },
+ },
+ },
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: {
+ fontFamily: "Karla",
+ fontSize: "17px",
+ letterSpacing: "0px",
+ fontWeight: 300,
+ backgroundColor: "#1A1C1E",
+ color: "white",
+ zIndex: 10,
+ opacity: 1,
+ },
+ arrow: {
+ color: "#1A1C1E",
+ },
+ },
+ },
+ },
+});
+
+export { lightTheme, darkTheme };
diff --git a/src/utils/checkStructure.ts b/src/utils/checkStructure.ts
new file mode 100644
index 0000000..1e67a4f
--- /dev/null
+++ b/src/utils/checkStructure.ts
@@ -0,0 +1,57 @@
+// check the structure of products
+export const checkStructure = (content: any) => {
+ let isValid = true;
+ if (!content?.title) isValid = false;
+ if (!content?.created) isValid = false;
+ if (!content?.description) isValid = false;
+ if (!content?.type) isValid = false;
+ if (!Array.isArray(content?.images)) isValid = false;
+ if (!Array.isArray(content?.price)) isValid = false;
+
+ return isValid;
+};
+
+export const checkStructureOrders = (content: any) => {
+ let isValid = true;
+ if (!content?.delivery) isValid = false;
+ if (!content?.created) isValid = false;
+ if (!content?.details) isValid = false;
+ if (!content?.payment) isValid = false;
+ if (!Array.isArray(content?.communicationMethod)) isValid = false;
+
+ return isValid;
+};
+
+export const checkStructureMailMessages = (content: any) => {
+ let isValid = true;
+ if (!content?.title) isValid = false;
+ if (!content?.description) isValid = false;
+ if (!content?.createdAt) isValid = false;
+ if (!content?.version) isValid = false;
+ if (!Array.isArray(content?.attachments)) isValid = false;
+ if (!Array.isArray(content?.textContent)) isValid = false;
+ if (typeof content?.generalData !== "object") isValid = false;
+
+ return isValid;
+};
+
+// check the structure of stores
+export const checkStructureStore = (content: any) => {
+ let isValid = true;
+ if (!content?.title) isValid = false;
+ if (!content?.created) isValid = false;
+ if (!content?.description) isValid = false;
+ if (!content?.shipsTo) isValid = false;
+ if (!content?.shortStoreId) isValid = false;
+ return isValid;
+};
+
+export const checkStructureStoreReviews = (content: any) => {
+ let isValid = true;
+ if (!content?.title) isValid = false;
+ if (!content?.created) isValid = false;
+ if (!content?.description) isValid = false;
+ if (!content?.rating) isValid = false;
+
+ return isValid;
+};
diff --git a/src/utils/extractTextFromSlate.ts b/src/utils/extractTextFromSlate.ts
new file mode 100644
index 0000000..7686787
--- /dev/null
+++ b/src/utils/extractTextFromSlate.ts
@@ -0,0 +1,14 @@
+export function extractTextFromSlate(nodes: any) {
+ if(!Array.isArray(nodes)) return ""
+ let text = "";
+
+ for (const node of nodes) {
+ if (node.text) {
+ text += node.text;
+ } else if (node.children) {
+ text += extractTextFromSlate(node.children);
+ }
+ }
+
+ return text;
+ }
\ No newline at end of file
diff --git a/src/utils/fetchCatalogues.ts b/src/utils/fetchCatalogues.ts
new file mode 100644
index 0000000..cab815e
--- /dev/null
+++ b/src/utils/fetchCatalogues.ts
@@ -0,0 +1,36 @@
+import { checkStructure } from './checkStructure'
+
+export const fetchAndEvaluateCatalogues = async (data: any) => {
+ const getCatalogues = async () => {
+ const { user, catalogueId } = data
+ let obj: any = {
+ isValid: false
+ }
+
+ if (!user || !catalogueId) return obj
+
+ try {
+ const catalogueHashMap = await qortalRequest( {
+ action: "FETCH_QDN_RESOURCE",
+ name: user,
+ service: "DOCUMENT",
+ identifier: catalogueId
+ })
+
+ if (catalogueHashMap) {
+ obj = {
+ ...catalogueHashMap,
+ user,
+ id: catalogueId,
+ isValid: true
+ }
+ }
+ return obj
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ const res = await getCatalogues()
+ return res
+}
diff --git a/src/utils/fetchMail.ts b/src/utils/fetchMail.ts
new file mode 100644
index 0000000..8acd882
--- /dev/null
+++ b/src/utils/fetchMail.ts
@@ -0,0 +1,73 @@
+import { MAIL_SERVICE_TYPE } from '../constants/mail'
+import { checkStructure, checkStructureMailMessages } from './checkStructure'
+import { extractTextFromSlate } from './extractTextFromSlate'
+import {
+ base64ToUint8Array,
+ objectToUint8ArrayFromResponse,
+ uint8ArrayToObject
+} from './toBase64'
+
+export const fetchAndEvaluateMail = async (data: any) => {
+ const getBlogPost = async () => {
+ const { user, messageIdentifier, content, otherUser } = data
+ let obj: any = {
+ ...content,
+ isValid: false
+ }
+
+ try {
+ // throw new Error('hello')
+ if (!user || !messageIdentifier) return obj
+ const url = `/arbitrary/${MAIL_SERVICE_TYPE}/${user}/${messageIdentifier}`
+ let res = await qortalRequest({
+ action: 'FETCH_QDN_RESOURCE',
+ name: user,
+ service: MAIL_SERVICE_TYPE,
+ identifier: messageIdentifier,
+ encoding: 'base64'
+ })
+ const base64 = res
+ const resName = await qortalRequest({
+ action: 'GET_NAME_DATA',
+ name: otherUser
+ })
+ if (!resName?.owner) return obj
+
+ const recipientAddress = resName.owner
+ const resAddress = await qortalRequest({
+ action: 'GET_ACCOUNT_DATA',
+ address: recipientAddress
+ })
+ if (!resAddress?.publicKey) return obj
+ const recipientPublicKey = resAddress.publicKey
+ let requestEncryptBody: any = {
+ action: 'DECRYPT_DATA',
+ encryptedData: base64,
+ publicKey: recipientPublicKey
+ }
+ const resDecrypt = await qortalRequest(requestEncryptBody)
+
+ if (!resDecrypt) return obj
+ const decryptToUnit8Array = base64ToUint8Array(resDecrypt)
+ const responseData = uint8ArrayToObject(decryptToUnit8Array)
+
+ if (checkStructureMailMessages(responseData)) {
+ obj = {
+ ...content,
+ ...responseData,
+ user,
+ title: responseData.title,
+ createdAt: responseData.createdAt,
+ id: messageIdentifier,
+ isValid: true
+ }
+ }
+ return obj
+ } catch (error) {
+ console.log({ error })
+ }
+ }
+
+ const res = await getBlogPost()
+ return res
+}
diff --git a/src/utils/fetchOrders.ts b/src/utils/fetchOrders.ts
new file mode 100644
index 0000000..0923ff3
--- /dev/null
+++ b/src/utils/fetchOrders.ts
@@ -0,0 +1,52 @@
+import { Status } from '../state/features/orderSlice'
+import { checkStructure, checkStructureOrders } from './checkStructure'
+import { base64ToObject } from './toBase64'
+
+export const fetchAndEvaluateOrders = async (data: any) => {
+ const getOrders = async () => {
+
+ const { user, orderId, content } = data
+
+ let obj: any = {
+ ...content,
+ isValid: false
+ }
+
+ if (!user || !orderId) return obj
+
+ try {
+ const data = await qortalRequest({
+ action: 'FETCH_QDN_RESOURCE',
+ name: user,
+ service: 'DOCUMENT_PRIVATE',
+ identifier: orderId,
+ encoding: 'base64'
+ })
+ const decryptedData = await qortalRequest({
+ action: 'DECRYPT_DATA',
+ encryptedData: data
+ })
+ let statusDocument: any = {
+ status: 'Received',
+ note: ''
+ }
+ const dataToObject = await base64ToObject(decryptedData)
+
+ if (checkStructureOrders(dataToObject)) {
+ obj = {
+ ...dataToObject,
+ ...content,
+ ...statusDocument,
+ user,
+ id: orderId,
+ isValid: true
+ }
+ }
+ return obj
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ const res = await getOrders()
+ return res
+}
diff --git a/src/utils/fetchPosts.ts b/src/utils/fetchPosts.ts
new file mode 100644
index 0000000..adfb380
--- /dev/null
+++ b/src/utils/fetchPosts.ts
@@ -0,0 +1,41 @@
+import { checkStructure } from './checkStructure'
+import { extractTextFromSlate } from './extractTextFromSlate'
+
+export const fetchAndEvaluateProducts = async (data: any) => {
+ const getStoreProducts = async () => {
+ const { user, productId, content } = data
+ let obj: any = {
+ ...content,
+ isValid: false
+ }
+
+ if (!user || !productId) return obj
+
+ try {
+ const url = `/arbitrary/PRODUCT/${user}/${productId}`
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+
+ const responseData = await response.json()
+ if (checkStructure(responseData)) {
+ obj = {
+ ...responseData,
+ ...content,
+ user,
+ title: responseData.title,
+ created: responseData.created,
+ id: productId,
+ isValid: true
+ }
+ }
+ return obj
+ } catch (error) {}
+ }
+
+ const res = await getStoreProducts()
+ return res
+}
diff --git a/src/utils/fetchStoreReviews.ts b/src/utils/fetchStoreReviews.ts
new file mode 100644
index 0000000..e7fbe63
--- /dev/null
+++ b/src/utils/fetchStoreReviews.ts
@@ -0,0 +1,36 @@
+import { checkStructureStoreReviews } from "./checkStructure";
+
+export const fetchAndEvaluateStoreReviews = async (data: any) => {
+ const getStoreReviews = async () => {
+ const { owner, reviewId, content } = data;
+ let obj: any = {
+ ...content,
+ isValid: false
+ };
+
+ if (!owner || !reviewId) return obj;
+ // Fetch review rawdata from QDN based on resource's location (need name, service type and identifier)
+ try {
+ const url = `/arbitrary/DOCUMENT/${owner}/${reviewId}`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+
+ const responseData = await response.json();
+ if (checkStructureStoreReviews(responseData)) {
+ obj = {
+ ...responseData,
+ id: reviewId,
+ isValid: true
+ };
+ }
+ return obj;
+ } catch (error) {}
+ };
+
+ const res = await getStoreReviews();
+ return res;
+};
diff --git a/src/utils/fetchStores.ts b/src/utils/fetchStores.ts
new file mode 100644
index 0000000..7b05bd7
--- /dev/null
+++ b/src/utils/fetchStores.ts
@@ -0,0 +1,40 @@
+import { checkStructure, checkStructureStore } from './checkStructure'
+
+export const fetchAndEvaluateStores = async (data: any) => {
+ const getStoreStores = async () => {
+ const { owner, storeId, content } = data
+ let obj: any = {
+ ...content,
+ isValid: false
+ }
+
+ if (!owner || !storeId) return obj
+ // Fetch rawdata from QDN based on resource's location (need name, service type and identifier)
+ try {
+ const url = `/arbitrary/STORE/${owner}/${storeId}`
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+
+ const responseData = await response.json()
+ if (checkStructureStore(responseData)) {
+ obj = {
+ ...content,
+ ...responseData,
+ owner,
+ id: storeId,
+ isValid: true
+ }
+ }
+ return obj
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ const res = await getStoreStores()
+ return res
+}
diff --git a/src/utils/storeIdFormats.ts b/src/utils/storeIdFormats.ts
new file mode 100644
index 0000000..5d2f15e
--- /dev/null
+++ b/src/utils/storeIdFormats.ts
@@ -0,0 +1,9 @@
+// This function extracts the store title of the identifier string
+export const extractStoreTitle = (identifier: string): string => {
+ // Split the identifier string by the '-post-' delimiter
+ // Split the identifier string by the '-general-' delimiter
+ const parts = identifier.split('-general-');
+ // Remove any hyphens from the parts[1] value
+ const shopTitle = (parts[1] || '').replace(/-/g, '');
+ return shopTitle;
+}
diff --git a/src/utils/time.ts b/src/utils/time.ts
new file mode 100644
index 0000000..3b2830f
--- /dev/null
+++ b/src/utils/time.ts
@@ -0,0 +1,24 @@
+import moment from 'moment'
+
+export function formatTimestamp(timestamp: number): string {
+ const now = moment()
+ const timestampMoment = moment(timestamp)
+ const elapsedTime = now.diff(timestampMoment, 'minutes')
+
+ if (elapsedTime < 1) {
+ return 'Just now'
+ } else if (elapsedTime < 60) {
+ return `${elapsedTime}m`
+ } else if (elapsedTime < 1440) {
+ return `${Math.floor(elapsedTime / 60)}h`
+ } else {
+ return timestampMoment.format('MMM D')
+ }
+}
+
+
+export const formatDate = (unixTimestamp: number): string => {
+ const date = moment(unixTimestamp, 'x').fromNow()
+
+ return date
+}
\ No newline at end of file
diff --git a/src/utils/toBase64.ts b/src/utils/toBase64.ts
new file mode 100644
index 0000000..a0c1ee7
--- /dev/null
+++ b/src/utils/toBase64.ts
@@ -0,0 +1,190 @@
+export const toBase64 = (file: File): Promise =>
+ new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(file)
+ reader.onload = () => resolve(reader.result)
+ reader.onerror = (error) => {
+ reject(error)
+ }
+ })
+
+export function objectToBase64(obj: any) {
+ // Step 1: Convert the object to a JSON string
+ const jsonString = JSON.stringify(obj)
+
+ // Step 2: Create a Blob from the JSON string
+ const blob = new Blob([jsonString], { type: 'application/json' })
+
+ // Step 3: Create a FileReader to read the Blob as a base64-encoded string
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.onloadend = () => {
+ if (typeof reader.result === 'string') {
+ // Remove 'data:application/json;base64,' prefix
+ const base64 = reader.result.replace(
+ 'data:application/json;base64,',
+ ''
+ )
+ resolve(base64)
+ } else {
+ reject(new Error('Failed to read the Blob as a base64-encoded string'))
+ }
+ }
+ reader.onerror = () => {
+ reject(reader.error)
+ }
+ reader.readAsDataURL(blob)
+ })
+}
+
+export function objectToUint8Array(obj: any) {
+ // Convert the object to a JSON string
+ const jsonString = JSON.stringify(obj)
+
+ // Encode the JSON string as a byte array using TextEncoder
+ const encoder = new TextEncoder()
+ const byteArray = encoder.encode(jsonString)
+
+ // Create a new Uint8Array and set its content to the encoded byte array
+ const uint8Array = new Uint8Array(byteArray)
+
+ return uint8Array
+}
+
+export function uint8ArrayToBase64(uint8Array: Uint8Array): string {
+ const length = uint8Array.length
+ let binaryString = ''
+ const chunkSize = 1024 * 1024 // Process 1MB at a time
+
+ for (let i = 0; i < length; i += chunkSize) {
+ const chunkEnd = Math.min(i + chunkSize, length)
+ const chunk = uint8Array.subarray(i, chunkEnd)
+ binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(
+ ''
+ )
+ }
+
+ return btoa(binaryString)
+}
+
+export function objectToUint8ArrayFromResponse(obj: any) {
+ const len = Object.keys(obj).length
+ const result = new Uint8Array(len)
+
+ for (let i = 0; i < len; i++) {
+ result[i] = obj[i]
+ }
+
+ return result
+}
+// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
+// let binary = ''
+// const bytes = new Uint8Array(arrayBuffer)
+// const len = bytes.length
+
+// for (let i = 0; i < len; i++) {
+// binary += String.fromCharCode(bytes[i])
+// }
+
+// return btoa(binary)
+// }
+
+export function base64ToUint8Array(base64: string) {
+ const binaryString = atob(base64)
+ const len = binaryString.length
+ const bytes = new Uint8Array(len)
+
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i)
+ }
+
+ return bytes
+}
+
+export function uint8ArrayToObject(uint8Array: Uint8Array) {
+ // Decode the byte array using TextDecoder
+ const decoder = new TextDecoder()
+ const jsonString = decoder.decode(uint8Array)
+
+ // Convert the JSON string back into an object
+ const obj = JSON.parse(jsonString)
+
+ return obj
+}
+
+export async function base64ToObject(base64: string) {
+ const binaryString = atob(base64)
+ const len = binaryString.length
+ const bytes = new Uint8Array(len)
+
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i)
+ }
+
+ const decoder = new TextDecoder()
+ const jsonString = decoder.decode(bytes)
+
+ // Convert the JSON string back into an object
+ const obj: object = await new Promise((resolve, reject) => {
+ try {
+ const result = JSON.parse(jsonString)
+ resolve(result)
+ } catch (err) {
+ reject(err)
+ }
+ })
+
+ return obj
+}
+
+export function processFileInChunks(file: File): Promise {
+ return new Promise(
+ (resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
+ const reader = new FileReader()
+
+ reader.onload = function (event: ProgressEvent) {
+ const arrayBuffer = event.target?.result as ArrayBuffer
+ const uint8Array = new Uint8Array(arrayBuffer)
+ resolve(uint8Array)
+ }
+
+ reader.onerror = function (error: ProgressEvent) {
+ reject(error)
+ }
+
+ reader.readAsArrayBuffer(file)
+ }
+ )
+}
+
+// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise {
+// const fileStream = file.stream();
+// const reader = fileStream.getReader();
+// const totalLength = file.size;
+
+// if (totalLength <= 0 || isNaN(totalLength)) {
+// throw new Error('Invalid file size');
+// }
+
+// const combinedArray = new Uint8Array(totalLength);
+// let offset = 0;
+
+// while (offset < totalLength) {
+// const { value, done } = await reader.read();
+
+// if (done) {
+// break;
+// }
+
+// const chunk = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
+
+// // Set elements one by one instead of using combinedArray.set(chunk, offset)
+// for (let i = 0; i < chunk.length; i++) {
+// combinedArray[offset + i] = chunk[i];
+// }
+
+// offset += chunk.length;
+// }
+
+// return combinedArray;
+// }
diff --git a/src/utils/withTimeout.ts b/src/utils/withTimeout.ts
new file mode 100644
index 0000000..72c9a5a
--- /dev/null
+++ b/src/utils/withTimeout.ts
@@ -0,0 +1,17 @@
+export const withTimeout = async (ms: number, promise: Promise): Promise => {
+ let timer: NodeJS.Timeout | undefined;
+ const timeoutPromise = new Promise((_, reject) => {
+ timer = setTimeout(() => {
+ reject(new Error(`Promise timed out after ${ms}ms`));
+ }, ms);
+ });
+
+ try {
+ const result = await Promise.race([promise, timeoutPromise]);
+ clearTimeout(timer);
+ return result;
+ } catch (error) {
+ clearTimeout(timer);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/webworkers/decodeBase64.js b/src/webworkers/decodeBase64.js
new file mode 100644
index 0000000..d5c2d5e
--- /dev/null
+++ b/src/webworkers/decodeBase64.js
@@ -0,0 +1,62 @@
+self.addEventListener('message', async (event) => {
+ //
+
+ // const qortalRequest = event.data.qortalRequest
+ // const name = event.data.name
+ // const service = event.data.service
+ // const identifier = event.data.identifier
+
+ // const url2 = `/arbitrary/VIDEO/crowetic/q-blog-video-xGR8HP?&encoding=base64`
+ // const res = await fetch(url2);
+ // const data = await res.text();
+ // self.postMessage(data);
+ const url2 = `/arbitrary/VIDEO/crowetic/q-blog-video-xGR8HP`
+
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', url2, true)
+ xhr.responseType = 'blob'
+ xhr.onload = () => {
+ const headers = xhr.getAllResponseHeaders()
+ const blob = xhr.response
+ const url = URL.createObjectURL(blob)
+ const byteLength = blob.size
+ const contentRange = `bytes 0-${byteLength}/${byteLength}`
+ const contentType = xhr.getResponseHeader('Content-Type')
+ self.postMessage(url)
+ // this.dispatchEvent(new CustomEvent('loaded', { detail: { headers, byteLength, contentRange, contentType } }));
+ }
+ xhr.send()
+ // fetch(url2)
+ // .then(response => response.blob())
+ // .then(blob => {
+ //
+
+ // // Create a new Blob with the 'video/mp4' MIME type
+ // const mp4Blob = new Blob([blob], { type: 'video/mp4' });
+ //
+
+ // // Generate an object URL from the new Blob
+ // const url = URL.createObjectURL(mp4Blob);
+ // self.postMessage(url);
+ // })
+ // .catch(error => console.error(error));
+ // const response = await fetch(url2, {
+ // method: 'GET'
+ // })
+ //
+ // const responseData = await response.json()
+ //
+ // const base64Data = responseData
+ // const decodedData = atob(base64Data);
+ // const byteNumbers = new Array(decodedData.length);
+
+ // for (let i = 0; i < decodedData.length; i++) {
+ // byteNumbers[i] = decodedData.charCodeAt(i);
+ // }
+
+ // const byteArray = new Uint8Array(byteNumbers);
+ // const blob = new Blob([byteArray], { type: 'video/mp4' });
+ // const url = URL.createObjectURL(blob);
+
+ // self.postMessage(url);
+})
diff --git a/src/webworkers/getBlogWorker.js b/src/webworkers/getBlogWorker.js
new file mode 100644
index 0000000..6ef3ee0
--- /dev/null
+++ b/src/webworkers/getBlogWorker.js
@@ -0,0 +1,74 @@
+import { checkStructure } from '../utils/checkStructure'
+
+function extractTextFromSlate(nodes) {
+ if (!Array.isArray(nodes)) return ''
+ let text = ''
+
+ for (const node of nodes) {
+ if (node.text) {
+ text += node.text
+ } else if (node.children) {
+ text += extractTextFromSlate(node.children)
+ }
+ }
+
+ return text
+}
+// worker.js
+self.onmessage = async (event) => {
+ const getBlogPost = async () => {
+ const { data } = event
+ const { user, postId, content } = data
+ let obj = {
+ remove: true,
+ id: postId
+ }
+
+ if (!user || !postId) return obj
+
+ try {
+ const url = `/arbitrary/BLOG_POST / ${user}/${postId}`
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+
+ const responseData = await response.json()
+
+ if (checkStructure(responseData)) {
+ const findImage = responseData.postContent.find(
+ (data) => data?.type === 'image'
+ )
+ const findText = responseData.postContent.find(
+ (data) => data?.type === 'editor'
+ )
+ obj = {
+ content: responseData.postContent,
+ ...content,
+ user,
+ title: responseData.title,
+ createdAt: responseData.createdAt,
+ id: postId,
+ postImage: findImage ? findImage?.content?.image : ''
+ }
+
+ if (findText && findText.content) {
+ obj.description = extractTextFromSlate(findText?.content)
+ }
+ }
+ return obj
+ } catch (error) { }
+ }
+
+ const res = await getBlogPost()
+ self.postMessage(res)
+ //
+ // const { data } = event;
+ // // Perform your computation using the data
+ // const result = data.map((x) => x * 2);
+
+ // // Send the result back to the main thread
+ // self.postMessage(result);
+}
diff --git a/src/wrappers/GlobalWrapper-styles.tsx b/src/wrappers/GlobalWrapper-styles.tsx
new file mode 100644
index 0000000..85034cf
--- /dev/null
+++ b/src/wrappers/GlobalWrapper-styles.tsx
@@ -0,0 +1,26 @@
+import { styled } from "@mui/system";
+import { Button, Typography } from "@mui/material";
+
+export const CustomModalTitle = styled(Typography)({
+ textAlign: "center",
+ fontFamily: "Merriweather Sans",
+ fontSize: "30px",
+ fontWeight: 500,
+ color: "#a01717",
+ userSelect: "none"
+});
+
+export const CustomModalButton = styled(Button)(({ theme }) => ({
+ alignSelf: "center",
+ fontFamily: "Raleway",
+ fontWeight: 400,
+ width: "50%",
+ backgroundColor: theme.palette.secondary.main,
+ color: "#fff",
+ transition: "all 0.3s ease-in-out",
+ borderRadius: "5px",
+ "&:hover": {
+ backgroundColor: theme.palette.secondary.dark,
+ cursor: "pointer"
+ }
+}));
diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx
new file mode 100644
index 0000000..d7763af
--- /dev/null
+++ b/src/wrappers/GlobalWrapper.tsx
@@ -0,0 +1,875 @@
+import React, { useEffect, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { addUser } from "../state/features/authSlice";
+import { RootState } from "../state/store";
+import CreateStoreModal, {
+ onPublishParam
+} from "../components/modals/CreateStoreModal";
+import EditStoreModal, {
+ onPublishParamEdit
+} from "../components/modals/EditStoreModal";
+import {
+ setCurrentStore,
+ setDataContainer,
+ toggleEditStoreModal,
+ toggleCreateStoreModal,
+ setIsLoadingGlobal,
+ resetProducts,
+ resetListProducts,
+ DataContainer,
+ ProductDataContainer,
+ updateRecentlyVisitedStoreId,
+ clearDataCotainer
+} from "../state/features/globalSlice";
+import NavBar from "../components/layout/Navbar/Navbar";
+import PageLoader from "../components/common/PageLoader";
+import { setNotification } from "../state/features/notificationsSlice";
+import ConsentModal from "../components/modals/ConsentModal";
+import { objectToBase64 } from "../utils/toBase64";
+import { Cart } from "../pages/Cart/Cart";
+import {
+ Store,
+ addToAllMyStores,
+ addToHashMapStores,
+ addToStores,
+ setAllMyStores
+} from "../state/features/storeSlice";
+import { useFetchStores } from "../hooks/useFetchStores";
+import { DATA_CONTAINER_BASE, STORE_BASE } from "../constants/identifiers";
+import { ReusableModal } from "../components/modals/ReusableModal";
+import { Box, Button, Stack, Typography } from "@mui/material";
+import { useNavigate } from "react-router-dom";
+import { CustomModalButton, CustomModalTitle } from "./GlobalWrapper-styles";
+interface Props {
+ children: React.ReactNode;
+ setTheme: (val: string) => void;
+}
+interface ShortDataContainer {
+ storeId: string;
+ shortStoreId: string;
+ owner: string;
+ products: Record;
+}
+
+const GlobalWrapper: React.FC = ({ children, setTheme }) => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ // Get user from auth
+ const user = useSelector((state: RootState) => state.auth.user);
+ // Fetch all my stores from global redux
+ const myStores = useSelector((state: RootState) => state.store.myStores);
+ // Fetch recentlyVisitedStoreId from cart redux
+ const recentlyVisitedStoreId = useSelector(
+ (state: RootState) => state.global.recentlyVisitedStoreId
+ );
+ // Open create & edit shop modals
+ const isOpenCreateStoreModal = useSelector(
+ (state: RootState) => state.global.isOpenCreateStoreModal
+ );
+ const isOpenEditStoreModal = useSelector(
+ (state: RootState) => state.global.isOpenEditStoreModal
+ );
+ // Get current store from global store
+ const currentStore = useSelector(
+ (state: RootState) => state.global.currentStore
+ );
+ // Loading global state
+ const isLoadingGlobal = useSelector(
+ (state: RootState) => state.global.isLoadingGlobal
+ );
+ const userOwnDataContainer = useSelector(
+ (state: RootState) => state.global.dataContainer
+ );
+
+ const { getStore, checkAndUpdateResource } = useFetchStores();
+
+ const [userAvatar, setUserAvatar] = useState("");
+ const [closeCreateStoreModal, setCloseCreateStoreModal] =
+ useState(false);
+ const [hasAttemptedToFetchShopInitial, setHasAttemptedToFetchShopInitial] =
+ useState