From b1fd3a3df0649958822e3f4e9bf8261da0b9c566 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Mon, 19 Feb 2024 09:46:38 -0500 Subject: [PATCH] feat: Shareable file resource link (#1451) --- package-lock.json | 210 ++------------ package.json | 3 +- src/common/components/CopyLink.js | 85 ++++++ src/common/components/SocialShare.js | 80 +----- src/custom-hooks.js | 14 + src/group/components/GroupForm.js | 5 +- src/group/components/GroupView.test.js | 194 +++++++++++-- src/group/groupFragments.ts | 3 + src/group/groupService.test.ts | 2 +- src/group/groupSlice.ts | 2 +- src/group/groupUtils.js | 4 +- src/landscape/landscapeFragments.js | 3 + src/landscape/landscapeService.test.ts | 2 +- src/landscape/landscapeSlice.js | 2 +- src/landscape/landscapeUtils.js | 4 +- src/localization/locales/en-US.json | 16 +- src/localization/locales/es-ES.json | 16 +- src/navigation/components/Routes.js | 19 ++ src/sharedData/components/SharedDataCard.js | 26 +- .../components/SharedDataEntryBase.js | 62 ++++- .../components/SharedDataEntryFile.js | 256 +++++++++++++++++- .../components/SharedDataEntryLink.js | 14 +- .../components/SharedResourceDownload.js | 99 +++++++ .../components/SharedResourceDownload.test.js | 116 ++++++++ src/sharedData/sharedDataConstants.js | 4 + src/sharedData/sharedDataService.js | 51 +++- src/sharedData/sharedDataSlice.js | 113 +++++++- src/sharedData/sharedDataUtils.js | 12 +- 28 files changed, 1057 insertions(+), 360 deletions(-) create mode 100644 src/common/components/CopyLink.js create mode 100644 src/sharedData/components/SharedResourceDownload.js create mode 100644 src/sharedData/components/SharedResourceDownload.test.js diff --git a/package-lock.json b/package-lock.json index cedcc8522..ffe9a36d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "slate-hyperscript": "^0.100.0", "slate-react": "^0.101.0", "source-map-explorer": "^2.5.3", - "terraso-client-shared": "github:techmatters/terraso-client-shared#a416b76", + "terraso-client-shared": "github:techmatters/terraso-client-shared#1b54c54228a95025201c2b57ec458ba62c195769", "use-debounce": "^9.0.4", "uuid": "^9.0.1", "web-vitals": "^3.5.0", @@ -78,6 +78,7 @@ "flat": "^6.0.1", "i18next-conv": "^14.0.0", "jest-axe": "^8.0.0", + "jest-when": "^3.6.0", "plausible-tracker": "^0.3.8", "prettier": "^3.2.5", "react-scripts": "^5.0.1", @@ -4479,7 +4480,6 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -4498,7 +4498,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -4515,7 +4514,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -4533,7 +4531,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -4547,7 +4544,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/@jest/console/node_modules/has-flag": { @@ -4555,7 +4551,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -4566,7 +4561,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -4580,7 +4574,6 @@ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/console": "^29.7.0", @@ -4629,7 +4622,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -4657,7 +4649,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -4674,7 +4665,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -4692,7 +4682,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -4706,7 +4695,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/@jest/core/node_modules/has-flag": { @@ -4714,7 +4702,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -4725,7 +4712,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -4752,7 +4738,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4763,7 +4748,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -4785,7 +4769,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -4804,7 +4787,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -4821,7 +4803,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -4838,7 +4819,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -4854,7 +4834,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -4868,7 +4847,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -4879,7 +4857,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -4893,7 +4870,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -4908,7 +4884,6 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", @@ -4925,7 +4900,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "expect": "^29.7.0", @@ -4952,7 +4926,6 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -4971,7 +4944,6 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/environment": "^29.7.0", @@ -4988,7 +4960,6 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", @@ -5033,7 +5004,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -5061,7 +5031,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -5078,7 +5047,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -5096,7 +5064,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -5110,7 +5077,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/@jest/reporters/node_modules/has-flag": { @@ -5118,7 +5084,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -5129,7 +5094,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.12.3", @@ -5147,7 +5111,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -5174,7 +5137,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5185,7 +5147,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -5202,7 +5163,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -5219,7 +5179,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -5233,7 +5192,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5250,7 +5208,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -5264,7 +5221,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -5279,7 +5235,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "optional": true, "peer": true }, "node_modules/@jest/schemas": { @@ -5299,7 +5254,6 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -5315,7 +5269,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/console": "^29.7.0", @@ -5332,7 +5285,6 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", @@ -5349,7 +5301,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -5360,7 +5311,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -5387,7 +5337,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5398,7 +5347,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -5415,7 +5363,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -6948,7 +6895,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "type-detect": "4.0.8" @@ -6959,7 +6905,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -11410,7 +11355,6 @@ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -11433,7 +11377,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -11450,7 +11393,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -11468,7 +11410,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -11482,7 +11423,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/create-jest/node_modules/has-flag": { @@ -11490,7 +11430,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -11501,7 +11440,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -12200,7 +12138,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, - "optional": true, "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -13062,7 +12999,6 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=12" @@ -17569,7 +17505,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/core": "^29.7.0", @@ -17723,7 +17658,6 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "optional": true, "peer": true, "dependencies": { "execa": "^5.0.0", @@ -17739,7 +17673,6 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/environment": "^29.7.0", @@ -17772,7 +17705,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -17789,7 +17721,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -17807,7 +17738,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -17821,7 +17751,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-circus/node_modules/has-flag": { @@ -17829,7 +17758,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -17840,7 +17768,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -17856,7 +17783,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -17870,7 +17796,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -17884,7 +17809,6 @@ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/core": "^29.7.0", @@ -17919,7 +17843,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -17936,7 +17859,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -17954,7 +17876,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -17968,7 +17889,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-cli/node_modules/has-flag": { @@ -17976,7 +17896,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -17987,7 +17906,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -18006,7 +17924,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -18022,7 +17939,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -18036,7 +17952,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -18050,7 +17965,6 @@ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -18097,7 +18011,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -18125,7 +18038,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -18142,7 +18054,6 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/transform": "^29.7.0", @@ -18165,7 +18076,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/template": "^7.3.3", @@ -18182,7 +18092,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", @@ -18200,7 +18109,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -18218,7 +18126,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -18232,7 +18139,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-config/node_modules/has-flag": { @@ -18240,7 +18146,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -18251,7 +18156,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -18278,7 +18182,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -18289,7 +18192,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -18311,7 +18213,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -18330,7 +18231,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -18347,7 +18247,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -18364,7 +18263,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -18380,7 +18278,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -18394,7 +18291,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -18405,7 +18301,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -18419,7 +18314,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -18545,7 +18439,6 @@ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "optional": true, "peer": true, "dependencies": { "detect-newline": "^3.0.0" @@ -18559,7 +18452,6 @@ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -18577,7 +18469,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -18594,7 +18485,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -18612,7 +18502,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -18626,7 +18515,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-each/node_modules/has-flag": { @@ -18634,7 +18522,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -18645,7 +18532,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -18661,7 +18547,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -18675,7 +18560,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -18902,7 +18786,6 @@ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/environment": "^29.7.0", @@ -19538,7 +19421,6 @@ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "jest-get-type": "^29.6.3", @@ -19553,7 +19435,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -19567,7 +19448,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -19810,7 +19690,6 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -19873,7 +19752,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", @@ -19888,7 +19766,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -20011,7 +19888,6 @@ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/console": "^29.7.0", @@ -20045,7 +19921,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -20073,7 +19948,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -20090,7 +19964,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -20108,7 +19981,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -20122,7 +19994,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-runner/node_modules/has-flag": { @@ -20130,7 +20001,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -20141,7 +20011,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -20168,7 +20037,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -20179,7 +20047,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -20201,7 +20068,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -20220,7 +20086,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -20237,7 +20102,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20254,7 +20118,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -20270,7 +20133,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -20284,7 +20146,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -20295,7 +20156,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=0.10.0" @@ -20306,7 +20166,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "optional": true, "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -20318,7 +20177,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20332,7 +20190,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -20347,7 +20204,6 @@ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/environment": "^29.7.0", @@ -20382,7 +20238,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -20410,7 +20265,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -20427,7 +20281,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -20445,7 +20298,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -20459,7 +20311,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-runtime/node_modules/has-flag": { @@ -20467,7 +20318,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -20478,7 +20328,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -20505,7 +20354,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -20516,7 +20364,6 @@ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -20538,7 +20385,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -20557,7 +20403,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -20574,7 +20419,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20591,7 +20435,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -20607,7 +20450,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -20621,7 +20463,6 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -20632,7 +20473,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20646,7 +20486,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -20674,7 +20513,6 @@ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -20707,7 +20545,6 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.11.6", @@ -20735,7 +20572,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -20752,7 +20588,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -20770,7 +20605,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -20784,7 +20618,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-snapshot/node_modules/has-flag": { @@ -20792,7 +20625,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -20803,7 +20635,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/types": "^29.6.3", @@ -20830,7 +20661,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "optional": true, "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -20841,7 +20671,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@types/node": "*", @@ -20858,7 +20687,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20875,7 +20703,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -20889,7 +20716,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -20905,7 +20731,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=10" @@ -20919,7 +20744,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -20936,7 +20760,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -20950,7 +20773,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "imurmurhash": "^0.1.4", @@ -20965,7 +20787,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-util": { @@ -21181,7 +21002,6 @@ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "optional": true, "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", @@ -21202,7 +21022,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-convert": "^2.0.1" @@ -21219,7 +21038,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "peer": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -21237,7 +21055,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "peer": true, "dependencies": { "color-name": "~1.1.4" @@ -21251,7 +21068,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "optional": true, "peer": true }, "node_modules/jest-watcher/node_modules/has-flag": { @@ -21259,7 +21075,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, "peer": true, "engines": { "node": ">=8" @@ -21270,7 +21085,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -21279,6 +21093,15 @@ "node": ">=8" } }, + "node_modules/jest-when": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jest-when/-/jest-when-3.6.0.tgz", + "integrity": "sha512-+cZWTy0ekAJo7M9Om0Scdor1jm3wDiYJWmXE8U22UVnkH54YCXAuaqz3P+up/FdtOg8g4wHOxV7Thd7nKhT6Dg==", + "dev": true, + "peerDependencies": { + "jest": ">= 25" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -25343,7 +25166,6 @@ "url": "https://opencollective.com/fast-check" } ], - "optional": true, "peer": true }, "node_modules/pvtsutils": { @@ -30528,20 +30350,20 @@ }, "node_modules/terraso-backend": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#eb859151210c69123178b88cfa4863b52d619bdc", - "integrity": "sha512-toJ1fC0pLlEIQpxKO9pAyJ3fC2cWov+nnQ7H0+lwmB6VddgM4FTx+mGKxGi/Vlgz6UyMx5jWenyEg2RmFFQufw==" + "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#f5170e6a4399a4b9ee7a1453ded5285a28881e8e", + "integrity": "sha512-+U3mu1RdcX7adVK2MUpSD5YL4s74O85MIOEgPyZGxW4GbMdLKobM7NrE4O2SUUkbWoWExIClXx73hoVA9NTuHA==" }, "node_modules/terraso-client-shared": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#9c4e9bff02dc80caa2c407db04a6b696bcfb7c6c", - "integrity": "sha512-Nu5tm526sS0apqXazzjv4lqQkXIKL0d9bUiv0zSwFquSsWHke16PQFIJ2e67rJsbW61ec6wqkxzBrZzDmomUbQ==", + "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#1b54c54228a95025201c2b57ec458ba62c195769", + "integrity": "sha512-0Gf3wiXE9g7e/7xEDoQaq+ZFM/wgQJjI1kx/C1md4gPLywC8DJvY1ojCzaR7C3AOWNJR3oAso4lOD9W+Rhhx6w==", "dependencies": { "@reduxjs/toolkit": "^1.9.7", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "react": "^18.2.0", "react-redux": "^8.1.3", - "terraso-backend": "github:techmatters/terraso-backend#eb859151210c69123178b88cfa4863b52d619bdc", + "terraso-backend": "github:techmatters/terraso-backend#f5170e6a4399a4b9ee7a1453ded5285a28881e8e", "uuid": "^9.0.1" } }, diff --git a/package.json b/package.json index 2f1493382..fd7237f63 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "slate-hyperscript": "^0.100.0", "slate-react": "^0.101.0", "source-map-explorer": "^2.5.3", - "terraso-client-shared": "github:techmatters/terraso-client-shared#a416b76", + "terraso-client-shared": "github:techmatters/terraso-client-shared#1b54c54228a95025201c2b57ec458ba62c195769", "use-debounce": "^9.0.4", "uuid": "^9.0.1", "web-vitals": "^3.5.0", @@ -107,6 +107,7 @@ "flat": "^6.0.1", "i18next-conv": "^14.0.0", "jest-axe": "^8.0.0", + "jest-when": "^3.6.0", "plausible-tracker": "^0.3.8", "prettier": "^3.2.5", "react-scripts": "^5.0.1", diff --git a/src/common/components/CopyLink.js b/src/common/components/CopyLink.js new file mode 100644 index 000000000..3f5e9f4bd --- /dev/null +++ b/src/common/components/CopyLink.js @@ -0,0 +1,85 @@ +/* + * Copyright © 2024 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, Button, InputLabel, TextField } from '@mui/material'; + +import { useCopy } from 'custom-hooks'; + +const CopyLink = props => { + const { pageUrl, onShare } = props; + const { t } = useTranslation(); + + const { copied, copyToClipboard } = useCopy(pageUrl, () => { + onShare?.('link'); + }); + + return ( + <> + + {t('share.copy')} + + + {t('share.copy_button')} + + ), + }} + /> + {copied && ( + + {t('share.copy_button_done')} + + )} + + ); +}; + +export default CopyLink; diff --git a/src/common/components/SocialShare.js b/src/common/components/SocialShare.js index eb6defeec..8bd4c7b8c 100644 --- a/src/common/components/SocialShare.js +++ b/src/common/components/SocialShare.js @@ -34,8 +34,12 @@ import { } from '@mui/material'; import useMediaQuery from '@mui/material/useMediaQuery'; +import { useCopy } from 'custom-hooks'; + import { useAnalytics } from 'monitoring/analytics'; +import CopyLink from './CopyLink'; + import theme from 'theme'; const SocialShareContext = createContext({}); @@ -62,20 +66,6 @@ export const SocialShareContextProvider = props => { ); }; -const useCopy = (content, onCopy) => { - const [copied, setCopied] = useState(false); - - useEffect(() => () => setCopied(false), []); - - const copyToClipboard = () => { - navigator.clipboard.writeText(content); - setCopied(true); - onCopy?.(); - }; - - return { copied, copyToClipboard }; -}; - const CopyEmbededCode = props => { const { onShare } = props; const isSmall = useMediaQuery(theme.breakpoints.down('sm')); @@ -150,68 +140,6 @@ const CopyEmbededCode = props => { ); }; -const CopyLink = props => { - const { pageUrl, onShare } = props; - const { t } = useTranslation(); - - const { copied, copyToClipboard } = useCopy(pageUrl, () => { - onShare('link'); - }); - - return ( - <> - - {t('share.copy')} - - - {t('share.copy_button')} - - ), - }} - /> - {copied && ( - - {t('share.copy_button_done')} - - )} - - ); -}; - const PostToService = props => { const { name, pageUrl, onShare } = props; const { t } = useTranslation(); diff --git a/src/custom-hooks.js b/src/custom-hooks.js index f4e10dca0..d9349d926 100644 --- a/src/custom-hooks.js +++ b/src/custom-hooks.js @@ -47,3 +47,17 @@ export const useIsMounted = () => { return isMounted; }; + +export const useCopy = (content, onCopy) => { + const [copied, setCopied] = useState(false); + + useEffect(() => () => setCopied(false), []); + + const copyToClipboard = () => { + navigator.clipboard.writeText(content); + setCopied(true); + onCopy?.(); + }; + + return { copied, copyToClipboard }; +}; diff --git a/src/group/components/GroupForm.js b/src/group/components/GroupForm.js index d93cab76b..f5577e5f7 100644 --- a/src/group/components/GroupForm.js +++ b/src/group/components/GroupForm.js @@ -219,7 +219,10 @@ const GroupForm = () => { dispatch( saveGroup({ group: { - ..._.omit(['membershipsInfo.membershipType'], group), + ..._.omit( + ['membershipsInfo.membershipType', 'sharedResources'], + group + ), membershipType: group.membershipsInfo.membershipType, }, user, diff --git a/src/group/components/GroupView.test.js b/src/group/components/GroupView.test.js index 45f4ce6d9..8037fc54e 100644 --- a/src/group/components/GroupView.test.js +++ b/src/group/components/GroupView.test.js @@ -14,8 +14,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { render, screen, within } from 'tests/utils'; +import { act, fireEvent, render, screen, within } from 'tests/utils'; import React from 'react'; +import { when } from 'jest-when'; import { useParams } from 'react-router-dom'; import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; @@ -50,6 +51,11 @@ beforeEach(() => { useParams.mockReturnValue({ slug: 'slug-1', }); + Object.assign(navigator, { + clipboard: { + writeText: jest.fn(), + }, + }); }); test('GroupView: Display error', async () => { @@ -79,7 +85,18 @@ test('GroupView: Not found', async () => { await setup(); expect(screen.getByText(/Group not found/i)).toBeInTheDocument(); }); -test('GroupView: Display data', async () => { + +const groupViewMemberBase = async ( + accountMembership = { + id: 'user-id', + userRole: 'member', + membershipStatus: 'APPROVED', + user: { + firstName: 'Member First Name', + lastName: 'Member Last Name', + }, + } +) => { global.fetch.mockReturnValue( Promise.resolve({ json: () => [], @@ -98,20 +115,15 @@ test('GroupView: Display data', async () => { }, })), }; - const accountMembership = { - id: 'user-id', - userRole: 'member', - membershipStatus: 'APPROVED', - user: { - firstName: 'Member First Name', - lastName: 'Member Last Name', - }, - }; + const sharedResources = { edges: Array(6) .fill(0) .map((item, index) => ({ node: { + id: `shared-resource-id-${index}`, + shareAccess: 'MEMBERS', + shareUrl: 'https://example.com', source: { id: `de-${index}`, createdAt: '2022-05-20T16:25:21.536679+00:00', @@ -125,28 +137,34 @@ test('GroupView: Display data', async () => { }, })), }; - terrasoApi.requestGraphQL.mockReturnValue( - Promise.resolve({ - groups: { - edges: [ - { - node: { - name: 'Group name', - description: 'Group description', - website: 'https://www.group.org', - email: 'email@email.com', - membershipList: { - memberships, - accountMembership, + when(terrasoApi.requestGraphQL) + .calledWith(expect.stringContaining('query groupToView'), expect.anything()) + .mockReturnValue( + Promise.resolve({ + groups: { + edges: [ + { + node: { + name: 'Group name', + description: 'Group description', + website: 'https://www.group.org', + email: 'email@email.com', + membershipList: { + memberships, + accountMembership, + }, + sharedResources, }, - sharedResources, }, - }, - ], - }, - }) - ); + ], + }, + }) + ); await setup(); +}; + +test('GroupView: Display data', async () => { + await groupViewMemberBase(); // Group info expect( @@ -180,3 +198,119 @@ test('GroupView: Display data', async () => { const items = entriesList.getAllByRole('listitem'); expect(items.length).toBe(6); }); +test('GroupView: Share link manager', async () => { + when(terrasoApi.requestGraphQL) + .calledWith( + expect.stringContaining('mutation updateSharedResource'), + expect.anything() + ) + .mockResolvedValue({ + updateSharedResource: { + sharedResource: { + id: `shared-resource-id-0`, + shareAccess: 'ALL', + shareUrl: 'https://example.com', + source: { + id: `de-0`, + createdAt: '2022-05-20T16:25:21.536679+00:00', + name: `Data Entry 0`, + createdBy: { id: 'user-id', firstName: 'First', lastName: 'Last' }, + description: `Description 0`, + size: 3456, + entryType: 'FILE', + visualizations: { edges: [] }, + }, + }, + errors: null, + }, + }); + + await groupViewMemberBase({ + id: 'user-id', + userRole: 'manager', + membershipStatus: 'APPROVED', + user: { + firstName: 'Member First Name', + lastName: 'Member Last Name', + }, + }); + + const sharedDataRegion = within( + screen.getByRole('region', { name: 'Shared files and Links' }) + ); + expect( + sharedDataRegion.getByRole('heading', { name: 'Shared files and Links' }) + ).toBeInTheDocument(); + const entriesList = within(sharedDataRegion.getByRole('list')); + const items = entriesList.getAllByRole('listitem'); + expect(items.length).toBe(6); + + const shareButton = within(items[0]).getByRole('button', { + name: 'Share File “Data Entry 0”', + }); + await act(async () => fireEvent.click(shareButton)); + + const dialog = screen.getByRole('dialog', { + name: 'Share “Data Entry 0” File', + }); + + // Access level + const shareAccess = within(dialog).getByRole('combobox', { + name: `Share access level`, + }); + await act(async () => fireEvent.mouseDown(shareAccess)); + const listbox = within(screen.getByRole('listbox')); + await act(async () => + fireEvent.click(listbox.getByRole('option', { name: 'Anyone with link' })) + ); + + expect(terrasoApi.requestGraphQL).toHaveBeenCalledWith( + expect.stringContaining('mutation updateSharedResource'), + { + input: { + id: 'shared-resource-id-0', + shareAccess: 'ALL', + }, + } + ); + + // Copy link + await act(async () => + fireEvent.click(within(dialog).getByRole('button', { name: 'Copy Link' })) + ); + const copyCall = navigator.clipboard.writeText.mock.calls[0]; + expect(copyCall[0].toString()).toStrictEqual('https://example.com'); +}); + +test('GroupView: Share link member', async () => { + await groupViewMemberBase(); + + const sharedDataRegion = within( + screen.getByRole('region', { name: 'Shared files and Links' }) + ); + expect( + sharedDataRegion.getByRole('heading', { name: 'Shared files and Links' }) + ).toBeInTheDocument(); + const entriesList = within(sharedDataRegion.getByRole('list')); + const items = entriesList.getAllByRole('listitem'); + + const shareButton = within(items[0]).getByRole('button', { + name: 'Share File “Data Entry 0”', + }); + await act(async () => fireEvent.click(shareButton)); + + const dialog = screen.getByRole('dialog', { + name: 'Share “Data Entry 0” File', + }); + + expect( + within(dialog).getByText(/Members can view and download this file/i) + ).toBeInTheDocument(); + + // Copy link + await act(async () => + fireEvent.click(within(dialog).getByRole('button', { name: 'Copy Link' })) + ); + const copyCall = navigator.clipboard.writeText.mock.calls[0]; + expect(copyCall[0].toString()).toStrictEqual('https://example.com'); +}); diff --git a/src/group/groupFragments.ts b/src/group/groupFragments.ts index 0fbdcaade..2378e7c94 100644 --- a/src/group/groupFragments.ts +++ b/src/group/groupFragments.ts @@ -39,6 +39,9 @@ export const groupDataEntries = /* GraphQL */ ` sharedResources { edges { node { + id + shareAccess + shareUrl source { ... on DataEntryNode { ...dataEntry diff --git a/src/group/groupService.test.ts b/src/group/groupService.test.ts index 6d68c2a3e..9accf7d1a 100644 --- a/src/group/groupService.test.ts +++ b/src/group/groupService.test.ts @@ -52,7 +52,7 @@ test('GroupService: Fetch group', async () => { pendingCount: undefined, totalCount: undefined, }, - dataEntries: undefined, + sharedResources: undefined, }); }); test('GroupService: Fetch group not found', async () => { diff --git a/src/group/groupSlice.ts b/src/group/groupSlice.ts index 6dfe45240..3917d4e95 100644 --- a/src/group/groupSlice.ts +++ b/src/group/groupSlice.ts @@ -46,7 +46,7 @@ export const fetchGroupView = createAsyncThunk( 'group/fetchGroupView', async (slug: string, user, { dispatch }) => { const group = await groupService.fetchGroupToView(slug, user); - dispatch(setList(group.dataEntries)); + dispatch(setList(group.sharedResources)); return group; } ); diff --git a/src/group/groupUtils.js b/src/group/groupUtils.js index bf036be08..4fbb9b6f3 100644 --- a/src/group/groupUtils.js +++ b/src/group/groupUtils.js @@ -16,10 +16,10 @@ */ import { extractMembershipsInfo } from 'terraso-client-shared/collaboration/membershipsUtils'; -import { extractDataEntries } from 'sharedData/sharedDataUtils'; +import { extractSharedResources } from 'sharedData/sharedDataUtils'; export const extractGroup = group => ({ ...group, membershipsInfo: extractMembershipsInfo(group.membershipList), - dataEntries: extractDataEntries(group), + sharedResources: extractSharedResources(group), }); diff --git a/src/landscape/landscapeFragments.js b/src/landscape/landscapeFragments.js index 4c9068ded..68573be40 100644 --- a/src/landscape/landscapeFragments.js +++ b/src/landscape/landscapeFragments.js @@ -129,6 +129,9 @@ export const landscapeDataEntries = /* GraphQL */ ` sharedResources { edges { node { + id + shareAccess + shareUrl source { ... on DataEntryNode { ...dataEntry diff --git a/src/landscape/landscapeService.test.ts b/src/landscape/landscapeService.test.ts index 6751567f9..9097d960c 100644 --- a/src/landscape/landscapeService.test.ts +++ b/src/landscape/landscapeService.test.ts @@ -45,7 +45,6 @@ test('LandscapeService: Fetch landscape with missing fields', async () => { description: 'Landscape description', website: 'https://www.landscape.org', areaPolygon: null, - dataEntries: undefined, accountMembership: undefined, membershipsInfo: { accountMembership: undefined, @@ -57,6 +56,7 @@ test('LandscapeService: Fetch landscape with missing fields', async () => { }, partnership: undefined, partnershipStatus: undefined, + sharedResources: undefined, taxonomyTerms: {}, developmentStrategy: undefined, affiliatedGroups: [], diff --git a/src/landscape/landscapeSlice.js b/src/landscape/landscapeSlice.js index 9be5c9656..d7757341b 100644 --- a/src/landscape/landscapeSlice.js +++ b/src/landscape/landscapeSlice.js @@ -73,7 +73,7 @@ export const fetchLandscapeView = createAsyncThunk( params, currentUser ); - dispatch(setList(landscape.dataEntries)); + dispatch(setList(landscape.sharedResources)); return landscape; } ); diff --git a/src/landscape/landscapeUtils.js b/src/landscape/landscapeUtils.js index 55eafff7b..504ca2122 100644 --- a/src/landscape/landscapeUtils.js +++ b/src/landscape/landscapeUtils.js @@ -27,7 +27,7 @@ import { Typography } from '@mui/material'; import { countryNameForCode } from 'common/countries'; import * as gisService from 'gis/gisService'; import { normalizeLongitude, parseGeoJson } from 'gis/gisUtils'; -import { extractDataEntries } from 'sharedData/sharedDataUtils'; +import { extractSharedResources } from 'sharedData/sharedDataUtils'; import { extractTerms } from 'taxonomies/taxonomiesUtils'; import { ALL_PARTNERSHIP_STATUS } from './landscapeConstants'; @@ -155,7 +155,7 @@ export const extractLandscape = async (landscape, useLocationApi) => { areaPolygon: extractLandscapeGeoJson(landscape), partnershipStatus: ALL_PARTNERSHIP_STATUS[landscape.partnershipStatus], partnership: extractPartnership(landscape), - dataEntries: extractDataEntries(landscape), + sharedResources: extractSharedResources(landscape), taxonomyTerms: extractTerms(_.get('taxonomyTerms.edges', landscape)), affiliatedGroups: extractAffiliatedGroups(landscape), developmentStrategy: extractDevelopmentStrategy(landscape), diff --git a/src/localization/locales/en-US.json b/src/localization/locales/en-US.json index e0f6c23b0..c09041ac1 100644 --- a/src/localization/locales/en-US.json +++ b/src/localization/locales/en-US.json @@ -552,6 +552,17 @@ "description_update": "Update description", "delete_label": "Delete “{{name}}”", "download_label": "Download “{{name}}”", + "share_file_label": "Share File “{{name}}”", + "share_file_dialog_title": "Share “{{name}}” File", + "share_file_dialog_close": "Close", + "share_file_dialog_share_access_label": "Share access level", + "share_file_dialog_share_access_type_ALL": "Anyone with link", + "share_file_dialog_share_access_type_MEMBERS": "Members", + "share_file_dialog_share_access_suffix": "can view and download this file", + "share_file_dialog_share_access_all_warning": "Anyone with the link can access this file.", + "share_file_dialog_share_access_not_allowed": "Only group managers and file owners can change this setting.", + "share_file_dialog_share_access_updated": "Access updated.", + "share_file_dialog_share_access_done": "Done", "invalid_extension": "The file extension ({{file.resourceType}}) does not match the file’s contents", "invalid_extension_zip": "The file {{file.name}}{{file.resourceType}} is a not a valid zip.", "invalid_shapefile": "The file {{file.name}}{{file.resourceType}} is a valid zip, but is not a valid ESRI shapefile.", @@ -656,7 +667,10 @@ "link_delete_tooltip": "Remove link “{{name}}”.", "file_delete_tooltip": "Remove file “{{name}}”.", "visualization_unsaved_changes_title": "Leave Map?", - "visualization_unsaved_changes_message": "You have unsaved changes that will be lost if you leave the page." + "visualization_unsaved_changes_message": "You have unsaved changes that will be lost if you leave the page.", + "shared_resource_download_no_preview": "No file preview available", + "shared_resource_download_button": "Download File", + "shared_resource_download_optional_auth_top_message": "<1>Join Terraso and share files with your community." }, "common": { "dialog_cancel_label": "Cancel", diff --git a/src/localization/locales/es-ES.json b/src/localization/locales/es-ES.json index 4c05b482a..2b3fbe670 100644 --- a/src/localization/locales/es-ES.json +++ b/src/localization/locales/es-ES.json @@ -595,6 +595,17 @@ "description_update": "Actualizar descripción", "delete_label": "Elminar “{{name}}”", "download_label": "Descargar “{{name}}”", + "share_file_label": "Compartir archivo “{{name}}”", + "share_file_dialog_title": "Compartir “{{name}}” archivo", + "share_file_dialog_close": "Cerrar", + "share_file_dialog_share_access_label": "Nivel de acceso", + "share_file_dialog_share_access_type_ALL": "Cualquier persona con el enlace", + "share_file_dialog_share_access_type_MEMBERS": "Miembros", + "share_file_dialog_share_access_suffix": "pueden ver y descargar este archivo", + "share_file_dialog_share_access_all_warning": "Cualquiera que tenga el enlace puede acceder a este archivo.", + "share_file_dialog_share_access_not_allowed": "Sólo los administradores de grupos y propietarios de archivos pueden cambiar esta configuración.", + "share_file_dialog_share_access_updated": "Acceso actualizado.", + "share_file_dialog_share_access_done": "Hecho", "invalid_extension": "La extensión del archivo ({{file.resourceType}}) no coincide con el contenido del archivo", "invalid_extension_zip": "El archivo {{file.name}}{{file.resourceType}} no es un zip válido.", "invalid_shapefile": "El archivo {{file.name}}{{file.resourceType}} es un zip válido, pero no es un archivo de ESRI shapefile válido.", @@ -699,7 +710,10 @@ "link_delete_tooltip": "Eliminar enlace “{{name}}”.", "file_delete_tooltip": "Eliminar archivo “{{name}}”.", "visualization_unsaved_changes_title": "¿Dejar el mapa?", - "visualization_unsaved_changes_message": "Tienes cambios no guardados que se perderán si abandonas la página." + "visualization_unsaved_changes_message": "Tienes cambios no guardados que se perderán si abandonas la página.", + "shared_resource_download_no_preview": "No hay vista previa de archivos disponible", + "shared_resource_download_button": "Descargar archivo", + "shared_resource_download_optional_auth_top_message": "<1>Únete a Terraso y comparte archivos con tu comunidad." }, "share": { "button": "Comparte", diff --git a/src/navigation/components/Routes.js b/src/navigation/components/Routes.js index c3c159b6b..780af7a09 100644 --- a/src/navigation/components/Routes.js +++ b/src/navigation/components/Routes.js @@ -47,6 +47,7 @@ import LandscapeSharedDataVisualization from 'landscape/components/LandscapeShar import LandscapeSharedDataVisualizationConfig from 'landscape/components/LandscapeSharedDataVisualizationConfig'; import LandscapeView from 'landscape/components/LandscapeView'; import LandscapeMembers from 'landscape/membership/components/LandscapeMembers'; +import SharedResourceDownload from 'sharedData/components/SharedResourceDownload'; import StoryMapInvite from 'storyMap/components/StoryMapInvite'; import StoryMapNew from 'storyMap/components/StoryMapNew'; import StoryMapsToolsHome from 'storyMap/components/StoryMapsToolHome'; @@ -107,6 +108,13 @@ const paths = [ showBreadcrumbs: true, breadcrumbsLabel: 'group.breadcrumbs_visualization', }), + path('/groups/:groupSlug/download/:shareUuid', SharedResourceDownload, { + optionalAuth: { + enabled: true, + topMessage: + 'sharedData.shared_resource_download_optional_auth_top_message', + }, + }), path('/landscapes/map', LandscapeMapEmbed, { optionalAuth: { enabled: true, @@ -157,6 +165,17 @@ const paths = [ breadcrumbsLabel: 'landscape.breadcrumbs_visualization', } ), + path( + '/landscapes/:landscapeSlug/download/:shareUuid', + SharedResourceDownload, + { + optionalAuth: { + enabled: true, + topMessage: + 'sharedData.shared_resource_download_optional_auth_top_message', + }, + } + ), path('/notifications/unsubscribe', Unsubscribe, { optionalAuth: { enabled: true, diff --git a/src/sharedData/components/SharedDataCard.js b/src/sharedData/components/SharedDataCard.js index 8e2a609e0..4026ba21d 100644 --- a/src/sharedData/components/SharedDataCard.js +++ b/src/sharedData/components/SharedDataCard.js @@ -36,13 +36,15 @@ import { ScrollTo } from 'navigation/scrollTo'; import SharedDataEntryFile from './SharedDataEntryFile'; import SharedDataEntryLink from './SharedDataEntryLink'; -const SharedFilesCard = props => { +const SharedDataCard = props => { const { t } = useTranslation(); const { onUploadClick, onAddVisualizationClick } = props; const { owner, entityTypeLocalized } = useCollaborationContext(); const { allowed } = usePermission(`sharedData.viewFiles`, owner); - const { data: sharedFiles, fetching } = useSelector(_.get('sharedData.list')); - const hasFiles = !_.isEmpty(sharedFiles); + const { data: sharedResources, fetching } = useSelector( + _.get('sharedData.list') + ); + const hasSharedData = !_.isEmpty(sharedResources); if (!allowed) { return null; @@ -73,19 +75,19 @@ const SharedFilesCard = props => { {t('sharedData.card_description')} - {hasFiles && ( + {hasSharedData && ( <> - {sharedFiles.map(dataEntry => - dataEntry.entryType === 'LINK' ? ( + {sharedResources.map(sharedResource => + sharedResource.dataEntry.entryType === 'LINK' ? ( ) : ( ) )} @@ -108,7 +110,7 @@ const SharedFilesCard = props => { - {onAddVisualizationClick && hasFiles && ( + {onAddVisualizationClick && hasSharedData && ( @@ -119,4 +121,4 @@ const SharedFilesCard = props => { ); }; -export default SharedFilesCard; +export default SharedDataCard; diff --git a/src/sharedData/components/SharedDataEntryBase.js b/src/sharedData/components/SharedDataEntryBase.js index 68c32ff40..252da524d 100644 --- a/src/sharedData/components/SharedDataEntryBase.js +++ b/src/sharedData/components/SharedDataEntryBase.js @@ -34,6 +34,7 @@ import { deleteSharedData, resetProcessing, updateSharedData, + updateSharedResource, } from 'sharedData/sharedDataSlice'; import theme from 'theme'; @@ -48,23 +49,28 @@ const SharedDataEntryBase = props => { const { i18n, t } = useTranslation(); const { owner, entityType, updateOwner } = useCollaborationContext(); const { - dataEntry, + sharedResource, children, EntryTypeIcon, DownloadComponent, + ShareComponent, info, deleteTooltip, } = props; const [isEditingName, setIsEditingName] = useState(false); const [isEditingDescription, setIsEditingDescription] = useState(false); + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); const processing = useSelector( - _.get(`sharedData.processing.${dataEntry.id}`) + _.get(`sharedData.processing.${sharedResource.id}`) ); const dispatch = useDispatch(); const { trackEvent } = useAnalytics(); const onConfirm = useCallback(() => { - dispatch(deleteSharedData({ dataEntry })).then(data => { + dispatch(deleteSharedData({ dataEntry, sharedResource })).then(data => { const success = _.get('meta.requestStatus', data) === 'fulfilled'; if (success) { updateOwner(); @@ -77,12 +83,21 @@ const SharedDataEntryBase = props => { } dispatch(resetProcessing(dataEntry.id)); }); - }, [dataEntry, dispatch, owner.slug, trackEvent, updateOwner, entityType]); + }, [ + dataEntry, + dispatch, + owner.slug, + trackEvent, + updateOwner, + entityType, + sharedResource, + ]); const onUpdate = useCallback( field => value => { dispatch( updateSharedData({ + sharedResource, dataEntry: { ..._.pick(['id', 'name', 'description'], dataEntry), [field]: value, @@ -99,7 +114,15 @@ const SharedDataEntryBase = props => { dispatch(resetProcessing(dataEntry.id)); }); }, - [dataEntry, dispatch, owner.slug, trackEvent, updateOwner, entityType] + [ + dataEntry, + dispatch, + owner.slug, + trackEvent, + updateOwner, + entityType, + sharedResource, + ] ); const onUpdateName = useMemo(() => onUpdate('name'), [onUpdate]); @@ -108,6 +131,27 @@ const SharedDataEntryBase = props => { [onUpdate] ); + const onUpdateSharedResource = useCallback( + sharedResource => { + return dispatch( + updateSharedResource({ + sharedResource, + }) + ).then(data => { + const success = _.get('meta.requestStatus', data) === 'fulfilled'; + if (success) { + updateOwner(); + trackEvent('dataEntry.edit', { + props: { [entityType]: owner.slug }, + }); + } + dispatch(resetProcessing(dataEntry.id)); + return data; + }); + }, + [dataEntry, dispatch, owner.slug, trackEvent, updateOwner, entityType] + ); + const description = useMemo( () => _.get('description', dataEntry), [dataEntry] @@ -183,6 +227,12 @@ const SharedDataEntryBase = props => { justifyContent="flex-end" display={isEditingName ? 'none' : 'inherit'} > + {ShareComponent && ( + + )} { - + diff --git a/src/sharedData/components/SharedDataEntryFile.js b/src/sharedData/components/SharedDataEntryFile.js index d1c4f93e5..35f69a9c7 100644 --- a/src/sharedData/components/SharedDataEntryFile.js +++ b/src/sharedData/components/SharedDataEntryFile.js @@ -14,24 +14,45 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { filesize } from 'filesize'; import _ from 'lodash/fp'; +import { usePermission } from 'permissions'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'terrasoApi/store'; +import CloseIcon from '@mui/icons-material/Close'; import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import LockIcon from '@mui/icons-material/Lock'; import MapIcon from '@mui/icons-material/Map'; +import PublicIcon from '@mui/icons-material/Public'; +import ShareIcon from '@mui/icons-material/Share'; import { + Alert, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, Divider, Grid, IconButton, Link, ListItem, + MenuItem, + Select, Stack, + Tooltip, + Typography, } from '@mui/material'; import { useCollaborationContext } from 'collaboration/collaborationContext'; +import CopyLink from 'common/components/CopyLink'; import RouterLink from 'common/components/RouterLink'; import { formatDate } from 'localization/utils'; +import { + SHARE_ACCESS_ALL, + SHARE_ACCESS_TYPES, +} from 'sharedData/sharedDataConstants'; import { useSharedData } from 'sharedData/sharedDataHooks'; import SharedDataEntryBase, { ICON_SIZE } from './SharedDataEntryBase'; @@ -84,8 +105,11 @@ const Visualizations = props => { const DownloadComponent = props => { const { t } = useTranslation(); const { downloadFile } = useSharedData(); - const { dataEntry } = props; - + const { sharedResource } = props; + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); const handleDownload = e => { e.preventDefault(); downloadFile(dataEntry); @@ -110,16 +134,234 @@ const DownloadComponent = props => { ); }; +const ShareDialog = props => { + const { t } = useTranslation(); + const { owner } = useCollaborationContext(); + + const { sharedResource, open, handleClose, onUpdateSharedResource } = props; + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); + const processing = useSelector( + _.get(`sharedData.processing.${sharedResource.id}`) + ); + const { allowed: allowedToEditSharedData } = usePermission( + 'sharedData.edit', + { + owner, + dataEntry, + } + ); + const [showUpdateSuccess, setShowUpdateSuccess] = useState(); + + // focus on the close button on open + const onCloseRefChange = ref => { + if (ref) { + ref.focus(); + } + }; + + const onChange = useCallback( + event => { + setShowUpdateSuccess(false); + const shareAccess = event.target.value; + onUpdateSharedResource({ + ...sharedResource, + shareAccess, + }).then(data => { + const success = _.get('meta.requestStatus', data) === 'fulfilled'; + if (success) { + setShowUpdateSuccess(true); + } + }); + }, + [onUpdateSharedResource, sharedResource] + ); + + const onCloseWrapper = useCallback(() => { + setShowUpdateSuccess(false); + handleClose(); + }, [handleClose]); + + return ( + + + + {t('sharedData.share_file_dialog_title', { name: dataEntry.name })} + + + + + + + {!allowedToEditSharedData && ( + + {[ + t('sharedData.share_file_dialog_share_access_type', { + context: sharedResource.shareAccess, + }), + t('sharedData.share_file_dialog_share_access_suffix'), + ].join(' ')} + + )} + + + + {t('sharedData.share_file_dialog_share_access_suffix')} + + + {!allowedToEditSharedData && ( + + + + {t('sharedData.share_file_dialog_share_access_not_allowed')} + + + )} + {sharedResource.shareAccess === SHARE_ACCESS_ALL && ( + + + + {t('sharedData.share_file_dialog_share_access_all_warning')} + + + )} + + + + {showUpdateSuccess ? ( + + {t('sharedData.share_file_dialog_share_access_updated', { + name: sharedResource.dataEntry.name, + })} + + ) : ( +
+ )} + + +
+ ); +}; + +const ShareComponent = props => { + const { t } = useTranslation(); + const { sharedResource, onUpdateSharedResource } = props; + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); + + const [open, setOpen] = useState(false); + + const handleOpen = () => setOpen(true); + const handleClose = () => { + setOpen(false); + }; + + const label = t('sharedData.share_file_label', { + name: dataEntry.name, + }); + + return ( + <> + + + ({ + marginTop: '2px', + width: ICON_SIZE, + height: ICON_SIZE, + color: theme.palette.secondary.main, + })} + /> + + + + + ); +}; + const SharedDataEntryFile = props => { - const { dataEntry } = props; + const { sharedResource } = props; return ( - + ); }; diff --git a/src/sharedData/components/SharedDataEntryLink.js b/src/sharedData/components/SharedDataEntryLink.js index c92803237..0c4a437e8 100644 --- a/src/sharedData/components/SharedDataEntryLink.js +++ b/src/sharedData/components/SharedDataEntryLink.js @@ -28,7 +28,11 @@ import SharedDataEntryBase, { ICON_SIZE } from './SharedDataEntryBase'; const DownloadComponent = props => { const { t } = useTranslation(); const { owner, entityType } = useCollaborationContext(); - const { dataEntry } = props; + const { sharedResource } = props; + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); return ( { const SharedDataEntryLink = props => { const { t } = useTranslation(); - const { dataEntry } = props; + const { sharedResource } = props; + const dataEntry = useMemo( + () => sharedResource.dataEntry, + [sharedResource.dataEntry] + ); const domain = useMemo(() => { const url = new URL(dataEntry.url); @@ -72,7 +80,7 @@ const SharedDataEntryLink = props => { return ( } DownloadComponent={DownloadComponent} info={domain} diff --git a/src/sharedData/components/SharedResourceDownload.js b/src/sharedData/components/SharedResourceDownload.js new file mode 100644 index 000000000..d44481180 --- /dev/null +++ b/src/sharedData/components/SharedResourceDownload.js @@ -0,0 +1,99 @@ +/* + * Copyright © 2024 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import React, { useCallback, useEffect } from 'react'; +import queryString from 'query-string'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useFetchData } from 'terraso-client-shared/store/utils'; +import { useSelector } from 'terrasoApi/store'; +import { Button, Paper, Stack, Typography } from '@mui/material'; + +import NotFound from 'layout/NotFound'; +import PageContainer from 'layout/PageContainer'; +import PageLoader from 'layout/PageLoader'; +import { generateReferrerPath } from 'navigation/navigationUtils'; +import { fetchSharedResource } from 'sharedData/sharedDataSlice'; + +const SharedResourceDownload = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + const { shareUuid } = useParams(); + const { data: sharedResource, fetching: fetchingSharedResource } = + useSelector(state => state.sharedData.sharedResource); + const hasToken = useSelector(state => state.account.hasToken); + + useFetchData( + useCallback(() => fetchSharedResource({ shareUuid }), [shareUuid]) + ); + + useEffect(() => { + if (fetchingSharedResource || sharedResource || hasToken) { + return; + } + console.log({ + fetchingSharedResource, + sharedResource, + hasToken, + }); + const referrer = generateReferrerPath(location); + + const to = referrer + ? queryString.stringifyUrl({ + url: '/account', + query: { + referrer, + }, + }) + : '/account'; + navigate(to); + }, [fetchingSharedResource, location, sharedResource, hasToken, navigate]); + + const downloadFile = useCallback(() => { + window.open(sharedResource.downloadUrl, '_blank'); + }, [sharedResource]); + + if (fetchingSharedResource) { + return ; + } + + if (!sharedResource) { + return ; + } + + return ( + + + {`${sharedResource.dataEntry?.name}.${sharedResource.dataEntry?.resourceType}`} + + {t('sharedData.shared_resource_download_no_preview')} + + + + + ); +}; + +export default SharedResourceDownload; diff --git a/src/sharedData/components/SharedResourceDownload.test.js b/src/sharedData/components/SharedResourceDownload.test.js new file mode 100644 index 000000000..dc52fc132 --- /dev/null +++ b/src/sharedData/components/SharedResourceDownload.test.js @@ -0,0 +1,116 @@ +/* + * Copyright © 2024 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import { act, fireEvent, render, screen } from 'tests/utils'; +import { when } from 'jest-when'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; + +import SharedResourceDownload from './SharedResourceDownload'; + +jest.mock('terraso-client-shared/terrasoApi/api'); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(), + useNavigate: jest.fn(), + useLocation: jest.fn(), +})); + +const DEFAULT_ACCOUNT = { + currentUser: {}, + account: { + hasToken: 'token', + }, +}; + +const setup = async (account = DEFAULT_ACCOUNT) => { + await render(, { + account, + }); +}; + +beforeEach(() => { + window.open = jest.fn(); + useParams.mockReturnValue({ + shareUuid: 'share-uuid-1', + }); + useNavigate.mockReturnValue(jest.fn()); + useLocation.mockReturnValue({ + pathname: + '/groups/private-1/shared-resource/download/72bd83f7-229e-4770-a686-42f5a922468b', + }); +}); + +test('SharedResourceDownload: Has access', async () => { + when(terrasoApi.requestGraphQL) + .calledWith( + expect.stringContaining('query sharedResource'), + expect.anything() + ) + .mockResolvedValue({ + sharedResource: { + downloadUrl: 'https://example.com', + source: { + name: 'map', + resourceType: 'geojson', + }, + }, + }); + await setup(); + + expect( + screen.getByRole('heading', { name: 'map.geojson' }) + ).toBeInTheDocument(); + const downloadButton = screen.getByRole('button', { name: 'Download File' }); + await act(async () => fireEvent.click(downloadButton)); + + expect(window.open).toHaveBeenCalledWith('https://example.com', '_blank'); +}); + +test('SharedResourceDownload: No access', async () => { + when(terrasoApi.requestGraphQL) + .calledWith( + expect.stringContaining('query sharedResource'), + expect.anything() + ) + .mockResolvedValue({ + sharedResource: null, + }); + await setup(); + + expect( + screen.getByRole('heading', { name: 'Page not found' }) + ).toBeInTheDocument(); +}); + +test('SharedResourceDownload: Login redirect', async () => { + const navigate = jest.fn(); + useNavigate.mockReturnValue(navigate); + when(terrasoApi.requestGraphQL) + .calledWith( + expect.stringContaining('query sharedResource'), + expect.anything() + ) + .mockResolvedValue({ + sharedResource: null, + }); + await setup({ currentUser: {} }); + + expect(navigate).toHaveBeenCalledWith( + '/account?referrer=%2Fgroups%2Fprivate-1%2Fshared-resource%2Fdownload%2F72bd83f7-229e-4770-a686-42f5a922468b' + ); +}); diff --git a/src/sharedData/sharedDataConstants.js b/src/sharedData/sharedDataConstants.js index c2b1477d1..641b2d6ea 100644 --- a/src/sharedData/sharedDataConstants.js +++ b/src/sharedData/sharedDataConstants.js @@ -16,3 +16,7 @@ */ export const MAX_DESCRIPTION_CHARACTERS = 200; + +export const SHARE_ACCESS_ALL = 'ALL'; +export const SHARE_ACCESS_MEMBERS = 'MEMBERS'; +export const SHARE_ACCESS_TYPES = [SHARE_ACCESS_ALL, SHARE_ACCESS_MEMBERS]; diff --git a/src/sharedData/sharedDataService.js b/src/sharedData/sharedDataService.js index 3ecad0f68..2eb99852a 100644 --- a/src/sharedData/sharedDataService.js +++ b/src/sharedData/sharedDataService.js @@ -18,7 +18,7 @@ import _ from 'lodash/fp'; import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; import { graphql } from 'terrasoApi/shared/graphqlSchema'; -import { extractDataEntry } from './sharedDataUtils'; +import { extractDataEntry, extractSharedResource } from './sharedDataUtils'; import { SHARED_DATA_ACCEPTED_EXTENSIONS } from 'config'; @@ -262,3 +262,52 @@ export const fetchVisualizationConfig = ({ configuration: JSON.parse(visualizationConfig.configuration), })); }; + +export const updateSharedResource = ({ sharedResource }) => { + const query = graphql(` + mutation updateSharedResource($input: SharedResourceUpdateMutationInput!) { + updateSharedResource(input: $input) { + sharedResource { + id + shareAccess + shareUrl + source { + ... on DataEntryNode { + ...dataEntry + ...dataEntryVisualizations + } + } + } + errors + } + } + `); + return terrasoApi + .requestGraphQL(query, { + input: _.pick(['id', 'shareAccess'], sharedResource), + }) + .then(_.get('updateSharedResource.sharedResource')) + .then(extractSharedResource); +}; + +export const fetchSharedResource = ({ shareUuid }) => { + const query = graphql(` + query sharedResource($shareUuid: String!) { + sharedResource(shareUuid: $shareUuid) { + downloadUrl + source { + ... on DataEntryNode { + ...dataEntry + } + } + } + } + `); + return terrasoApi + .requestGraphQL(query, { + shareUuid, + }) + .then(_.get('sharedResource')) + .then(sharedResource => sharedResource || Promise.reject('not_found')) + .then(extractSharedResource); +}; diff --git a/src/sharedData/sharedDataSlice.js b/src/sharedData/sharedDataSlice.js index 3f697702d..4bc76a353 100644 --- a/src/sharedData/sharedDataSlice.js +++ b/src/sharedData/sharedDataSlice.js @@ -42,6 +42,10 @@ const initialState = { data: null, deleting: false, }, + sharedResource: { + fetching: true, + data: null, + }, }; export const uploadSharedDataFile = createAsyncThunk( @@ -103,13 +107,19 @@ export const deleteVisualizationConfig = createAsyncThunk( }, }) ); +export const updateSharedResource = createAsyncThunk( + 'sharedData/updateSharedResource', + sharedDataService.updateSharedResource +); +export const fetchSharedResource = createAsyncThunk( + 'sharedData/fetchSharedResource', + sharedDataService.fetchSharedResource, + null, + null +); -const setProcessing = (state, action) => - _.set( - `processing.${action.meta.arg.dataEntry.id}`, - action.meta.requestStatus === 'pending', - state - ); +const setProcessing = (state, requestStatus, sharedResource) => + _.set(`processing.${sharedResource.id}`, requestStatus === 'pending', state); const sharedDataSlice = createSlice({ name: 'sharedData', @@ -134,28 +144,84 @@ const sharedDataSlice = createSlice({ }, extraReducers: builder => { - builder.addCase(updateSharedData.pending, setProcessing); - builder.addCase(updateSharedData.rejected, setProcessing); - + builder.addCase(updateSharedData.pending, (state, action) => + setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ) + ); + builder.addCase(updateSharedData.rejected, (state, action) => + setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ) + ); builder.addCase(updateSharedData.fulfilled, (state, action) => ({ ...state, list: { ...state.list, data: state.list.data.map(item => - item.id === action.meta.arg.dataEntry.id ? action.payload : item + item.id === action.meta.arg.sharedResource.id + ? { ...item, dataEntry: action.payload } + : item ), }, })); - builder.addCase(deleteSharedData.pending, setProcessing); - builder.addCase(deleteSharedData.rejected, setProcessing); + builder.addCase(updateSharedResource.pending, (state, action) => + setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ) + ); + builder.addCase(updateSharedResource.rejected, (state, action) => { + return setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ); + }); + builder.addCase(updateSharedResource.fulfilled, (state, action) => { + return { + ...state, + processing: _.omit(action.meta.arg.sharedResource.id, { + ...state.processing, + }), + list: { + ...state.list, + data: state.list.data.map(item => + item.id === action.meta.arg.sharedResource.id + ? action.payload + : item + ), + }, + }; + }); + + builder.addCase(deleteSharedData.pending, (state, action) => + setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ) + ); + builder.addCase(deleteSharedData.rejected, (state, action) => + setProcessing( + state, + action.meta.requestStatus, + action.meta.arg.sharedResource + ) + ); builder.addCase(deleteSharedData.fulfilled, (state, action) => ({ ...state, list: { ...state.list, data: state.list.data.filter( - item => item.id !== action.meta.arg.dataEntry.id + item => item.id !== action.meta.arg.sharedResource.id ), }, })); @@ -267,14 +333,12 @@ const sharedDataSlice = createSlice({ ...state, visualizationConfig: initialState.visualizationConfig, })); - builder.addCase(fetchVisualizationConfig.rejected, state => ({ ...state, visualizationConfig: { fetching: false, }, })); - builder.addCase(fetchVisualizationConfig.fulfilled, (state, action) => ({ ...state, visualizationConfig: { @@ -282,6 +346,25 @@ const sharedDataSlice = createSlice({ data: action.payload, }, })); + + builder.addCase(fetchSharedResource.pending, state => ({ + ...state, + sharedResource: initialState.sharedResource, + })); + builder.addCase(fetchSharedResource.rejected, state => ({ + ...state, + sharedResource: { + fetching: false, + data: null, + }, + })); + builder.addCase(fetchSharedResource.fulfilled, (state, action) => ({ + ...state, + sharedResource: { + fetching: false, + data: action.payload, + }, + })); }, }); diff --git a/src/sharedData/sharedDataUtils.js b/src/sharedData/sharedDataUtils.js index 0cd316907..88ea3e2cd 100644 --- a/src/sharedData/sharedDataUtils.js +++ b/src/sharedData/sharedDataUtils.js @@ -14,13 +14,17 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ +import _ from 'lodash/fp'; export const extractDataEntry = dataEntry => ({ ...dataEntry, visualizations: dataEntry?.visualizations?.edges?.map(edge => edge.node), }); -export const extractDataEntries = parent => - parent.sharedResources?.edges - .map(edge => edge.node.source) - .map(dataEntry => extractDataEntry(dataEntry)); +export const extractSharedResource = sharedResource => ({ + ..._.omit('source', sharedResource), + dataEntry: extractDataEntry(sharedResource.source), +}); + +export const extractSharedResources = parent => + parent.sharedResources?.edges.map(edge => extractSharedResource(edge.node));