From e962349dd1f031eddf3e4971ecc161056a61444b Mon Sep 17 00:00:00 2001 From: Koochr Date: Tue, 1 Nov 2022 13:02:10 +0000 Subject: [PATCH 1/5] Rewrite context and make various fixes --- .eslintrc.js | 14 +- hardhat/contracts/Blog.sol | 48 +- package-lock.json | 1782 +++-------------- package.json | 25 +- src/@types/enums.ts | 24 + src/@types/global.d.ts | 7 +- src/@types/interfaces.ts | 71 - src/@types/types.ts | 55 +- src/App.tsx | 39 +- src/components/BlogPreviewItem.tsx | 149 +- src/components/Button.tsx | 19 +- src/components/ColorImports.tsx | 4 +- src/components/DeleteBlogModal.tsx | 36 + src/components/ErrorPlaceholder.tsx | 6 + src/components/Header.tsx | 9 +- src/components/IdentityInfo.tsx | 33 +- src/components/Loader.tsx | 7 +- src/components/SearchBar.tsx | 14 +- src/components/ToastNotification.tsx | 12 +- src/context/AppContext.tsx | 143 -- src/context/PostsContext.tsx | 283 +++ src/context/ThemeContext.tsx | 66 + src/context/ToastContext.tsx | 37 + src/context/UserContext.tsx | 150 ++ src/context/useBlogs.ts | 89 - src/context/utils.ts | 3 +- src/index.js | 3 +- src/layouts/PageLayout.tsx | 21 +- src/pages/Admin.tsx | 136 +- src/pages/Blog.tsx | 488 ++--- .../{Create.tsx => CreateOrEditPost.tsx} | 172 +- ...ateProfile.tsx => CreateOrEditProfile.tsx} | 103 +- src/pages/Customize.tsx | 63 +- src/pages/Home.tsx | 70 +- src/utils.js | 3 - 35 files changed, 1517 insertions(+), 2667 deletions(-) delete mode 100644 src/@types/interfaces.ts create mode 100644 src/components/DeleteBlogModal.tsx create mode 100644 src/components/ErrorPlaceholder.tsx delete mode 100644 src/context/AppContext.tsx create mode 100644 src/context/PostsContext.tsx create mode 100644 src/context/ThemeContext.tsx create mode 100644 src/context/ToastContext.tsx create mode 100644 src/context/UserContext.tsx delete mode 100644 src/context/useBlogs.ts rename src/pages/{Create.tsx => CreateOrEditPost.tsx} (59%) rename src/pages/{CreateProfile.tsx => CreateOrEditProfile.tsx} (61%) delete mode 100644 src/utils.js diff --git a/.eslintrc.js b/.eslintrc.js index b32332b..ae17ca7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,7 +46,7 @@ module.exports = { 'semi': ['error', 'always'], 'linebreak-style': 'off', 'no-fallthrough': 'off', - 'no-console': 'warn', // TODO: replace console with logger in helpers and set to error + 'no-console': ['warn', {allow: ['error']}], 'no-debugger': 'error', 'max-len': [ 'warn', @@ -110,5 +110,15 @@ module.exports = { // 'import/imports-first': ['error', 'absolute-first'], // 'import/extensions': ['error', 'never'], // 'import/no-unresolved': 'off', - } + }, + overrides: [ + { + files: ['hardhat/**/*', 'watch.docker.js'], + rules: {'no-console': 'off'} + }, + { + files: ['*.js'], + rules: {'@typescript-eslint/explicit-module-boundary-types': 'off'} + } + ] }; diff --git a/hardhat/contracts/Blog.sol b/hardhat/contracts/Blog.sol index 4dd1e30..82096f1 100644 --- a/hardhat/contracts/Blog.sol +++ b/hardhat/contracts/Blog.sol @@ -12,14 +12,14 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { Counters.Counter private numBlogPosts; Counters.Counter internal commentIds; - struct Theme{ + struct Theme { string background; string primary; string text; } struct UserInfo { - address walletAddress; + address walletAddress; // @deprecated string dataStorageHash; } @@ -62,18 +62,14 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { function _authorizeUpgrade(address) internal override onlyOwner {} // User Info methods - function getUserInfo() public view returns (UserInfo memory) { - return user; + function getUserInfo() public view returns (string memory) { + return user.dataStorageHash; } function saveUserInfo( - address _walletAddress, - string calldata _dataStorageHash - ) public payable { - user = UserInfo({ - walletAddress: _walletAddress, - dataStorageHash: _dataStorageHash - }); + string calldata dataStorageHash + ) public onlyOwner { + user.dataStorageHash = dataStorageHash; } // Blog Post methods @@ -95,7 +91,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { bool _isPublished, string calldata _publishDate, string calldata _tags - ) public payable onlyOwner { + ) public onlyOwner { string[] memory _previousStorageHashes; numBlogPosts.increment(); BlogPost memory blog = BlogPost({ @@ -114,7 +110,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { string calldata _storageHash, string calldata _publishDate, string calldata _tags - ) public payable onlyOwner { + ) public onlyOwner { uint256 index = _getBlogIndexById(id); blogPosts[index].previousStorageHashes.push( blogPosts[index].storageHash @@ -124,7 +120,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { blogPosts[index].tags = _tags; } - function deleteBlog(uint256 _id) public payable onlyOwner { + function deleteBlog(uint256 _id) public onlyOwner { uint256 requiredBlogIndex = _getBlogIndexById(_id); deletedBlogPosts.push(blogPosts[requiredBlogIndex]); for (uint256 i = requiredBlogIndex; i < blogPosts.length - 1; i++) { @@ -133,13 +129,13 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { blogPosts.pop(); } - function publish(uint256 _id) public payable onlyOwner { + function publish(uint256 _id) public onlyOwner { uint256 requiredBlogIndex = _getBlogIndexById(_id); BlogPost storage requiredBlog = blogPosts[requiredBlogIndex]; requiredBlog.isPublished = true; } - function unpublish(uint256 _id) public payable onlyOwner { + function unpublish(uint256 _id) public onlyOwner { uint256 requiredBlogIndex = _getBlogIndexById(_id); BlogPost storage requiredBlog = blogPosts[requiredBlogIndex]; requiredBlog.isPublished = false; @@ -154,9 +150,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { return commentsByBlogPostId[_id]; } - function addCommentToBlogPost(uint256 _id, string calldata _comment) - public - payable + function addCommentToBlogPost(uint256 _id, string calldata _comment) public { Comment memory comment = Comment({ id: commentIds.current(), @@ -171,7 +165,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 _blogId, uint256 _commentId, string calldata _comment - ) public payable { + ) public { for (uint256 i = 0; i < commentsByBlogPostId[_blogId].length; i++) { if (commentsByBlogPostId[_blogId][i].id == _commentId) { if ( @@ -185,9 +179,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { } } - function deleteCommentForBlogPost(uint256 _blogId, uint256 _commentId) - public - payable + function deleteCommentForBlogPost(uint256 _blogId, uint256 _commentId) public { uint256 index; for (uint256 i = 0; i < commentsByBlogPostId[_blogId].length; i++) { @@ -222,7 +214,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { return likesByBlogPostId[_id]; } - function likeBlogPost(uint256 _id) public payable { + function likeBlogPost(uint256 _id) public { for (uint256 i = 0; i < likesByBlogPostId[_id].length; i++) { if (likesByBlogPostId[_id][i] == msg.sender) { revert("You have already liked the blog"); @@ -231,7 +223,7 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { likesByBlogPostId[_id].push(msg.sender); } - function unlikeBlogPost(uint256 _id) public payable { + function unlikeBlogPost(uint256 _id) public { uint256 index; for (uint256 i = 0; i < likesByBlogPostId[_id].length; i++) { if (likesByBlogPostId[_id][i] == msg.sender) { @@ -316,7 +308,11 @@ contract Blog is Initializable, UUPSUpgradeable, OwnableUpgradeable { return theme; } - function setTheme(string calldata _background, string calldata _primary, string calldata _text) public payable { + function setTheme( + string calldata _background, + string calldata _primary, + string calldata _text + ) public payable onlyOwner { theme = Theme({ background: _background, primary: _primary, diff --git a/package-lock.json b/package-lock.json index 95dc8a8..e9da0bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,16 @@ { - "name": "template.point", + "name": "blogsoftware.point", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "template.point", + "name": "blogsoftware.point", "version": "0.0.1", "dependencies": { "@emotion/react": "^11.10.0", "@emotion/styled": "^11.9.3", "@mui/icons-material": "^5.8.4", - "@openzeppelin/contracts": "^4.7.3", - "@openzeppelin/contracts-upgradeable": "^4.7.3", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "@types/jest": "^28.1.6", - "@types/node": "^18.6.1", - "@types/react": "^18.0.15", - "@types/react-dom": "^18.0.6", "axios": "^0.27.2", "boring-avatars": "^1.7.0", "dayjs": "^1.11.4", @@ -29,31 +20,31 @@ "react-router-dom": "^6.3.0", "react-scripts": "4.0.3", "timeago.js": "^4.0.2", - "typescript": "^4.7.4", "web-vitals": "^1.0.1" }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^1.0.2", "@nomiclabs/hardhat-ethers": "^2.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", + "@openzeppelin/contracts": "^4.7.3", + "@openzeppelin/contracts-upgradeable": "^4.7.3", "@openzeppelin/hardhat-upgrades": "^1.19.0", "@parcel/transformer-image": "^2.0.0-rc.0", + "@types/lodash": "^4.14.186", + "@types/node": "^18.11.8", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", "autoprefixer": "^10.4.7", - "dotenv": "^16.0.2", "eslint-plugin-prefer-arrow": "^1.2.3", "ethers": "^5.6.9", "hardhat": "^2.10.1", "parcel": "^2.0.0-rc.0", "postcss": "^8.4.14", "tailwindcss": "^3.1.6", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "typescript": "^4.8.4" } }, - "node_modules/@adobe/css-tools": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", - "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==" - }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -3963,17 +3954,6 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/@jest/source-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", @@ -4977,12 +4957,14 @@ "node_modules/@openzeppelin/contracts": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", - "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==" + "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==", + "dev": true }, "node_modules/@openzeppelin/contracts-upgradeable": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz", - "integrity": "sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==" + "integrity": "sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==", + "dev": true }, "node_modules/@openzeppelin/hardhat-upgrades": { "version": "1.19.1", @@ -7118,11 +7100,6 @@ "node": ">=6" } }, - "node_modules/@sinclair/typebox": { - "version": "0.24.27", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", - "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==" - }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -7535,321 +7512,6 @@ "node": ">=6" } }, - "node_modules/@testing-library/dom": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.1.tgz", - "integrity": "sha512-XEV2mBxgv6DKjL3+U3WEUzBgT2CjYksoXGlLrrJXYP8OvRfGkBonvelkorazpFlp8tkEecO06r43vN4DIEyegQ==", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "node_modules/@testing-library/dom/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==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", - "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@testing-library/jest-dom/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "7.31.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", - "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.6", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@testing-library/react/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@testing-library/react/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@testing-library/react/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/@testing-library/react/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/user-event": { - "version": "12.8.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz", - "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -8071,11 +7733,6 @@ "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==", "dev": true }, - "node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==" - }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -8215,40 +7872,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/jest": { - "version": "28.1.6", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.6.tgz", - "integrity": "sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ==", - "dependencies": { - "jest-matcher-utils": "^28.0.0", - "pretty-format": "^28.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -8276,6 +7899,12 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.186", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", + "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", + "dev": true + }, "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -8305,9 +7934,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.4.tgz", - "integrity": "sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==" + "version": "18.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz", + "integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==" }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -8385,9 +8014,9 @@ } }, "node_modules/@types/react": { - "version": "18.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", - "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", + "version": "18.0.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", + "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -8395,9 +8024,10 @@ } }, "node_modules/@types/react-dom": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", - "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", + "version": "18.0.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", + "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -8482,14 +8112,6 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dependencies": { - "@types/jest": "*" - } - }, "node_modules/@types/uglify-js": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.16.0.tgz", @@ -9261,14 +8883,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", - "engines": { - "node": ">=6.0" - } - }, "node_modules/arity-n": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", @@ -12112,11 +11726,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" - }, "node_modules/css/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12866,14 +12475,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -12939,11 +12540,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==" - }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -13089,15 +12685,6 @@ "node": ">=8" } }, - "node_modules/dotenv": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", - "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/dotenv-expand": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", @@ -29636,109 +29223,6 @@ "node": ">=8" } }, - "node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-diff/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-docblock": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", @@ -29889,14 +29373,6 @@ "node": ">= 10.14.2" } }, - "node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/jest-haste-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", @@ -30179,109 +29655,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-matcher-utils/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-message-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", @@ -32384,14 +31757,6 @@ "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", "dev": true }, - "node_modules/lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -32708,14 +32073,6 @@ "dom-walk": "^0.1.0" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "engines": { - "node": ">=4" - } - }, "node_modules/mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -38102,38 +37459,6 @@ "renderkid": "^2.0.4" } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -38730,7 +38055,8 @@ "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==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/react-quill": { "version": "2.0.0", @@ -39243,18 +38569,6 @@ "node": "*" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -42189,17 +41503,6 @@ "npm": ">=3" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -43801,9 +43104,9 @@ } }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -47210,11 +46513,6 @@ } }, "dependencies": { - "@adobe/css-tools": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", - "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==" - }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -49962,14 +49260,6 @@ } } }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, "@jest/source-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", @@ -50612,12 +49902,14 @@ "@openzeppelin/contracts": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", - "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==" + "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==", + "dev": true }, "@openzeppelin/contracts-upgradeable": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz", - "integrity": "sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==" + "integrity": "sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A==", + "dev": true }, "@openzeppelin/hardhat-upgrades": { "version": "1.19.1", @@ -52044,11 +51336,6 @@ "tslib": "^1.9.3" } }, - "@sinclair/typebox": { - "version": "0.24.27", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", - "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==" - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -52338,236 +51625,6 @@ "defer-to-connect": "^1.0.1" } }, - "@testing-library/dom": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.1.tgz", - "integrity": "sha512-XEV2mBxgv6DKjL3+U3WEUzBgT2CjYksoXGlLrrJXYP8OvRfGkBonvelkorazpFlp8tkEecO06r43vN4DIEyegQ==", - "peer": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", - "requires": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" - }, - "dependencies": { - "@testing-library/dom": { - "version": "7.31.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", - "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.6", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/user-event": { - "version": "12.8.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz", - "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==", - "requires": { - "@babel/runtime": "^7.12.5" - } - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -52764,11 +51821,6 @@ "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==", "dev": true }, - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==" - }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -52908,33 +51960,6 @@ "@types/istanbul-lib-report": "*" } }, - "@types/jest": { - "version": "28.1.6", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.6.tgz", - "integrity": "sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ==", - "requires": { - "jest-matcher-utils": "^28.0.0", - "pretty-format": "^28.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -52962,6 +51987,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.186", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", + "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", + "dev": true + }, "@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -52991,9 +52022,9 @@ "peer": true }, "@types/node": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.4.tgz", - "integrity": "sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==" + "version": "18.11.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.8.tgz", + "integrity": "sha512-uGwPWlE0Hj972KkHtCDVwZ8O39GmyjfMane1Z3GUBGGnkZ2USDq7SxLpVIiIHpweY9DS0QTDH0Nw7RNBsAAZ5A==" }, "@types/node-fetch": { "version": "2.6.2", @@ -53070,9 +52101,9 @@ } }, "@types/react": { - "version": "18.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", - "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", + "version": "18.0.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", + "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -53080,9 +52111,10 @@ } }, "@types/react-dom": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", - "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", + "version": "18.0.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", + "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", + "dev": true, "requires": { "@types/react": "*" } @@ -53167,14 +52199,6 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, - "@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "requires": { - "@types/jest": "*" - } - }, "@types/uglify-js": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.16.0.tgz", @@ -53785,11 +52809,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==" - }, "arity-n": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", @@ -56042,11 +55061,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" - }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -56617,11 +55631,6 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==" - }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -56683,11 +55692,6 @@ "esutils": "^2.0.2" } }, - "dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==" - }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -56804,12 +55808,6 @@ } } }, - "dotenv": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", - "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==", - "dev": true - }, "dotenv-expand": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", @@ -69729,368 +68727,23 @@ } } }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==" - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "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==" - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", "pretty-format": "^26.6.2" }, "dependencies": { @@ -70102,6 +68755,15 @@ "color-convert": "^2.0.1" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -70115,6 +68777,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", @@ -70135,18 +68802,113 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } } } }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "jest-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "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==" + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" }, "dependencies": { "ansi-styles": { @@ -70179,29 +68941,59 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" } }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -70212,6 +69004,59 @@ } } }, + "jest-leak-detector": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "jest-message-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", @@ -71834,11 +70679,6 @@ "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", "dev": true }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==" - }, "magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -72106,11 +70946,6 @@ "dom-walk": "^0.1.0" } }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" - }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -76243,31 +75078,6 @@ "renderkid": "^2.0.4" } }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "peer": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "peer": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - } - } - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -76732,7 +75542,8 @@ "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "react-quill": { "version": "2.0.0", @@ -77095,15 +75906,6 @@ } } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, "reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -79450,14 +78252,6 @@ "is-hex-prefixed": "1.0.0" } }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "requires": { - "min-indent": "^1.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -80690,9 +79484,9 @@ } }, "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" }, "typical": { "version": "4.0.0", diff --git a/package.json b/package.json index c84a38f..2802070 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,12 @@ { - "name": "template.point", + "name": "blogsoftware.point", "version": "0.0.1", "author": "Point Network", - "description": "Template example zapp - use as a scaffold to create more Zapps!", + "description": "Point Blog Software", "dependencies": { "@emotion/react": "^11.10.0", "@emotion/styled": "^11.9.3", "@mui/icons-material": "^5.8.4", - "@openzeppelin/contracts": "^4.7.3", - "@openzeppelin/contracts-upgradeable": "^4.7.3", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "@types/jest": "^28.1.6", - "@types/node": "^18.6.1", - "@types/react": "^18.0.15", - "@types/react-dom": "^18.0.6", "axios": "^0.27.2", "boring-avatars": "^1.7.0", "dayjs": "^1.11.4", @@ -25,7 +16,6 @@ "react-router-dom": "^6.3.0", "react-scripts": "4.0.3", "timeago.js": "^4.0.2", - "typescript": "^4.7.4", "web-vitals": "^1.0.1" }, "scripts": { @@ -61,8 +51,14 @@ "@nomicfoundation/hardhat-toolbox": "^1.0.2", "@nomiclabs/hardhat-ethers": "^2.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", + "@openzeppelin/contracts": "^4.7.3", + "@openzeppelin/contracts-upgradeable": "^4.7.3", "@openzeppelin/hardhat-upgrades": "^1.19.0", "@parcel/transformer-image": "^2.0.0-rc.0", + "@types/lodash": "^4.14.186", + "@types/node": "^18.11.8", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", "autoprefixer": "^10.4.7", "eslint-plugin-prefer-arrow": "^1.2.3", "ethers": "^5.6.9", @@ -70,6 +66,7 @@ "parcel": "^2.0.0-rc.0", "postcss": "^8.4.14", "tailwindcss": "^3.1.6", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "typescript": "^4.8.4" } -} \ No newline at end of file +} diff --git a/src/@types/enums.ts b/src/@types/enums.ts index 98012e2..6af3357 100644 --- a/src/@types/enums.ts +++ b/src/@types/enums.ts @@ -28,6 +28,7 @@ export enum BlogContract { export enum RoutesEnum { home = '/', blog = '/blog', + deleted_blog = '/deleted_blog', admin = '/admin', create = '/create', edit = '/edit', @@ -35,3 +36,26 @@ export enum RoutesEnum { edit_profile = '/edit_profile', customize = '/customize', } + +export const backgroundColors = ['zinc', 'slate', 'gray', 'neutral']; + +export const primaryColors = [ + 'red', + 'orange', + 'amber', + 'yellow', + 'lime', + 'green', + 'emerald', + 'teal', + 'cyan', + 'sky', + 'blue', + 'indigo', + 'violet', + 'purple', + 'fuchsia', + 'pink', + 'rose', + 'slate' +]; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 647b558..4ebbb71 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -1,7 +1,8 @@ export {}; declare global { - interface Window { - point: any; - } + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + point: any; + } } diff --git a/src/@types/interfaces.ts b/src/@types/interfaces.ts deleted file mode 100644 index 4e2f105..0000000 --- a/src/@types/interfaces.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {Dispatch, ReactEventHandler, SetStateAction} from 'react'; -import {Theme} from './types'; - -export interface ButtonProps { - children: string; - disabled?: boolean; - onClick?: ReactEventHandler; -} - -export interface AppContentInterface { - toast: ToastNotification; - setToast: Dispatch>; - loading: boolean; - isOwner: boolean; - userInfo: UserInfoState; - getUserInfo: () => void; - blogs: BlogsState; - getAllBlogs: () => void; - getDeletedBlogs: () => Promise<(Blog & BlogContractData)[]>; - getDataFromStorage: (storageHash: string) => any; - ownerAddress: string; - ownerIdentity: string; - visitorAddress: string; - visitorIdentity: string; - theme: Theme; - setTheme: Dispatch>; -} - -export interface BlogsState { - loading: boolean; - data: (Blog & BlogContractData)[]; - error?: string; -} - -export interface Blog { - coverImage: string; - title: string; - content: string; - publisher: string; - createdDate: string; -} - -export interface BlogContractData { - id: number; - storageHash: string; - isPublished: boolean; - publishDate: string; - previousStorageHashes: string[]; - tags: string; -} - -export interface UserInfo { - avatar: string; - about: string; -} - -export interface UserInfoContractData { - walletAddress: string; - dataStorageHash: string; -} - -export interface UserInfoState { - loading: boolean; - data: UserInfoContractData & UserInfo; - error?: string; -} - -export interface ToastNotification { - color: 'green-500' | 'red-500'; - message: string; -} diff --git a/src/@types/types.ts b/src/@types/types.ts index fb4551e..a9082b6 100644 --- a/src/@types/types.ts +++ b/src/@types/types.ts @@ -1,22 +1,39 @@ -export type EditBlogContractParams = [ - id: number, - _storageHash: string, - _publishDate: string, - _tags: string -]; +import {PropsWithChildren, ReactEventHandler} from 'react'; -export type AddBlogContractParams = [ - _storageHash: string, - _isPublished: boolean, - _publishDate: string, - _tags: string -]; - -export type Comment = [ - id: string, - comment: string, - commentedBy: string, - identity?: string -]; +export type Comment = { + id: number, + comment: string, + commentedBy: string, + identity?: string +}; export type Theme = [background: string, primary: string, text: string]; + +export type ButtonProps = { + disabled?: boolean; + onClick?: ReactEventHandler; +} & PropsWithChildren + +export type BlogPost = { + coverImage: Blob | null; + title: string; + content: string; + publisher: string; + createdDate: string; + id: number; + storageHash: string; + isPublished: boolean; + publishDate: string; + previousStorageHashes: string[]; + tags: string; +} + +export type UserInfo = { + avatar: Blob | null; + about: string; +} + +export type ToastNotification = { + color: 'green-500' | 'red-500'; + message: string; +} diff --git a/src/App.tsx b/src/App.tsx index 208467b..db334d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,34 +2,45 @@ import {Route, Routes, BrowserRouter as Router} from 'react-router-dom'; import Home from './pages/Home'; import Blog from './pages/Blog'; import Admin from './pages/Admin'; -import Create from './pages/Create'; -import CreateProfile from './pages/CreateProfile'; +import CreateOrEditPost from './pages/CreateOrEditPost'; +import CreateOrEditProfile from './pages/CreateOrEditProfile'; import Customize from './pages/Customize'; import ToastNotification from './components/ToastNotification'; -import {ProvideAppContext} from './context/AppContext'; import {RoutesEnum} from './@types/enums'; import ColorImports from './components/ColorImports'; +import {ProvideToastContext} from './context/ToastContext'; +import {ProvideThemeContext} from './context/ThemeContext'; +import {FunctionComponent} from 'react'; +import {ProvideUserContext} from './context/UserContext'; +import {ProvidePostsContext} from './context/PostsContext'; -const Main = () => ( +const Main: FunctionComponent = () => ( } /> } /> + } /> } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> } /> ); -const App = () => ( +const App: FunctionComponent = () => ( - - -
- - + + + + + +
+ + + + + ); diff --git a/src/components/BlogPreviewItem.tsx b/src/components/BlogPreviewItem.tsx index 794e88c..92f2a32 100644 --- a/src/components/BlogPreviewItem.tsx +++ b/src/components/BlogPreviewItem.tsx @@ -1,101 +1,31 @@ -import {useEffect, useState} from 'react'; +import {FunctionComponent, useContext, useState} from 'react'; import {useNavigate} from 'react-router-dom'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import DoDisturbIcon from '@mui/icons-material/DoDisturb'; -import {ErrorButton, OutlinedButton} from './Button'; -import {Blog, BlogContractData} from '../@types/interfaces'; -import {BlogContract} from '../@types/enums'; -import {useAppContext} from '../context/AppContext'; +import {ThemeContext} from '../context/ThemeContext'; +import DeleteBlogModal from './DeleteBlogModal'; +import {PostsContext} from '../context/PostsContext'; +import {BlogPost} from '../@types/types'; -const BlogPreviewItem = ({ +const BlogPreviewItem: FunctionComponent<{ + admin?: boolean; + deleted?: boolean; + data: BlogPost; +}> = ({ admin, data, deleted -}: { - admin?: boolean; - deleted?: boolean; - data: Blog & BlogContractData; }) => { - const {getAllBlogs, theme, setToast} = useAppContext(); + const {theme} = useContext(ThemeContext); + const {postSaving, deletePost, publishPost, unpublishPost} = useContext(PostsContext); const navigate = useNavigate(); - - const [loading, setLoading] = useState(false); const [requestDelete, setRequestDelete] = useState(false); - const [coverImage, setCoverImage] = useState(null); - const getCoverImage = async () => { - if (data.coverImage) { - const blob = await window.point.storage.getFile({id: data.coverImage}); - setCoverImage(blob); - } - }; - useEffect(() => { - getCoverImage(); - }, [data]); const handleDelete = async () => { - setLoading(true); - try { - await window.point.contract.send({ - contract: BlogContract.name, - method: BlogContract.deleteBlog, - params: [data.id] - }); - await getAllBlogs(); - setToast({color: 'green-500', message: 'Blog post moved to trash'}); - setRequestDelete(false); - } catch (error) { - setToast({ - color: 'red-500', - message: 'Failed to delete the blog post. Please try again' - }); - } - setLoading(false); - }; - - const handlePublish = async () => { - setLoading(true); - try { - await window.point.contract.send({ - contract: BlogContract.name, - method: BlogContract.publish, - params: [data.id] - }); - setToast({ - color: 'green-500', - message: 'Blog post published & moved to published successfully' - }); - await getAllBlogs(); - } catch (error) { - setToast({ - color: 'red-500', - message: 'Failed to publish the blog post. Please try again' - }); - } - setLoading(false); - }; - - const handleUnPublish = async () => { - setLoading(true); - try { - await window.point.contract.send({ - contract: BlogContract.name, - method: BlogContract.unpublish, - params: [data.id] - }); - setToast({ - color: 'green-500', - message: 'Blog post unpublished & moved to drafts successfully' - }); - await getAllBlogs(); - } catch (error) { - setToast({ - color: 'red-500', - message: 'Failed to unpublish the blog post. Please try again' - }); - } - setLoading(false); + await deletePost(data.id); + setRequestDelete(false); }; return ( @@ -104,33 +34,14 @@ const BlogPreviewItem = ({ admin ? 'mb-5' : '' } hover:shadow-lg border-opacity-20 relative`} > - {/* DELETE MODAL: START */} - {requestDelete ? ( -
-
-
-
-

- Are your sure you want to move this blog post to Trash? -

-
- setRequestDelete(false)}> - Cancel - - - Move to Trash - -
-
-
-
- ) : null} - {/* DELETE MODAL: END */} - {admin && !deleted ? ( + {requestDelete && {setRequestDelete(false);}} + deleting={postSaving} + />} + {admin && !deleted && ( <> - {data.previousStorageHashes?.length ? ( + {data.previousStorageHashes.length > 0 && (
- ) : null} + )}
{data.isPublished ? ( {unpublishPost(data.id);}} /> ) : ( {publishPost(data.id);}} /> )} navigate(`/edit?id=${data.storageHash}`)} + onClick={() => navigate(`/edit?id=${data.id}`)} />
- ) : null} + )}
- {coverImage ? ( + {data.coverImage ? ( cover for blog post @@ -184,7 +95,7 @@ const BlogPreviewItem = ({ className='flex-1 flex flex-col cursor-pointer' onClick={() => navigate( - `/blog?id=${data.storageHash}${deleted ? '?deleted=true' : ''}` + deleted ? `/deleted_blog?id=${data.id}` : `/blog?id=${data.id}` ) } > @@ -195,7 +106,7 @@ const BlogPreviewItem = ({ dangerouslySetInnerHTML={{__html: `

${data.content.slice(0, 200)}...

`}} >

- {data.tags.split(',').map((tag) => ( + {data.tags.split(',').filter(tag => Boolean(tag)).map((tag) => (

= ({ children, disabled = false, onClick -}: ButtonProps) => { - const {theme} = useAppContext(); +}) => { + const {theme} = useContext(ThemeContext); return (

- ) : null} + )}
- {avatar && ( + {userInfo.avatar && ( avatar @@ -134,11 +135,9 @@ const IdentityInfo = ({admin}: { admin?: boolean }) => {

{ownerIdentity}

{numFollowers} followers

-

{userInfo.data.about}

+

{userInfo.about}

- ) : ( - Loading User Info... ); }; diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx index e42e500..b54e367 100644 --- a/src/components/Loader.tsx +++ b/src/components/Loader.tsx @@ -1,8 +1,9 @@ import RefreshIcon from '@mui/icons-material/Refresh'; -import {useAppContext} from '../context/AppContext'; +import {FunctionComponent, PropsWithChildren, useContext} from 'react'; +import {ThemeContext} from '../context/ThemeContext'; -const Loader = ({children}: { children: string }) => { - const {theme} = useAppContext(); +const Loader: FunctionComponent = ({children}) => { + const {theme} = useContext(ThemeContext); return (
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 7775916..decffce 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,15 +1,15 @@ -import {Dispatch, SetStateAction} from 'react'; +import {ChangeEventHandler, FunctionComponent, useContext} from 'react'; import SearchIcon from '@mui/icons-material/Search'; -import {useAppContext} from '../context/AppContext'; +import {ThemeContext} from '../context/ThemeContext'; -const SearchBar = ({ +const SearchBar: FunctionComponent<{ + value: string; + onChange: ChangeEventHandler +}> = ({ value, onChange -}: { - value: string; - onChange: Dispatch>; }) => { - const {theme} = useAppContext(); + const {theme} = useContext(ThemeContext); return (
diff --git a/src/components/ToastNotification.tsx b/src/components/ToastNotification.tsx index a354145..f39d194 100644 --- a/src/components/ToastNotification.tsx +++ b/src/components/ToastNotification.tsx @@ -1,12 +1,8 @@ -import {useEffect} from 'react'; -import {useAppContext} from '../context/AppContext'; +import {FunctionComponent, useContext} from 'react'; +import {ToastContext} from '../context/ToastContext'; -const Alert = () => { - const {toast, setToast} = useAppContext(); - - useEffect(() => { - if (toast.message) {setTimeout(() => setToast({color: 'green-500', message: ''}), 3000);} - }, [toast]); +const Alert: FunctionComponent = () => { + const {toast} = useContext(ToastContext); return (
{}, - loading: true, - isOwner: false, - blogs: {loading: true, data: []}, - setBlogs: () => {}, - userInfo: { - loading: true, - data: {about: '', walletAddress: '', dataStorageHash: '', avatar: ''} - }, - getUserInfo: () => {}, - getAllBlogs: () => {}, - getDeletedBlogs: async () => [], - getDataFromStorage: async () => {}, - ownerIdentity: '', - visitorAddress: '', - ownerAddress: '', - visitorIdentity: '' -} as AppContentInterface); - -export const useAppContext = () => useContext(AppContext); - -export const ProvideAppContext = ({children}: { children: any }) => { - const navigate = useNavigate(); - - const [toast, setToast] = useState({ - color: 'green-500', - message: '' - }); - const [loading, setLoading] = useState(true); - const [isOwner, setIsOwner] = useState(false); - const [userInfo, setUserInfo] = useState({ - loading: true, - data: { - walletAddress: '', - about: '', - avatar: '', - dataStorageHash: '' - } - }); - const [visitorAddress, setVisitorAddress] = useState(''); - const [visitorIdentity, setVisitorIdentity] = useState(''); - const [ownerAddress, setOwnerAddress] = useState(''); - const [ownerIdentity, setOwnerIdentity] = useState(''); - const [theme, setTheme] = useState(['white', 'indigo', 'black']); - - const Blogs = useBlogs({setToast}); - - useEffect(() => { - (async () => { - setLoading(true); - - const {data}: { data: Theme } = await window.point.contract.call({ - contract: BlogContract.name, - method: BlogContract.getTheme - }); - setTheme(data); - - const {data: owner} = await window.point.contract.call({ - contract: BlogContract.name, - method: BlogContract.owner, - params: [] - }); - setOwnerAddress(owner); - - const ownerId = await utils.getIdentityFromAddress(owner); - setOwnerIdentity(ownerId); - - const visitor = await utils.getWalletAddress(); - setVisitorAddress(visitor); - - const visitorId = await utils.getIdentityFromAddress(visitor); - setVisitorIdentity(visitorId); - - const visitorIsOwner = visitor.toLowerCase() === owner.toLowerCase(); - setIsOwner(visitorIsOwner); - - const hash = await getUserInfo(); - if (!hash && visitorIsOwner) { - navigate(RoutesEnum.profile, {replace: true}); - } else { - Blogs.getAllBlogs(); - } - - setLoading(false); - })(); - }, []); - - const getUserInfo = async () => { - setUserInfo((prev) => ({...prev, loading: true})); - - const {data: [walletAddress, dataStorageHash]}: { data: [walletAddress: string, dataStorageHash: string] } = - await window.point.contract.call({ - contract: BlogContract.name, - method: BlogContract.getUserInfo - }); - if (dataStorageHash) { - const data = await utils.getDataFromStorage(dataStorageHash); - setUserInfo((prev) => ({ - ...prev, - loading: false, - data: {...data, walletAddress, dataStorageHash} - })); - } - return dataStorageHash; - }; - - return ( - - {children} - - ); -}; diff --git a/src/context/PostsContext.tsx b/src/context/PostsContext.tsx new file mode 100644 index 0000000..5236e0f --- /dev/null +++ b/src/context/PostsContext.tsx @@ -0,0 +1,283 @@ +import { + createContext, + FunctionComponent, + PropsWithChildren, + useContext, + useEffect, + useState +} from 'react'; +import {UserContext} from './UserContext'; +import {BlogContract} from '../@types/enums'; +import utils from './utils'; +import dayjs from 'dayjs'; +import {ToastContext} from './ToastContext'; +import {useNavigate} from 'react-router-dom'; +import {BlogPost} from '../@types/types'; + +type PostsContext = { + postsLoading: boolean; + postSaving: boolean; + postsError: boolean; + posts: BlogPost[]; + deletedPosts: BlogPost[]; + createOrEditPost: (params: Pick & { + publish: boolean; editId: number | null + }) => void; + deletePost: (id: number) => Promise; + publishPost: (id: number) => Promise; + unpublishPost: (id: number) => Promise; +} + +export const PostsContext = createContext({} as unknown as PostsContext); + +export const ProvidePostsContext: FunctionComponent = ({children}) => { + const navigate = useNavigate(); + const {userLoading, isOwner, ownerAddress} = useContext(UserContext); + const {setToast} = useContext(ToastContext); + const [postsLoading, setPostsLoading] = useState(true); + const [postSaving, setPostSaving] = useState(false); + const [postsError, setPostsError] = useState(false); + const [posts, setPosts] = useState([]); + const [deletedPosts, setDeletedPosts] = useState([]); + + const getPosts = async (deleted: boolean): Promise => { + const {data} = await window.point.contract.call({ + contract: BlogContract.name, + method: deleted ? BlogContract.getDeletedBlogs : BlogContract.getAllBlogs + }); + return Promise.all(data.map(async ([ + id, + storageHash, + isPublished, + publishDate, + previousStorageHashes, + tags + ]: [string, string, boolean, string, string[], string[]]) => { + const storageData = await utils.getDataFromStorage(storageHash); + return { + ...storageData, + coverImage: storageData.coverImage + ? await window.point.storage.getFile({id: storageData.coverImage}) + : null, + id: Number(id), + storageHash, + isPublished, + publishDate, + tags, + previousStorageHashes + }; + })); + }; + + const getData = async () => { + if (userLoading) return; + setPostsLoading(true); + setPostsError(false); + try { + const results = await Promise.all([ + getPosts(false), + ...(isOwner ? [getPosts(true)] : []) + ]); + setPosts(results[0].reverse()); + if (isOwner) { + setDeletedPosts(results[1].reverse()); + } + } catch (e) { + console.error(e); + setPostsError(true); + } + setPostsLoading(false); + }; + useEffect(() => { + getData(); + }, [userLoading, isOwner]); + + const createOrEditPost = async ({ + title, + content, + coverImage, + tags, + publish, + editId + }: Pick & { + publish: boolean; editId: number | null + }) => { + setPostSaving(true); + try { + const now = dayjs().format('MMM DD, YYYY'); + + let coverImageHash = ''; + if (coverImage) { + const coverImageFormData = new FormData(); + coverImageFormData.append('files', coverImage); + const {data} = await window.point.storage.postFile( + coverImageFormData + ); + coverImageHash = data; + } + + const form = JSON.stringify({ + coverImage: coverImageHash, + title, + content, + publisher: ownerAddress, + createdDate: now + }); + const file = new File([form], 'blog.json', {type: 'application/json'}); + + const formData = new FormData(); + formData.append('files', file); + const res = await window.point.storage.postFile(formData); + // Save data to smart contract + if (editId) { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.editBlog, + params: [ + editId, + res.data, + now, + tags + ] + }); + setToast({ + color: 'green-500', + message: 'Blog post updated successfully' + }); + setPosts(prevState => prevState.map(post => post.id === editId ? ( + { + ...post, + title, + content, + coverImage, + tags, + storageHash: res.data, + previousStorageHashes: [...post.previousStorageHashes, post.storageHash], + createdDate: now + } + ) : post)); + } else { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.addBlog, + params: [ + res.data, + publish, + now, + tags + ] + }); + setToast({ + color: 'green-500', + message: 'Blog post added successfully' + }); + setPosts(prevState => [ + ...prevState, + { + id: (prevState[0]?.id ?? 0) + 1, + title, + content, + coverImage, + tags, + isPublished: publish, + publisher: ownerAddress, + storageHash: res.data, + previousStorageHashes: [], + publishDate: now, + createdDate: now + } + ]); + } + navigate('/'); + } catch (e) { + console.error(e); + setToast({ + color: 'red-500', + message: 'Failed to save the blog post. Please try again' + }); + } + setPostSaving(false); + }; + + const deletePost = async (id: number) => { + setPostSaving(true); + try { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.deleteBlog, + params: [id] + }); + setToast({color: 'green-500', message: 'Blog post moved to trash'}); + setPosts(prevState => prevState.filter(post => post.id !== id)); + } catch (e) { + console.error(e); + setToast({ + color: 'red-500', + message: 'Failed to delete the blog post. Please try again' + }); + } + setPostSaving(false); + }; + + const publishPost = async (id: number) => { + setPostSaving(true); + try { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.publish, + params: [id] + }); + setToast({ + color: 'green-500', + message: 'Blog post published & moved to published successfully' + }); + setPosts(prevState => prevState.map(post => post.id === id + ? {...post, isPublished: true} + : post)); + } catch (error) { + setToast({ + color: 'red-500', + message: 'Failed to publish the blog post. Please try again' + }); + } + setPostSaving(false); + }; + + const unpublishPost = async (id: number) => { + setPostSaving(true); + try { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.unpublish, + params: [id] + }); + setToast({ + color: 'green-500', + message: 'Blog post unpublished & moved to drafts successfully' + }); + setPosts(prevState => prevState.map(post => post.id === id + ? {...post, isPublished: false} + : post)); + } catch (error) { + setToast({ + color: 'red-500', + message: 'Failed to unpublish the blog post. Please try again' + }); + } + setPostSaving(false); + }; + + return + {children} + ; +}; diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx new file mode 100644 index 0000000..bb5352a --- /dev/null +++ b/src/context/ThemeContext.tsx @@ -0,0 +1,66 @@ +import { + createContext, + Dispatch, + FunctionComponent, PropsWithChildren, + SetStateAction, + useContext, useEffect, + useState +} from 'react'; +import {Theme} from '../@types/types'; +import {BlogContract} from '../@types/enums'; +import {ToastContext} from './ToastContext'; + +type ThemeContext = { + theme: Theme; + themeLoading: boolean; + setTheme: Dispatch> + saveTheme: () => Promise +} + +export const ThemeContext = createContext({} as unknown as ThemeContext); + +export const ProvideThemeContext: FunctionComponent = ({children}) => { + const {setToast} = useContext(ToastContext); + const [themeLoading, setThemeLoading] = useState(false); + const [theme, setTheme] = useState(['white', 'indigo', 'black']); + + const getTheme = async () => { + setThemeLoading(true); + try { + const {data}: { data: Theme } = await window.point.contract.call({ + contract: BlogContract.name, + method: BlogContract.getTheme + }); + setTheme(data); + } catch (e) { + setToast({ + color: 'red-500', + message: 'Failed to load the theme. Please try again' + }); + } + setThemeLoading(false); + }; + useEffect(() => { + getTheme(); + }, []); + + const saveTheme = async () => { + try { + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.setTheme, + params: [...theme] + }); + setToast({color: 'green-500', message: 'Theme updated successfully'}); + } catch (error) { + setToast({ + color: 'red-500', + message: 'Failed to save the theme. Please try again' + }); + } + }; + + return + {children} + ; +}; diff --git a/src/context/ToastContext.tsx b/src/context/ToastContext.tsx new file mode 100644 index 0000000..6d83d62 --- /dev/null +++ b/src/context/ToastContext.tsx @@ -0,0 +1,37 @@ +import { + createContext, + Dispatch, + FunctionComponent, + PropsWithChildren, + SetStateAction, + useEffect, + useState +} from 'react'; +import {ToastNotification} from '../@types/types'; + +type ToastContext = { + toast: ToastNotification + setToast: Dispatch> +} + +export const ToastContext = createContext({} as unknown as ToastContext); + +export const ProvideToastContext: FunctionComponent = ({children}) => { + const [toast, setToast] = useState({ + color: 'green-500', + message: '' + }); + + useEffect(() => { + if (toast.message) { + setTimeout( + () => setToast({color: 'green-500', message: ''}), + 3000 + ); + } + }, [toast]); + + return + {children} + ; +}; diff --git a/src/context/UserContext.tsx b/src/context/UserContext.tsx new file mode 100644 index 0000000..a8d5901 --- /dev/null +++ b/src/context/UserContext.tsx @@ -0,0 +1,150 @@ +import { + createContext, + FunctionComponent, + PropsWithChildren, + useContext, + useEffect, + useState +} from 'react'; +import {BlogContract, RoutesEnum} from '../@types/enums'; +import utils from './utils'; +import {useNavigate} from 'react-router-dom'; +import {ToastContext} from './ToastContext'; +import {UserInfo} from '../@types/types'; + +type UserContext = { + userLoading: boolean; + userSaving: boolean; + userError: boolean; + userInfo: UserInfo; + visitorAddress: string; + visitorIdentity: string; + ownerAddress: string; + ownerIdentity: string; + isOwner: boolean; + saveUserInfo: (info: Pick) => Promise; +} + +export const UserContext = createContext({} as unknown as UserContext); + +export const ProvideUserContext: FunctionComponent = ({children}) => { + const navigate = useNavigate(); + const {setToast} = useContext(ToastContext); + + const [userLoading, setUserLoading] = useState(true); + const [userSaving, setUserSaving] = useState(false); + const [userError, setUserError] = useState(false); + const [userInfo, setUserInfo] = useState({ + about: '', + avatar: null + }); + const [visitorAddress, setVisitorAddress] = useState(''); + const [visitorIdentity, setVisitorIdentity] = useState(''); + const [ownerAddress, setOwnerAddress] = useState(''); + const [ownerIdentity, setOwnerIdentity] = useState(''); + const [isOwner, setIsOwner] = useState(false); + + const getData = async () => { + setUserLoading(true); + setUserError(false); + try { + const [ + {data: owner}, + visitor, + {data: dataStorageHash} + ] = await Promise.all([ + window.point.contract.call({ + contract: BlogContract.name, + method: BlogContract.owner, + params: [] + }), + utils.getWalletAddress(), + window.point.contract.call({ + contract: BlogContract.name, + method: BlogContract.getUserInfo + }) + ]); + setOwnerAddress(owner); + setVisitorAddress(visitor); + const _isOwner = visitor.toLowerCase() === owner.toLowerCase(); + setIsOwner(_isOwner); + + const [ownerId, visitorId] = await Promise.all([ + utils.getIdentityFromAddress(owner), + utils.getIdentityFromAddress(visitor) + ]); + setOwnerIdentity(ownerId); + setVisitorIdentity(visitorId); + + if (dataStorageHash) { + const {about, avatar} = await utils.getDataFromStorage(dataStorageHash); + setUserInfo({ + about, + avatar: avatar ? await window.point.storage.getFile({id: avatar}) : null + }); + } else if (_isOwner) { + navigate(RoutesEnum.profile, {replace: true}); + } + } catch (e) { + console.error(e); + setUserError(true); + } + setUserLoading(false); + }; + useEffect(() => { + getData(); + }, []); + + const saveUserInfo = async (info: Pick) => { + setUserSaving(true); + try { + let avatarImage = ''; + if (info.avatar) { + const avatarFormData = new FormData(); + avatarFormData.append('files', info.avatar); + const {data} = await window.point.storage.postFile(avatarFormData); + avatarImage = data; + } + const form = JSON.stringify({ + avatar: avatarImage, + about: info.about + }); + const file = new File([form], 'user.json', {type: 'application/json'}); + + const formData = new FormData(); + formData.append('files', file); + const res = await window.point.storage.postFile(formData); + + await window.point.contract.send({ + contract: BlogContract.name, + method: BlogContract.saveUserInfo, + params: [res.data] + }); + setUserInfo(prevState => ({...prevState, ...info})); + setToast({color: 'green-500', message: 'Profile saved successfully'}); + navigate(RoutesEnum.admin); + } catch (e) { + console.error(e); + setToast({ + color: 'red-500', + message: 'Failed to save the profile' + }); + } + setUserSaving(false); + }; + + return + {children} + ; +}; diff --git a/src/context/useBlogs.ts b/src/context/useBlogs.ts deleted file mode 100644 index 80e96db..0000000 --- a/src/context/useBlogs.ts +++ /dev/null @@ -1,89 +0,0 @@ -import {useState, SetStateAction, Dispatch} from 'react'; -import {BlogContract} from '../@types/enums'; -import { - Blog, - BlogContractData, - BlogsState, - ToastNotification -} from '../@types/interfaces'; -import utils from './utils'; - -const useBlogs = ({setToast}: { - setToast: Dispatch>; -}) => { - const [blogs, setBlogs] = useState({ - loading: true, - data: [] - }); - - const getAllBlogs = async () => { - try { - setBlogs((prev) => ({...prev, loading: true})); - - const {data}: { data: any[] } = await window.point.contract.call({ - contract: BlogContract.name, - method: BlogContract.getAllBlogs - }); - const blogs = await Promise.all( - data.map(async (contractData) => { - const [ - id, - storageHash, - isPublished, - publishDate, - previousStorageHashes, - tags - ] = contractData; - const data = await utils.getDataFromStorage(storageHash); - return { - ...data, - id, - storageHash, - isPublished, - publishDate, - tags, - previousStorageHashes: previousStorageHashes.reverse() - } as Blog & BlogContractData; - }) - ); - setBlogs({loading: false, data: blogs}); - } catch (error) { - setToast({ - color: 'red-500', - message: 'Failed to load blog posts. Please reload the page' - }); - } - }; - - const getDeletedBlogs = async () => { - try { - const {data}: { data: any[] } = await window.point.contract.call({ - contract: BlogContract.name, - method: BlogContract.getDeletedBlogs - }); - const blogs = await Promise.all( - data.map(async (contractData) => { - const [id, storageHash, isPublished, publishDate] = contractData; - const data = await utils.getDataFromStorage(storageHash); - return { - ...data, - id, - storageHash, - isPublished, - publishDate - } as Blog & BlogContractData; - }) - ); - return blogs; - } catch (error) { - setToast({ - color: 'red-500', - message: 'Failed to load deleted blog posts. Please reload the page' - }); - } - }; - - return {blogs, getAllBlogs, getDeletedBlogs}; -}; - -export default useBlogs; diff --git a/src/context/utils.ts b/src/context/utils.ts index e7015da..92a89a1 100644 --- a/src/context/utils.ts +++ b/src/context/utils.ts @@ -1,5 +1,3 @@ -import axios from 'axios'; - const getWalletAddress = async () => { const {data: {address}} = await window.point.wallet.address(); return address; @@ -26,4 +24,5 @@ const utils = Object.freeze({ getIdentityFromAddress, getDataFromStorage }); + export default utils; diff --git a/src/index.js b/src/index.js index 2d53a27..73deff5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; -import reportWebVitals from './reportWebVitals'; import './custom.css'; ReactDOM.render( @@ -14,4 +13,4 @@ ReactDOM.render( // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +// reportWebVitals(); diff --git a/src/layouts/PageLayout.tsx b/src/layouts/PageLayout.tsx index b03e58e..57f77c9 100644 --- a/src/layouts/PageLayout.tsx +++ b/src/layouts/PageLayout.tsx @@ -1,12 +1,25 @@ import Loader from '../components/Loader'; -import {useAppContext} from '../context/AppContext'; +import {FunctionComponent, PropsWithChildren, useContext} from 'react'; +import {UserContext} from '../context/UserContext'; +import {ThemeContext} from '../context/ThemeContext'; +import ErrorPlaceholder from '../components/ErrorPlaceholder'; -const PageLayout = ({children}: { children: any }) => { - const {loading, theme} = useAppContext(); +const PageLayout: FunctionComponent> = ({ + children, + loading, + error +}) => { + const {userLoading, userError} = useContext(UserContext); + const {theme, themeLoading} = useContext(ThemeContext); return (
- {loading ? ( + {userError || error ? ( + + ) : userLoading || themeLoading || loading ? (
Loading...
diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx index 9ba03c1..037a48e 100644 --- a/src/pages/Admin.tsx +++ b/src/pages/Admin.tsx @@ -1,21 +1,28 @@ -import {useCallback, useEffect, useState} from 'react'; -import {useNavigate} from 'react-router-dom'; -import _debounce from 'lodash/debounce'; +import { + ChangeEvent, + FunctionComponent, + useContext, + useEffect, + useMemo, + useState +} from 'react'; +import {useLocation, useNavigate} from 'react-router-dom'; import BlogPreviewItem from '../components/BlogPreviewItem'; import Header from '../components/Header'; import IdentityInfo from '../components/IdentityInfo'; import Loader from '../components/Loader'; import {PrimaryButton} from '../components/Button'; -import {useAppContext} from '../context/AppContext'; -import {Blog, BlogContractData} from '../@types/interfaces'; import {RoutesEnum} from '../@types/enums'; import PageLayout from '../layouts/PageLayout'; import SearchBar from '../components/SearchBar'; +import {ThemeContext} from '../context/ThemeContext'; +import {UserContext} from '../context/UserContext'; +import {PostsContext} from '../context/PostsContext'; enum BlogFilterOptions { - published = 'published', - drafts = 'drafts', - trash = 'trash', + published = 'published', + drafts = 'drafts', + trash = 'trash', } const emptyMessages = { @@ -29,94 +36,63 @@ const FilterOption = ({ filter, onClick }: { - children: string; - filter: string; - onClick: any; + children: string; + filter: string; + onClick: (value: string) => void }) => { - const {theme} = useAppContext(); + const {theme} = useContext(ThemeContext); return (
{onClick(children);}} > {children}
); }; -const Admin = () => { +const Admin: FunctionComponent = () => { const navigate = useNavigate(); - const {blogs, getDeletedBlogs, isOwner, loading} = useAppContext(); - - const [isLoading, setLoading] = useState(false); + const {isOwner, userLoading} = useContext(UserContext); + const {posts, deletedPosts, postsLoading} = useContext(PostsContext); const [searchTerm, setSearchTerm] = useState(''); - const [data, setData] = useState<(Blog & BlogContractData)[]>([]); - const [filter, setFilter] = useState( - new URL(window.location.href).searchParams.get( - 'filter' - ) as keyof typeof BlogFilterOptions - ); - useEffect(() => { - if (!loading && !isOwner) navigate(RoutesEnum.home, {replace: true}); - }, [isOwner, loading]); + const {search} = useLocation(); + const query = useMemo(() => new URLSearchParams(search), [search]); + const filter = query.get('filter') as keyof typeof BlogFilterOptions ?? BlogFilterOptions.published; - useEffect(() => { - (async () => { - if (!filter) setFilter(BlogFilterOptions.published); - setLoading(true); - setData(await handleDataChange(filter)); - setLoading(false); - setSearchTerm(''); - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filter, blogs.data]); + const displayedPosts = useMemo(() => { + const unfilteredPosts = filter === BlogFilterOptions.trash ? deletedPosts : posts; + const filteredPosts = unfilteredPosts + .filter(p => filter === BlogFilterOptions.published + ? p.isPublished + : filter === BlogFilterOptions.drafts + ? !p.isPublished + : true); + return searchTerm ? filteredPosts.filter( + (p) => + p.title.toLowerCase().includes(searchTerm.toLowerCase()) || + p.tags.toLowerCase().includes(searchTerm.toLowerCase()) + ) : filteredPosts; + }, [posts, deletedPosts, filter, searchTerm]); - const handleDataChange = async ( - filter: string - ): Promise<(Blog & BlogContractData)[]> => { - switch (filter) { - case BlogFilterOptions.published: - return blogs.data.filter((d) => d.isPublished); - case BlogFilterOptions.drafts: - return blogs.data.filter((d) => !d.isPublished); - case BlogFilterOptions.trash: - return await getDeletedBlogs(); - default: - return []; + useEffect(() => { + if (!userLoading && !isOwner) { + navigate(RoutesEnum.home, {replace: true}); } - }; + }, [isOwner, userLoading]); - const handleFilterChange = (e: any) => { - navigate(`${RoutesEnum.admin}?filter=${e.target.id}`); - setFilter(e.target.id); + const handleFilterChange = (value: string) => { + navigate(`${RoutesEnum.admin}?filter=${value}`); }; - const debounceFn = useCallback(_debounce(handleDebounceFn, 500), [ - blogs, - filter - ]); - - async function handleDebounceFn(inputValue: string) { - const _data = await handleDataChange(filter); - setData( - _data.filter( - (blog) => - blog.title.toLowerCase().includes(inputValue.toLowerCase()) || - blog.tags.toLowerCase().includes(inputValue.toLowerCase()) - ) - ); - } - - function handleChange(event: any) { + function handleChange(event: ChangeEvent) { setSearchTerm(event.target.value); - debounceFn(event.target.value); } return ( @@ -131,7 +107,7 @@ const Admin = () => {

Your Blog Posts

navigate(RoutesEnum.create)}> - Create New Blog Post + Create New Blog Post
@@ -150,23 +126,23 @@ const Admin = () => { className='overflow-y-scroll pr-6' style={{height: window.screen.height - 360}} > - {isLoading || blogs.loading ? ( + {postsLoading ? ( Loading Blog Posts... - ) : data.length ? ( - data.map((blog, i) => ( + ) : displayedPosts.length ? ( + displayedPosts.map((post) => ( )) ) : (

- {!searchTerm - ? emptyMessages[filter] - : `No blogs found for search term: "${searchTerm}"`} + {searchTerm + ? `No blogs found for search term: "${searchTerm}"` + : emptyMessages[filter]}

)} diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 43bfd77..47835c0 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -1,7 +1,6 @@ -import {useEffect, useMemo, useState} from 'react'; +import {FunctionComponent, useContext, useEffect, useMemo, useState} from 'react'; import Header from '../components/Header'; import {useLocation, useNavigate} from 'react-router-dom'; -import {useAppContext} from '../context/AppContext'; import PageLayout from '../layouts/PageLayout'; import {OutlinedButton, PrimaryButton} from '../components/Button'; import utils from '../context/utils'; @@ -9,90 +8,86 @@ import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import RecommendIcon from '@mui/icons-material/Recommend'; -import {Comment} from '../@types/types'; +import {BlogPost, Comment} from '../@types/types'; import {BlogContract, RoutesEnum} from '../@types/enums'; -import {Blog, BlogContractData} from '../@types/interfaces'; +import {PostsContext} from '../context/PostsContext'; +import {ToastContext} from '../context/ToastContext'; +import {UserContext} from '../context/UserContext'; +import {ThemeContext} from '../context/ThemeContext'; -const BlogPage = () => { +const BlogPage: FunctionComponent<{deleted?: boolean}> = ({deleted}) => { + const navigate = useNavigate(); const {search} = useLocation(); const query = useMemo(() => new URLSearchParams(search), [search]); - const isDeleted = query.get('deleted'); - const id = query.get('id'); + const id = Number(query.get('id')); + + const {theme} = useContext(ThemeContext); + const {visitorAddress, isOwner, visitorIdentity} = useContext(UserContext); + const {setToast} = useContext(ToastContext); + const {posts, deletedPosts, postsError, postsLoading} = useContext(PostsContext); + const post = useMemo( + () => deleted ? deletedPosts.find(p => p.id === id) : posts.find(p => p.id === id), + [posts, deletedPosts] + ); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); - const { - blogs, - getDataFromStorage, - visitorAddress, - isOwner, - getDeletedBlogs, - theme, - setToast - } = useAppContext(); + const [displayedRevision, setDisplayedRevision] = useState(0); + const [revisions, setRevisions] = useState< + Pick[] + >([]); + const displayedData = displayedRevision === 0 ? post! : revisions[displayedRevision - 1]!; - const [original, setOriginal] = useState< - (Omit & BlogContractData) | undefined - >(); - const [selectedHash, setSelectedHash] = useState(''); - const [displayData, setDisplayData] = useState< - | (Omit & BlogContractData & { coverImage?: File }) - | undefined - >(); const [isLiked, setIsLiked] = useState(false); const [numLikes, setNumLikes] = useState(0); - const [editCommentId, setEditCommentId] = useState(''); + + const [editCommentId, setEditCommentId] = useState(null); const [commentText, setCommentText] = useState(''); const [comments, setComments] = useState([]); - const navigate = useNavigate(); - - useEffect(() => { - (async () => { - if (id && !blogs.loading) { - const _blogs = isDeleted ? await getDeletedBlogs() : blogs.data; - const requiredBlog = _blogs.find((blog) => blog.storageHash === id); - if (requiredBlog) { - const _displayData = {...requiredBlog, coverImage: undefined}; - if (requiredBlog?.coverImage) { - const blob = await window.point.storage.getFile({id: requiredBlog.coverImage}); - _displayData.coverImage = blob; - } - setOriginal(_displayData); - setDisplayData(_displayData); - setSelectedHash(id); - } - } - })(); - }, [id, blogs]); + const getRevisions = async () => { + if (!post) return; + const _revisions = await Promise.all(post.previousStorageHashes.map(async hash => { + const storageData = await utils.getDataFromStorage(hash); + return { + coverImage: storageData.coverImage + ? await window.point.storage.getFile({id: storageData.coverImage}) + : null, + title: storageData.title, + content: storageData.content, + createdDate: storageData.createdDate + }; + })); + setRevisions(_revisions); + }; const getLikesForBlogPost = async () => { const {data}: { data: string[] } = await window.point.contract.call({ contract: BlogContract.name, method: BlogContract.getLikesForBlogPost, - params: [original?.id] + params: [id] }); - if ( - data.map((i) => i.toLowerCase()).includes(visitorAddress.toLowerCase()) - ) { - setIsLiked(true); - } else setIsLiked(false); + setIsLiked(data.map((i) => i.toLowerCase()).includes(visitorAddress.toLowerCase())); setNumLikes(data.length); }; const getCommentsForBlogPost = async () => { try { - const {data}: { data: Comment[] } = await window.point.contract.call({ + const {data}: { data: [string, string, string] } = await window.point.contract.call({ contract: BlogContract.name, method: BlogContract.getCommentsForBlogPost, - params: [original?.id] + params: [id] }); const _comments = await Promise.all( data.map(async ([_id, commentedBy, comment]) => { const identity = await utils.getIdentityFromAddress(commentedBy); - return [_id, commentedBy, comment, identity] as Comment; + return {id: Number(_id), commentedBy, comment, identity}; }) ); setComments(_comments.reverse()); - } catch (error) { + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to load comments for the blog post' @@ -100,42 +95,47 @@ const BlogPage = () => { } }; + const getData = async () => { + setLoading(true); + try { + await Promise.all([ + getLikesForBlogPost(), + getCommentsForBlogPost(), + ...(isOwner ? [getRevisions()] : []) + ]); + } catch (e) { + console.error(e); + setError(true); + } + setLoading(false); + }; + useEffect(() => { - if (original) { - getLikesForBlogPost(); - getCommentsForBlogPost(); + if (post) { + getData(); } - }, [original]); + }, [post]); useEffect(() => { if (editCommentId) { - setCommentText(comments.find(([_id]) => _id === editCommentId)![2]); + setCommentText(comments.find((c) => c.id === editCommentId)!.comment); } else { setCommentText(''); } }, [editCommentId]); - const handleIterationSelect = async (hash: string) => { - const requiredBlog = await getDataFromStorage(hash); - setDisplayData(requiredBlog); - setSelectedHash(hash); - }; - - const handleSelectLatest = () => { - setDisplayData(original); - setSelectedHash(id!); - }; - const handleLike = async () => { try { await window.point.contract.send({ contract: BlogContract.name, method: BlogContract.likeBlogPost, - params: [original?.id] + params: [id] }); setToast({color: 'green-500', message: 'Post liked successfully'}); - getLikesForBlogPost(); - } catch (error) { + setIsLiked(true); + setNumLikes(prevState => prevState + 1); + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to like the blog post. Please try again' @@ -148,11 +148,13 @@ const BlogPage = () => { await window.point.contract.send({ contract: BlogContract.name, method: BlogContract.unlikeBlogPost, - params: [original?.id] + params: [id] }); setToast({color: 'green-500', message: 'Post unliked successfully'}); - getLikesForBlogPost(); - } catch (error) { + setIsLiked(false); + setNumLikes(prevState => prevState - 1); + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to unlike the blog post. Please try again' @@ -165,12 +167,18 @@ const BlogPage = () => { await window.point.contract.send({ contract: BlogContract.name, method: BlogContract.addCommentToBlogPost, - params: [original?.id, commentText] + params: [id, commentText] }); setToast({color: 'green-500', message: 'Comment added successfully'}); setCommentText(''); - getCommentsForBlogPost(); - } catch (error) { + setComments(prevState => [{ + id: (prevState[0]?.id ?? 0) + 1, + comment: commentText, + commentedBy: visitorAddress, + identity: visitorIdentity + }, ...prevState]); + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to add the comment. Please try again' @@ -178,17 +186,18 @@ const BlogPage = () => { } }; - const handleDeleteComment = async (commentId: string) => { + const handleDeleteComment = async (commentId: number) => { try { await window.point.contract.send({ contract: BlogContract.name, method: BlogContract.deleteCommentForBlogPost, - params: [original?.id, commentId] + params: [id, commentId] }); setToast({color: 'green-500', message: 'Comment deleted successfully'}); setCommentText(''); - getCommentsForBlogPost(); - } catch (error) { + setComments(prevState => prevState.filter(c => c.id !== commentId)); + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to delete the comment. Please try again' @@ -201,12 +210,16 @@ const BlogPage = () => { await window.point.contract.send({ contract: BlogContract.name, method: BlogContract.editCommentForBlogPost, - params: [original?.id, editCommentId, commentText] + params: [id, editCommentId, commentText] }); setToast({color: 'green-500', message: 'Comment updated successfully'}); - setEditCommentId(''); - getCommentsForBlogPost(); - } catch (error) { + setEditCommentId(null); + setComments(prevState => prevState.map(comment => comment.id === editCommentId + ? {...comment, comment: commentText} + : comment) + ); + } catch (e) { + console.error(e); setToast({ color: 'red-500', message: 'Failed to update the comment. Please try again' @@ -214,87 +227,105 @@ const BlogPage = () => { } }; + if (loading || postsLoading) return ; + if (error || postsError) return ; + + if (!post) { + return ( +
+

+ This blog post does not exist. +

+ navigate(RoutesEnum.home)}> + Home + +
+ ); + } + return (
- {original ? ( - <> -
navigate(-1)} +
navigate(-1)} + > + + Back +
+ + {isOwner && post.previousStorageHashes && ( +
+

+ Iterations: +

+

{setDisplayedRevision(0);}} > - - Back -

+ {post.previousStorageHashes.length + 1} +

+ {post.previousStorageHashes.map((hash, i) => ( +

setDisplayedRevision(i + 1)} + > + {post.previousStorageHashes.length - i} +

+ ))} +
+ )} - {isOwner && original?.previousStorageHashes ? ( -
-

- Iterations: -

-

- {original?.previousStorageHashes.length! + 1} -

- {original?.previousStorageHashes.map((hash, i) => ( -

handleIterationSelect(hash)} - > - {original?.previousStorageHashes.length - i} -

- ))} -
- ) : null} +

{post.title}

+

+ {post.publishDate} +

+ {displayedData.coverImage ? ( +
+ cover of the blog +
+ ) : null} +
-

{displayData?.title}

-

- {displayData?.publishDate} + {!deleted && ( +

+ +

+ {numLikes} Like{String(numLikes).slice(-1) === '1' ? '' : 's'}

- {displayData?.coverImage ? ( -
- cover of the blog -
- ) : null} -
- - {!isDeleted ? ( -
- -

{numLikes} Likes

-
- ) : null} +
+ )} -
+
+ {post.tags.split(',').filter(tag => Boolean(tag)).length > 0 ? ( + <>
Tags:
- {original.tags.split(',').map((tag) => ( + {post.tags.split(',').filter(tag => Boolean(tag)).map((tag) => (

{ {tag}

))} -
+ + ) :
No Tags
} -
-
-
- {isDeleted ? 'Comments' : 'Leave a comment'} -
- {!isDeleted ? ( - <> - - {editCommentId ? ( -
- - Update Comment - - setEditCommentId('')}> - Cancel - -
- ) : ( - - Add Comment - - )} - - ) : null} -
- {comments.length ? ( - comments.map(([id, commentedBy, comment, identity]) => ( -
-
-

{identity}

- {commentedBy.toLowerCase() === - visitorAddress.toLowerCase() ? ( -
- setEditCommentId(id)} - className={`text-${theme[2]} opacity-50 hover:opacity-90 cursor-pointer transition-all`} - sx={{width: 18, height: 18}} - /> - handleDeleteComment(id)} - className='text-red-400 hover:text-red-500 cursor-pointer transition-all' - sx={{width: 18, height: 18}} - /> -
- ) : null} -
-

{comment}

+
+ +
+
+
+ {deleted ? 'Comments' : 'Leave a comment'} +
+ {!deleted && ( + <> +