diff --git a/CHANGELOG.md b/CHANGELOG.md index 336c2adc0..4924a34fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,20 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Added -- Dataset Templates UI integration, including create/edit flows, previews, and skeleton states. +- Edit Dataset Template Integration: "Edit Template" dropdown on the Dataset Templates listing now opens the Metadata or Terms editor and shows a "Template updated" toast on return. +- External Tools: Added guestbook and terms modal for Dataverse external tools. ### Changed ### Fixed +- After saving on either Edit Template tab (Metadata or Terms), the user is redirected to the templates listing with a success toast instead of staying on the edit page. +- Edit Template breadcrumb on the Terms page no longer renders the dataset's "Terms and Guestbook" label (templates have no guestbook). + ### Removed +- Standalone `EditTemplateMetadataFactory` and `EditTemplateTermsFactory` route factories — replaced by a single `EditTemplateFactory` dispatcher that selects the right page based on `editMode`. + --- ## [v0.3.0] -- 2026-04-24 @@ -32,6 +38,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Added Notifications tab in Account Page - Added runtime configuration options for homepage branding and support link. - Added an environment variable to docker-compose-dev.yml to hide the OIDC client used in the SPA from the JSF frontend: DATAVERSE_AUTH_OIDC_HIDDEN_JSF: 1 +- Dataset Templates UI integration, including create/edit flows, previews, and skeleton states. - Added a message note to the login page - Download with terms of use and guestbook. - Show terms modal before download when dataset has custom terms, a non-default license (not CC0 1.0), or a guestbook. Draft datasets and dataset editors bypass the modal. diff --git a/package-lock.json b/package-lock.json index 06d2b6657..b15979d05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.1.0-alpha.4", + "@iqss/dataverse-client-javascript": "2.1.0-pr430.a6e163d", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -230,93 +230,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -326,21 +239,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", @@ -371,20 +269,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", @@ -394,59 +278,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -474,22 +305,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", @@ -518,109 +333,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -676,23 +388,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", @@ -700,922 +395,7 @@ "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1624,48 +404,37 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { + "node_modules/@babel/plugin-syntax-jsx": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1676,119 +445,92 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1797,16 +539,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1815,102 +555,30 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/compat-data": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.15", - "babel-plugin-polyfill-corejs3": "^0.14.0", - "babel-plugin-polyfill-regenerator": "^0.6.6", - "core-js-compat": "^3.48.0", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1919,20 +587,20 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/runtime": { @@ -3285,9 +1953,9 @@ } }, "node_modules/@iqss/dataverse-client-javascript": { - "version": "2.1.0-alpha.4", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.1.0-alpha.4/0a0dc68d4d99581d7ec017e58dbce3407f99f5d9", - "integrity": "sha512-UwHnFSYuvhxpc/JG2cFr5+bwERZXqGfNBTMSUQwtJaj6vfHO7anJAAxAx8g/AB8b4JtR9rljnYjAbsGJXicfBw==", + "version": "2.1.0-pr430.a6e163d", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.1.0-pr430.a6e163d/9f90f2f84112ee929f5b22135c9cb9fced10726e", + "integrity": "sha512-XoJPHScEYh6s9x4Ncfa+CVebL4v88/Dx9U/Y5Spi6xRQuSnFDtH1C0UXlPRcE/2MgThfSH7Fwdgs2A1PwXildQ==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", @@ -4792,18 +3460,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -7019,6 +5675,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7032,6 +5689,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7045,6 +5703,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7058,6 +5717,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7071,6 +5731,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7084,6 +5745,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7097,6 +5759,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7110,6 +5773,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7123,6 +5787,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7136,6 +5801,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7149,6 +5815,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7162,6 +5829,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7175,6 +5843,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7188,6 +5857,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7201,6 +5871,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7214,6 +5885,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7227,6 +5899,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7240,6 +5913,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7253,6 +5927,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7266,6 +5941,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7279,6 +5955,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7292,6 +5969,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -8794,6 +7472,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", @@ -9574,34 +8253,11 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -10408,198 +9064,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -10680,7 +9144,7 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -10700,20 +9164,6 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -10761,6 +9211,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -10818,20 +9269,6 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -10937,6 +9374,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, "license": "MIT", "dependencies": { "default-require-extensions": "^3.0.0" @@ -10977,6 +9415,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -11349,154 +9788,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/babel-loader/node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/babel-loader/node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -11564,51 +9855,6 @@ "dev": true, "license": "MIT" }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/babel-plugin-styled-components": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", @@ -12004,7 +10250,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/byte-size": { @@ -12112,6 +10358,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, "license": "MIT", "dependencies": { "hasha": "^5.0.0", @@ -12127,6 +10374,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -12142,6 +10390,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -12436,17 +10685,6 @@ } } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0" - } - }, "node_modules/ci-info": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", @@ -12479,6 +10717,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12815,14 +11054,6 @@ "dev": true, "license": "ISC" }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, - "license": "ISC", - "peer": true - }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", @@ -12837,6 +11068,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, "license": "MIT" }, "node_modules/compare-func": { @@ -13065,21 +11297,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "browserslist": "^4.28.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -13202,6 +11419,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -13593,6 +11811,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13726,6 +11945,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, "license": "MIT", "dependencies": { "strip-bom": "^4.0.0" @@ -14248,12 +12468,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -14264,6 +12486,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -14283,21 +12506,6 @@ "once": "^1.4.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", - "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -14471,14 +12679,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -14541,6 +12741,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, "license": "MIT" }, "node_modules/esbuild": { @@ -15226,17 +13427,6 @@ "dev": true, "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", @@ -15570,6 +13760,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, "license": "MIT", "dependencies": { "commondir": "^1.0.1", @@ -15587,6 +13778,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -15865,6 +14057,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, "funding": [ { "type": "github", @@ -16047,6 +14240,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -16517,14 +14711,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true - }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -16832,6 +15018,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, "license": "MIT", "dependencies": { "is-stream": "^2.0.0", @@ -16848,6 +15035,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" @@ -16950,6 +15138,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, "node_modules/html-parse-stringify": { @@ -17259,6 +15448,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -17684,6 +15874,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17936,6 +16127,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18010,6 +16202,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, "license": "MIT" }, "node_modules/is-unc-path": { @@ -18132,6 +16325,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/isstream": { @@ -18145,6 +16339,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -18154,6 +16349,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "append-transform": "^2.0.0" @@ -18206,6 +16402,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, "license": "ISC", "dependencies": { "archy": "^1.0.0", @@ -18223,6 +16420,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -18232,6 +16430,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -18244,6 +16443,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -18253,6 +16453,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", @@ -18267,6 +16468,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", @@ -18281,6 +16483,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -21932,21 +20135,6 @@ "node": ">=8" } }, - "node_modules/loader-runner": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", - "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -21969,18 +20157,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, "license": "MIT" }, "node_modules/lodash.get": { @@ -22161,6 +20342,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -22176,6 +20358,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -23504,6 +21687,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -23819,6 +22003,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, "license": "MIT", "dependencies": { "process-on-spawn": "^1.0.0" @@ -24268,6 +22453,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", @@ -24309,6 +22495,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -24320,12 +22507,14 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, "license": "MIT" }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -24339,6 +22528,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -24353,6 +22543,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -24373,6 +22564,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.7.5", @@ -24388,6 +22580,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -24400,6 +22593,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -24415,6 +22609,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -24430,6 +22625,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -24442,6 +22638,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -24454,6 +22651,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -24463,6 +22661,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -24477,12 +22676,14 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, "license": "ISC" }, "node_modules/nyc/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -24505,6 +22706,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -24977,6 +23179,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, "license": "ISC", "dependencies": { "graceful-fs": "^4.1.15", @@ -25514,6 +23717,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -25633,6 +23837,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -25645,6 +23850,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -25658,6 +23864,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -25670,6 +23877,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -25685,6 +23893,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -25766,6 +23975,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -25987,6 +24197,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, "license": "MIT", "dependencies": { "fromentries": "^1.2.0" @@ -26461,6 +24672,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -26562,6 +24774,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -27152,28 +25365,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -27207,51 +25398,11 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/regjsparser": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", - "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, "license": "ISC", "dependencies": { "es6-error": "^4.0.1" @@ -27358,6 +25509,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -27376,6 +25528,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, "license": "ISC" }, "node_modules/requireindex": { @@ -27534,6 +25687,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -27550,6 +25704,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -27570,6 +25725,7 @@ "version": "4.52.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -27748,7 +25904,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -27785,49 +25941,10 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "loose-envify": "^1.1.0" } }, "node_modules/semver": { @@ -27843,6 +25960,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, "license": "ISC" }, "node_modules/set-function-length": { @@ -27902,6 +26020,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -27914,6 +26033,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -28008,6 +26128,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/sigstore": { @@ -28157,6 +26278,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -28203,6 +26325,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^2.0.0", @@ -28220,6 +26343,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -28233,6 +26357,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -28242,6 +26367,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -28483,6 +26609,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -28600,6 +26727,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -28626,6 +26754,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -29381,21 +27510,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -29511,114 +27625,6 @@ "node": ">=4" } }, - "node_modules/terser": { - "version": "5.46.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", - "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", - "devOptional": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true, - "license": "MIT", - "peer": true - }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -30328,6 +28334,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "license": "MIT", "dependencies": { "is-typedarray": "^1.0.0" @@ -30416,54 +28423,6 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -31034,6 +28993,7 @@ "version": "5.4.20", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -31209,6 +29169,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31225,6 +29186,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31241,6 +29203,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31257,6 +29220,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31273,6 +29237,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31289,6 +29254,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31305,6 +29271,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31321,6 +29288,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31337,6 +29305,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31353,6 +29322,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31369,6 +29339,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31385,6 +29356,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31401,6 +29373,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31417,6 +29390,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31433,6 +29407,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31449,6 +29424,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31465,6 +29441,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31481,6 +29458,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31497,6 +29475,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31513,6 +29492,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31529,6 +29509,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31545,6 +29526,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31561,6 +29543,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -31574,6 +29557,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -31788,21 +29772,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -31829,66 +29798,6 @@ "node": ">=12" } }, - "node_modules/webpack": { - "version": "5.106.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", - "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "loader-runner": "^4.3.1", - "mime-db": "^1.54.0", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.0.tgz", - "integrity": "sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -31896,17 +29805,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -31961,6 +29859,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -32041,6 +29940,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { diff --git a/package.json b/package.json index 277242f31..d5613efa0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.1.0-alpha.4", + "@iqss/dataverse-client-javascript": "2.1.0-pr430.a6e163d", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/public/locales/en/datasetTemplates.json b/public/locales/en/datasetTemplates.json index 7f70cc561..52d7a62eb 100644 --- a/public/locales/en/datasetTemplates.json +++ b/public/locales/en/datasetTemplates.json @@ -45,7 +45,12 @@ "deleteSuccess": "Template deleted.", "deleteError": "Something went wrong deleting the template. Try again later.", "copySuccess": "Template copied.", - "copyError": "Something went wrong copying the template. Try again later." + "copyError": "Something went wrong copying the template. Try again later.", + "setDefaultSuccess": "The template has been selected as the default template for this dataverse.", + "setDefaultError": "Something went wrong setting the template as default. Try again later.", + "unsetDefaultSuccess": "The template has been removed as the default template for this dataverse.", + "unsetDefaultError": "Something went wrong removing the default template. Try again later.", + "editSuccess": "Template updated." }, "copyNamePrefix": "copy {{name}}", "preview": { @@ -92,5 +97,24 @@ "termsOfAccessLabel": "Terms of Access for Restricted Files", "dataAccessPlaceLabel": "Data Access Place" } + }, + "editTemplate": { + "metadataPageTitle": "Edit Template Metadata", + "termsPageTitle": "Edit Template Terms", + "actions": { + "metadata": "Metadata", + "terms": "Terms" + }, + "alerts": { + "metadataUpdated": "Template metadata updated.", + "licenseUpdated": "Template license/terms updated.", + "termsOfAccessUpdated": "Template terms of access updated." + }, + "errors": { + "saveMetadataFailed": "Something went wrong updating the template metadata. Try again later.", + "saveLicenseFailed": "Something went wrong updating the template license/terms. Try again later.", + "saveTermsOfAccessFailed": "Something went wrong updating the template terms of access. Try again later.", + "loadingTemplate": "Something went wrong loading the template. Try again later." + } } } diff --git a/public/locales/es/datasetTemplates.json b/public/locales/es/datasetTemplates.json index 13bb0c65d..68ca79305 100644 --- a/public/locales/es/datasetTemplates.json +++ b/public/locales/es/datasetTemplates.json @@ -44,7 +44,12 @@ "deleteSuccess": "Plantilla eliminada.", "deleteError": "Algo salió mal al eliminar la plantilla. Inténtelo de nuevo más tarde.", "copySuccess": "Plantilla copiada.", - "copyError": "Algo salió mal al copiar la plantilla. Inténtelo de nuevo más tarde." + "copyError": "Algo salió mal al copiar la plantilla. Inténtelo de nuevo más tarde.", + "setDefaultSuccess": "La plantilla ha sido seleccionada como la plantilla predeterminada para este dataverse.", + "setDefaultError": "Algo salió mal al establecer la plantilla como predeterminada. Inténtelo de nuevo más tarde.", + "unsetDefaultSuccess": "La plantilla ha sido eliminada como la plantilla predeterminada para este dataverse.", + "unsetDefaultError": "Algo salió mal al eliminar la plantilla predeterminada. Inténtelo de nuevo más tarde.", + "editSuccess": "Plantilla actualizada." }, "copyNamePrefix": "copia {{name}}", "preview": { @@ -91,5 +96,24 @@ "termsOfAccessLabel": "Términos de acceso para archivos restringidos", "dataAccessPlaceLabel": "Lugar de acceso a los datos" } + }, + "editTemplate": { + "metadataPageTitle": "Editar metadatos de plantilla", + "termsPageTitle": "Editar términos de plantilla", + "actions": { + "metadata": "Metadatos", + "terms": "Términos" + }, + "alerts": { + "metadataUpdated": "Metadatos de plantilla actualizados.", + "licenseUpdated": "Licencia/términos de plantilla actualizados.", + "termsOfAccessUpdated": "Términos de acceso de plantilla actualizados." + }, + "errors": { + "saveMetadataFailed": "Algo salió mal al actualizar los metadatos de la plantilla. Inténtelo de nuevo más tarde.", + "saveLicenseFailed": "Algo salió mal al actualizar la licencia/términos de la plantilla. Inténtelo de nuevo más tarde.", + "saveTermsOfAccessFailed": "Algo salió mal al actualizar los términos de acceso de la plantilla. Inténtelo de nuevo más tarde.", + "loadingTemplate": "Algo salió mal al cargar la plantilla. Inténtelo de nuevo más tarde." + } } } diff --git a/src/router/routes.tsx b/src/router/routes.tsx index c664b40fa..53f436faa 100644 --- a/src/router/routes.tsx +++ b/src/router/routes.tsx @@ -107,10 +107,10 @@ const CreateTemplatePage = lazy(() => ) ) -const EditDatasetTemplateTermsPage = lazy(() => - import('../sections/templates/edit-template-terms/EditTemplateTermsFactory').then( - ({ EditTemplateTermsFactory }) => ({ - default: () => EditTemplateTermsFactory.create() +const EditDatasetTemplatePage = lazy(() => + import('../sections/templates/edit-template/EditTemplateFactory').then( + ({ EditTemplateFactory }) => ({ + default: () => EditTemplateFactory.create() }) ) ) @@ -333,10 +333,10 @@ export const routes: RouteObject[] = [ errorElement: }, { - path: Route.TEMPLATES_EDIT_TERMS, + path: Route.TEMPLATES_EDIT, element: ( }> - + ), errorElement: diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index d9853c57a..aff771c06 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -22,8 +22,7 @@ export enum Route { EDIT_FEATURED_ITEMS = '/collections/:collectionId/edit-featured-items', COLLECTION_TEMPLATES = '/:collectionId/templates', TEMPLATES_CREATE = '/:collectionId/templates/create', - TEMPLATES_EDIT_METADATA = '/:collectionId/templates/:templateId/edit/metadata', - TEMPLATES_EDIT_TERMS = '/:collectionId/templates/:templateId/edit/terms', + TEMPLATES_EDIT = '/templates/edit', FEATURED_ITEM = '/featured-item/:parentCollectionId/:featuredItemId', NOT_FOUND_PAGE = '/404', AUTH_CALLBACK = '/auth-callback', @@ -40,10 +39,18 @@ export const RouteWithParams = { EDIT_FEATURED_ITEMS: (collectionId: string) => `/collections/${collectionId}/edit-featured-items`, COLLECTION_TEMPLATES: (collectionId: string) => `/${collectionId}/templates`, TEMPLATES_CREATE: (collectionId: string) => `/${collectionId}/templates/create`, - TEMPLATES_EDIT_METADATA: (collectionId: string, templateId: number | string) => - `/${collectionId}/templates/${templateId}/edit/metadata`, - TEMPLATES_EDIT_TERMS: (collectionId: string, templateId: number | string) => - `/${collectionId}/templates/${templateId}/edit/terms`, + TEMPLATES_EDIT: ( + collectionId: string, + templateId: number | string, + editMode: TemplateEditMode + ) => { + const searchParams = new URLSearchParams({ + [QueryParamKey.ID]: templateId.toString(), + [QueryParamKey.OWNER_ID]: collectionId, + [QueryParamKey.EDIT_MODE]: editMode + }) + return `${Route.TEMPLATES_EDIT}?${searchParams.toString()}` + }, EDIT_FILE_METADATA: ( datasetPersistentId: string, datasetVersion: string, @@ -89,9 +96,17 @@ export enum QueryParamKey { COLLECTION_ID = 'collectionId', TAB = 'tab', FILE_ID = 'id', + ID = 'id', + OWNER_ID = 'ownerId', + EDIT_MODE = 'editMode', DATASET_VERSION = 'datasetVersion', REFERRER = 'referrer', AUTH_STATE = 'state', VALID_TOKEN_BUT_NOT_LINKED_ACCOUNT = 'validTokenButNotLinkedAccount', TOOL_TYPE = 'toolType' } + +export enum TemplateEditMode { + METADATA = 'METADATA', + LICENSE = 'LICENSE' +} diff --git a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx index fb39ee1df..94f71feda 100644 --- a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx +++ b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx @@ -230,7 +230,7 @@ export function EditTermsOfAccess({ disabled={isLoading || (!isRequestAccessEnabled && !isTermsOfAccessProvided)}> {isLoading ? tShared('saving') : tShared('saveChanges')} - + {tShared('cancel')} diff --git a/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.module.scss b/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.module.scss index 8c4b6631a..f96e1732a 100644 --- a/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.module.scss +++ b/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.module.scss @@ -10,5 +10,7 @@ } .form-actions { + display: flex; + gap: 1rem; margin-top: $spacer; } diff --git a/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.tsx b/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.tsx index 337072c8d..db7b691dd 100644 --- a/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.tsx +++ b/src/sections/shared/form/TemplateMetadataForm/TemplateForm/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { FormProvider, useForm } from 'react-hook-form' @@ -20,14 +20,16 @@ import { } from '../../DatasetMetadataForm/MetadataFieldsHelper' import { MetadataBlockFormFields } from '../../DatasetMetadataForm/MetadataForm/MetadataBlockFormFields' import { RequiredFieldText } from '../../RequiredFieldText/RequiredFieldText' -import { RouteWithParams } from '@/sections/Route.enum' +import { RouteWithParams, TemplateEditMode } from '@/sections/Route.enum' import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { Template } from '@/templates/domain/models/Template' import { useGetTemplatesByCollectionId } from '@/templates/domain/hooks/useGetTemplatesByCollectionId' import { SubmissionStatus, useSubmitTemplate } from '../useSubmitTemplate' import { TemplateInfo, TemplateInstructionInfo } from '@/templates/domain/models/TemplateInfo' +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' import styles from './index.module.scss' -interface TemplateFormProps { +type CommonTemplateFormProps = { collectionId: string templateRepository: TemplateRepository metadataBlocksInfo: MetadataBlockInfo[] @@ -35,21 +37,52 @@ interface TemplateFormProps { metadataFieldsForMapping: Record> } -export const TemplateForm = ({ - collectionId, - templateRepository, - metadataBlocksInfo, - formDefaultValues, - metadataFieldsForMapping -}: TemplateFormProps) => { +type TemplateFormProps = + | (CommonTemplateFormProps & { + mode: 'create' + template?: never + }) + | (CommonTemplateFormProps & { + mode: 'edit' + template: Template + }) + +export const TemplateForm = (props: TemplateFormProps) => { + const { + mode, + collectionId, + templateRepository, + metadataBlocksInfo, + formDefaultValues, + metadataFieldsForMapping + } = props + const template = mode === 'edit' ? props.template : undefined + const { t } = useTranslation('datasetTemplates') + const { t: tShared } = useTranslation('shared') const navigate = useNavigate() const [validationError, setValidationError] = useState(null) - const [templateName, setTemplateName] = useState('') - const [templateInstructions, setTemplateInstructions] = useState< - Record - >({}) - const { submissionStatus, submitError, submitTemplate } = useSubmitTemplate(collectionId) + const [templateName, setTemplateName] = useState(template?.name ?? '') + + const initialInstructionsMap = useMemo>(() => { + if (!template?.instructions) return {} + return template.instructions.reduce>( + (acc, instruction) => { + acc[instruction.instructionField] = instruction + return acc + }, + {} + ) + }, [template]) + + const [templateInstructions, setTemplateInstructions] = + useState>(initialInstructionsMap) + + const submitOptions = + mode === 'edit' && template + ? ({ mode: 'edit', templateRepository, templateId: template.id } as const) + : ({ mode: 'create', templateRepository, collectionId } as const) + const { submissionStatus, submitError, submitTemplate } = useSubmitTemplate(submitOptions) const { fetchDatasetTemplates } = useGetTemplatesByCollectionId({ templateRepository, @@ -75,7 +108,7 @@ export const TemplateForm = ({ }) } - const handleSaveAndAddTerms = () => { + const handleSubmit = () => { if (!templateName.trim()) { setValidationError(t('createTemplate.errors.nameRequired')) return @@ -87,7 +120,7 @@ export const TemplateForm = ({ const formValuesBackToDots = MetadataFieldsHelper.replaceSlashKeysWithDot(formValues) const datasetDto = MetadataFieldsHelper.formatFormValuesToDatasetDTO( formValuesBackToDots, - 'create' + mode === 'edit' ? 'edit' : 'create' ) const templateFields = datasetDto.metadataBlocks.flatMap((metadataBlock) => MetadataFieldsHelper.buildTemplateFieldsFromMetadataValues( @@ -98,6 +131,21 @@ export const TemplateForm = ({ const instructions = Object.values(templateInstructions) + if (mode === 'edit') { + const updatePayload: UpdateTemplateMetadataInfo = { + name: templateName.trim(), + fields: templateFields, + instructions + } + const didSubmit = await submitTemplate(updatePayload) + if (!didSubmit) return + + navigate(RouteWithParams.COLLECTION_TEMPLATES(collectionId), { + state: { fromEditTemplate: true } + }) + return + } + const templatePayload: TemplateInfo = { name: templateName.trim(), fields: templateFields, @@ -110,21 +158,27 @@ export const TemplateForm = ({ const updatedTemplates = await fetchDatasetTemplates() const normalizedName = templateName.trim().toLowerCase() const createdTemplate = updatedTemplates.find( - (template) => template.name.trim().toLowerCase() === normalizedName + (currentTemplate) => currentTemplate.name.trim().toLowerCase() === normalizedName ) if (!createdTemplate) return - navigate(RouteWithParams.TEMPLATES_EDIT_TERMS(collectionId, createdTemplate.id), { - state: { fromCreateTemplate: true } - }) + navigate( + RouteWithParams.TEMPLATES_EDIT(collectionId, createdTemplate.id, TemplateEditMode.LICENSE), + { + state: { fromCreateTemplate: true } + } + ) })() } + const submitButtonLabel = + mode === 'edit' ? tShared('saveChanges') : t('createTemplate.saveAddTerms') + return ( - {submissionStatus === SubmissionStatus.SubmitComplete && ( + {submissionStatus === SubmissionStatus.SubmitComplete && mode === 'create' && ( {t('createTemplate.alerts.success')} @@ -181,16 +235,16 @@ export const TemplateForm = ({ - {t('createTemplate.saveAddTerms')} + {submitButtonLabel} navigate(RouteWithParams.COLLECTION_TEMPLATES(collectionId))}> - {t('createTemplate.cancel')} + onClick={() => navigate(RouteWithParams.COLLECTION_TEMPLATES(collectionId))} + data-testid="cancel-edit-template-metadata-button"> + {tShared('cancel')} diff --git a/src/sections/shared/form/TemplateMetadataForm/TemplateMetadataForm.tsx b/src/sections/shared/form/TemplateMetadataForm/TemplateMetadataForm.tsx index 641a3ffbe..abdd0c3ad 100644 --- a/src/sections/shared/form/TemplateMetadataForm/TemplateMetadataForm.tsx +++ b/src/sections/shared/form/TemplateMetadataForm/TemplateMetadataForm.tsx @@ -3,21 +3,34 @@ import { Alert } from '@iqss/dataverse-design-system' import { MetadataBlockInfoRepository } from '@/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { type MetadataField } from '@/metadata-block-info/domain/models/MetadataBlockInfo' import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { Template } from '@/templates/domain/models/Template' import { useGetMetadataBlocksInfo } from '../DatasetMetadataForm/useGetMetadataBlocksInfo' import { MetadataFieldsHelper } from '../DatasetMetadataForm/MetadataFieldsHelper' import { MetadataFormSkeleton } from '../DatasetMetadataForm/MetadataForm/MetadataFormSkeleton' import { TemplateForm } from './TemplateForm' -interface TemplateMetadataFormProps { - collectionId: string - metadataBlockInfoRepository: MetadataBlockInfoRepository - templateRepository: TemplateRepository -} +type TemplateMetadataFormProps = + | { + mode: 'create' + collectionId: string + metadataBlockInfoRepository: MetadataBlockInfoRepository + templateRepository: TemplateRepository + template?: never + } + | { + mode: 'edit' + collectionId: string + metadataBlockInfoRepository: MetadataBlockInfoRepository + templateRepository: TemplateRepository + template: Template + } export const TemplateMetadataForm = ({ + mode, collectionId, metadataBlockInfoRepository, - templateRepository + templateRepository, + template }: TemplateMetadataFormProps) => { const { metadataBlocksInfo: metadataBlocksInfoForDisplay, @@ -29,13 +42,24 @@ export const TemplateMetadataForm = ({ metadataBlockInfoRepository }) - const metadataBlocksInfo = useMemo( - () => - MetadataFieldsHelper.replaceMetadataBlocksInfoDotNamesKeysWithSlash( - metadataBlocksInfoForDisplay - ), - [metadataBlocksInfoForDisplay] - ) + const metadataBlocksInfo = useMemo(() => { + const normalized = MetadataFieldsHelper.replaceMetadataBlocksInfoDotNamesKeysWithSlash( + metadataBlocksInfoForDisplay + ) + + if (mode === 'edit' && template?.datasetMetadataBlocks) { + const normalizedTemplateBlocks = + MetadataFieldsHelper.replaceDatasetMetadataBlocksDotKeysWithSlash( + template.datasetMetadataBlocks + ) + return MetadataFieldsHelper.addFieldValuesToMetadataBlocksInfo( + normalized, + normalizedTemplateBlocks + ) + } + + return normalized + }, [metadataBlocksInfoForDisplay, mode, template]) const metadataFieldsForMapping = useMemo( () => @@ -55,7 +79,7 @@ export const TemplateMetadataForm = ({ ) if (isLoading) { - return + return } if (error) { @@ -66,8 +90,23 @@ export const TemplateMetadataForm = ({ ) } + if (mode === 'edit') { + return ( + + ) + } + return ( Promise - submitError: null - } - | { - submissionStatus: SubmissionStatus.Errored - submitTemplate: (payload: TemplateInfo) => Promise - submitError: string - } +type CreateOptions = { + mode: 'create' + templateRepository: TemplateRepository + collectionId: string +} + +type EditOptions = { + mode: 'edit' + templateRepository: TemplateRepository + templateId: number +} + +type UseSubmitTemplateOptions = CreateOptions | EditOptions -export function useSubmitTemplate(collectionId: string): UseSubmitTemplateReturnType { - const { t } = useTranslation('datasetTemplates', { keyPrefix: 'createTemplate' }) +interface UseSubmitTemplateReturn { + submissionStatus: SubmissionStatus + submitTemplate: (payload: TemplateInfo | UpdateTemplateMetadataInfo) => Promise + submitError: string | null +} + +export function useSubmitTemplate(options: UseSubmitTemplateOptions): UseSubmitTemplateReturn { + const { t } = useTranslation('datasetTemplates') const [submissionStatus, setSubmissionStatus] = useState( SubmissionStatus.NotSubmitted ) const [submitError, setSubmitError] = useState(null) - const submitTemplate = async (template: TemplateInfo): Promise => { + const submitTemplate = async ( + payload: TemplateInfo | UpdateTemplateMetadataInfo + ): Promise => { setSubmissionStatus(SubmissionStatus.IsSubmitting) setSubmitError(null) try { - await createTemplate.execute( - template as Parameters[0], - collectionId - ) + if (options.mode === 'create') { + await createTemplateUseCase( + options.templateRepository, + payload as TemplateInfo, + options.collectionId + ) + } else { + await updateTemplateMetadataUseCase( + options.templateRepository, + options.templateId, + payload as UpdateTemplateMetadataInfo + ) + } setSubmissionStatus(SubmissionStatus.SubmitComplete) return true } catch (error) { + console.error('useSubmitTemplate error:', error) if (error instanceof WriteError) { const handler = new JSDataverseWriteErrorHandler(error) const formattedError = handler.getReasonWithoutStatusCode() ?? /* istanbul ignore next */ handler.getErrorMessage() setSubmitError(formattedError) + } else if (error instanceof Error && error.message) { + setSubmitError(error.message) } else { - setSubmitError(t('errors.saveFailed')) + setSubmitError( + options.mode === 'create' + ? t('createTemplate.errors.saveFailed') + : t('editTemplate.errors.saveMetadataFailed') + ) } setSubmissionStatus(SubmissionStatus.Errored) return false @@ -63,5 +90,5 @@ export function useSubmitTemplate(collectionId: string): UseSubmitTemplateReturn submissionStatus, submitTemplate, submitError - } as UseSubmitTemplateReturnType + } } diff --git a/src/sections/templates/DatasetTemplates.tsx b/src/sections/templates/DatasetTemplates.tsx index b2b232ced..19b6e2636 100644 --- a/src/sections/templates/DatasetTemplates.tsx +++ b/src/sections/templates/DatasetTemplates.tsx @@ -1,5 +1,5 @@ -import { useMemo, useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { startTransition, useEffect, useMemo, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Alert, @@ -23,7 +23,7 @@ import { Trash } from 'react-bootstrap-icons' import { toast } from 'react-toastify' -import { RouteWithParams } from '@/sections/Route.enum' +import { RouteWithParams, TemplateEditMode } from '@/sections/Route.enum' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { DatasetTemplatesSkeleton } from './DatasetTemplatesSkeleton' import { useGetCollectionUserPermissions } from '@/shared/hooks/useGetCollectionUserPermissions' @@ -34,12 +34,11 @@ import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepo import { useCollection } from '../collection/useCollection' import { useGetTemplatesByCollectionId } from '@/templates/domain/hooks/useGetTemplatesByCollectionId' import { NotFoundPage } from '../not-found-page/NotFoundPage' -import { NotImplementedModal } from '../not-implemented/NotImplementedModal' -import { useNotImplementedModal } from '../not-implemented/NotImplementedModalContext' import { Template } from '@/templates/domain/models/Template' import { ConfirmDeleteTemplateModal } from './confirm-delete-template-modal/ConfirmDeleteTemplateModal' import { TemplatePreviewModal } from './template-preview-modal/TemplatePreviewModal' import { useCopyTemplate } from './useCopyTemplate' +import { useSetTemplateAsDefault } from './useSetTemplateAsDefault' import styles from './DatasetTemplates.module.scss' @@ -57,9 +56,16 @@ export const DatasetTemplates = ({ collectionId }: DatasetTemplatesProps) => { const { t } = useTranslation('datasetTemplates') - const { t: tDataset } = useTranslation('dataset') const navigate = useNavigate() - const { isModalOpen, hideModal, showModal } = useNotImplementedModal() + const location = useLocation() + + useEffect(() => { + const state = location.state as { fromEditTemplate?: boolean } | null + if (state?.fromEditTemplate) { + toast.success(t('alerts.editSuccess')) + navigate(location.pathname, { replace: true, state: null }) + } + }, [location, navigate, t]) const [sortBy, setSortBy] = useState<'name' | 'created' | 'usage' | null>(null) const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc') const [templateToDelete, setTemplateToDelete] = useState(undefined) @@ -75,7 +81,8 @@ export const DatasetTemplates = ({ datasetTemplates, isLoadingDatasetTemplates, errorGetDatasetTemplates, - fetchDatasetTemplates + fetchDatasetTemplates, + toggleDefaultTemplate } = useGetTemplatesByCollectionId({ templateRepository, collectionIdOrAlias: collectionId @@ -85,6 +92,11 @@ export const DatasetTemplates = ({ templateRepository, metadataBlockInfoRepository }) + const { handleSetTemplateAsDefault, handleUnsetTemplateAsDefault, isSettingDefault } = + useSetTemplateAsDefault({ + collectionId, + templateRepository + }) const { collectionUserPermissions } = useGetCollectionUserPermissions({ collectionIdOrAlias: collectionId, @@ -175,15 +187,14 @@ export const DatasetTemplates = ({ } const handleEditTemplateAction = (template: Template, action: 'metadata' | 'terms') => { - if (action === 'metadata') { - navigate(RouteWithParams.TEMPLATES_EDIT_METADATA(collectionId, template.id)) - return - } - navigate(RouteWithParams.TEMPLATES_EDIT_TERMS(collectionId, template.id)) + const editMode = action === 'metadata' ? TemplateEditMode.METADATA : TemplateEditMode.LICENSE + navigate(RouteWithParams.TEMPLATES_EDIT(collectionId, template.id, editMode)) } const handleOpenPreviewModal = (template: Template) => { - setTemplateToPreview(template) + startTransition(() => { + setTemplateToPreview(template) + }) } const handleClosePreviewModal = () => { @@ -211,7 +222,6 @@ export const DatasetTemplates = ({ return ( <> - {template.isDefault ? ( - - - {t('actions.default')} - + { + if (isSettingDefault) return + const didUnset = await handleUnsetTemplateAsDefault() + if (didUnset) { + toggleDefaultTemplate(null) + } + }}> + + + {t('actions.default')} + + ) : ( { + const didSet = await handleSetTemplateAsDefault(template.id) + if (didSet) { + toggleDefaultTemplate(template.id) + } + }} className={styles['make-default-button']}> {t('actions.makeDefault')} @@ -378,12 +403,11 @@ export const DatasetTemplates = ({ onSelect={(eventKey) => handleEditTemplateAction(template, eventKey as 'metadata' | 'terms') }> - {/* waiting for Edit Template api support */} - - {tDataset('datasetActionButtons.editDataset.metadata')} + + {t('editTemplate.actions.metadata')} - - {tDataset('datasetActionButtons.editDataset.terms')} + + {t('editTemplate.actions.terms')} = { + dataset: Route.DATASETS, + collection: Route.COLLECTIONS, + file: Route.FILES +} + export const CreateTemplate = ({ collectionId, collectionRepository, @@ -38,13 +48,55 @@ export const CreateTemplate = ({ return } + const hierarchyItems = collection.hierarchy.toArray() + return ( - + + {hierarchyItems.map((item, index) => { + const isFirst = index === 0 + if (isFirst) { + return ( + {item.name}> + }}> + {item.name} + + ) + } + return ( + {item.name}> + }}> + {item.name} + + ) + })} + + {t('pageTitle')} + + {t('createTemplate.pageTitle')} + {t('createTemplate.pageTitle')} { + const { t } = useTranslation('datasetTemplates') + const { collection, isLoading: isLoadingCollection } = useCollection( + collectionRepository, + collectionId + ) + const { template, isLoadingTemplate, errorGetTemplate } = useGetTemplate({ + templateRepository, + templateId + }) + + const isLoadingData = isLoadingCollection || isLoadingTemplate + + if (!isLoadingCollection && !collection) { + return + } + + if (isLoadingData || !collection) { + return + } + + if (errorGetTemplate || !template) { + return ( + + + {errorGetTemplate ?? t('editTemplate.errors.loadingTemplate')} + + + ) + } + + return ( + + + + {collectionId} + + + {t('pageTitle')} + + {t('editTemplate.metadataPageTitle')} + + + {t('editTemplate.metadataPageTitle')} + + + + ) +} diff --git a/src/sections/templates/edit-template-metadata/EditTemplateMetadataSkeleton.tsx b/src/sections/templates/edit-template-metadata/EditTemplateMetadataSkeleton.tsx new file mode 100644 index 000000000..5a9e3cb9d --- /dev/null +++ b/src/sections/templates/edit-template-metadata/EditTemplateMetadataSkeleton.tsx @@ -0,0 +1,25 @@ +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' +import { Col, Row } from '@iqss/dataverse-design-system' +import { BreadcrumbsSkeleton } from '@/sections/shared/hierarchy/BreadcrumbsSkeleton' +import 'react-loading-skeleton/dist/skeleton.css' + +export const EditTemplateMetadataSkeleton = () => ( + + + + + + + + + + + + + + + + + + +) diff --git a/src/sections/templates/edit-template-terms/EditTemplateLicenseTerms.tsx b/src/sections/templates/edit-template-terms/EditTemplateLicenseTerms.tsx new file mode 100644 index 000000000..635acff5f --- /dev/null +++ b/src/sections/templates/edit-template-terms/EditTemplateLicenseTerms.tsx @@ -0,0 +1,288 @@ +import { useEffect, useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { useForm, Controller, FormProvider } from 'react-hook-form' +import { toast } from 'react-toastify' +import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system' +import { CustomTerms } from '@/dataset/domain/models/Dataset' +import { LicenseRepository } from '@/licenses/domain/repositories/LicenseRepository' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { Template } from '@/templates/domain/models/Template' +import { useGetLicenses } from '@/sections/edit-dataset-terms/edit-license-and-terms/useGetLicenses' +import { useUpdateTemplateLicenseTerms } from './useUpdateTemplateLicenseTerms' +import styles from '@/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.module.scss' + +const CUSTOM_LICENSE_VALUE = 'CUSTOM' as const + +interface FormData { + license: string + customTerms?: CustomTerms +} + +interface EditTemplateLicenseTermsProps { + template: Template + templateRepository: TemplateRepository + licenseRepository: LicenseRepository + onSuccess: () => void + onCancel?: () => void + onFormStateChange?: (isDirty: boolean) => void +} + +export function EditTemplateLicenseTerms({ + template, + templateRepository, + licenseRepository, + onSuccess, + onCancel, + onFormStateChange +}: EditTemplateLicenseTermsProps) { + const { t: tDataset } = useTranslation('dataset') + const { t: tTemplates } = useTranslation('datasetTemplates') + const { t: tShared } = useTranslation('shared') + + const formContainerRef = useRef(null) + + const { licenses, isLoadingLicenses, errorLicenses } = useGetLicenses({ + licenseRepository, + autoFetch: true + }) + + const { handleUpdateLicenseTerms, isLoading, error } = useUpdateTemplateLicenseTerms({ + templateRepository, + onSuccess: () => { + toast.success(tTemplates('editTemplate.alerts.licenseUpdated')) + onSuccess() + } + }) + + const initialCustomTerms = useMemo(() => { + if (template.termsOfUse.customTerms) { + return template.termsOfUse.customTerms + } + return { + termsOfUse: '', + confidentialityDeclaration: '', + specialPermissions: '', + restrictions: '', + citationRequirements: '', + depositorRequirements: '', + conditions: '', + disclaimer: '' + } + }, [template.termsOfUse.customTerms]) + + const licenseOptions = useMemo(() => { + const dynamicOptions = licenses + .sort((a, b) => a.sortOrder - b.sortOrder) + .map((license) => ({ + value: license.id.toString(), + label: license.name, + uri: license.uri, + iconUri: license.iconUri || '', + isDefault: license.isDefault + })) + + dynamicOptions.push({ + value: CUSTOM_LICENSE_VALUE, + label: tDataset('editTerms.datasetTerms.customTermsLabel'), + uri: '', + iconUri: '', + isDefault: template.termsOfUse.customTerms !== undefined + }) + + return dynamicOptions + }, [licenses, tDataset, template.termsOfUse.customTerms]) + + const defaultLicenseValue = useMemo(() => { + if (template.termsOfUse.customTerms !== undefined) { + return CUSTOM_LICENSE_VALUE + } + const matchingLicense = licenseOptions.find((option) => option.label === template.license?.name) + return matchingLicense?.value + }, [licenseOptions, template.license?.name, template.termsOfUse.customTerms]) + + const form = useForm({ + defaultValues: { + license: defaultLicenseValue, + customTerms: initialCustomTerms + }, + mode: 'onChange' + }) + + const { + control, + handleSubmit, + watch, + reset, + formState: { isValid, isDirty } + } = form + + useEffect(() => { + onFormStateChange?.(isDirty) + }, [isDirty, onFormStateChange]) + + useEffect(() => { + if (!isLoadingLicenses && licenseOptions.length > 0) { + reset({ + license: defaultLicenseValue, + customTerms: initialCustomTerms + }) + } + }, [defaultLicenseValue, initialCustomTerms, isLoadingLicenses, licenseOptions.length, reset]) + + const watchedLicense = watch('license') + const currentLicenseOption = licenseOptions.find((option) => option.value === watchedLicense) + const isCustomTerms = watchedLicense === CUSTOM_LICENSE_VALUE + + const customTermsFields = useMemo(() => { + return Object.keys(initialCustomTerms).map((fieldName) => ({ + name: fieldName, + translationKey: fieldName, + required: fieldName === 'termsOfUse' && isCustomTerms, + rows: 4, + rules: + fieldName === 'termsOfUse' && isCustomTerms + ? { required: tDataset('editTerms.datasetTerms.customTermsRequired') } + : {} + })) + }, [initialCustomTerms, isCustomTerms, tDataset]) + + const onSubmit = async (data: FormData) => { + if (data.license === CUSTOM_LICENSE_VALUE) { + await handleUpdateLicenseTerms(template.id, { + customTerms: data.customTerms + }) + } else { + const selectedLicense = licenseOptions.find((option) => option.value === data.license) + if (selectedLicense) { + await handleUpdateLicenseTerms(template.id, { name: selectedLicense.label }) + } + } + } + + return ( + + + { + void handleSubmit(onSubmit)(event) + }} + noValidate={true}> + + + {tDataset('license.title')} + + + + {tDataset('editTerms.datasetTerms.licenseDescription')} + + ( + + + + {licenseOptions.map((option) => ( + + {option.label} + + ))} + + {error?.message} + + + )} + /> + {errorLicenses && {errorLicenses}} + {currentLicenseOption && !isCustomTerms && ( + + {currentLicenseOption.iconUri && ( + + )} + {currentLicenseOption.uri && ( + + {currentLicenseOption.label} + + )} + + )} + + + + {isCustomTerms && ( + <> + {customTermsFields.map((field) => ( + + + {tDataset(`termsTab.${field.translationKey}`)} + + ( + + + + + {field.required && ( + + {error?.message} + + )} + + + + )} + /> + + ))} + > + )} + + {error && ( + + {error} + + )} + + + + {isLoading ? tShared('saving') : tShared('saveChanges')} + + {onCancel && ( + + {tShared('close')} + + )} + + + + + ) +} diff --git a/src/sections/templates/edit-template-terms/EditTemplateTerms.module.scss b/src/sections/templates/edit-template-terms/EditTemplateTerms.module.scss new file mode 100644 index 000000000..8dd256dab --- /dev/null +++ b/src/sections/templates/edit-template-terms/EditTemplateTerms.module.scss @@ -0,0 +1,3 @@ +.tab-container { + padding: 1.5rem 0; +} diff --git a/src/sections/templates/edit-template-terms/EditTemplateTermsFactory.tsx b/src/sections/templates/edit-template-terms/EditTemplateTermsFactory.tsx deleted file mode 100644 index 62c2b90bb..000000000 --- a/src/sections/templates/edit-template-terms/EditTemplateTermsFactory.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactElement } from 'react' -import { useParams } from 'react-router-dom' -import { TemplateJSDataverseRepository } from '@/templates/infrastructure/repositories/TemplateJSDataverseRepository' -import { EditTemplateTerms } from './index' - -const templateRepository = new TemplateJSDataverseRepository() - -export class EditTemplateTermsFactory { - static create(): ReactElement { - return - } -} - -function EditTemplateTermsWithParams() { - const { collectionId, templateId } = useParams<{ - collectionId: string - templateId: string - }>() as { - collectionId: string - templateId: string - } - - return ( - - ) -} diff --git a/src/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.tsx b/src/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.tsx new file mode 100644 index 000000000..213bb0a85 --- /dev/null +++ b/src/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.tsx @@ -0,0 +1,216 @@ +import { useEffect, useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { useForm, Controller, FormProvider, useWatch } from 'react-hook-form' +import { toast } from 'react-toastify' +import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { Template } from '@/templates/domain/models/Template' +import { useUpdateTemplateTermsOfAccess } from './useUpdateTemplateTermsOfAccess' +import styles from '@/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.module.scss' + +interface EditTemplateTermsOfAccessProps { + template: Template + templateRepository: TemplateRepository + onSuccess: () => void + onCancel?: () => void + onFormStateChange?: (isDirty: boolean) => void +} + +export function EditTemplateTermsOfAccess({ + template, + templateRepository, + onSuccess, + onCancel, + onFormStateChange +}: EditTemplateTermsOfAccessProps) { + const { t: tDataset } = useTranslation('dataset') + const { t: tTemplates } = useTranslation('datasetTemplates') + const { t: tShared } = useTranslation('shared') + + const defaultTermsOfAccess: TermsOfAccess = { + fileAccessRequest: false, + termsOfAccessForRestrictedFiles: undefined, + dataAccessPlace: undefined, + originalArchive: undefined, + availabilityStatus: undefined, + contactForAccess: undefined, + sizeOfCollection: undefined, + studyCompletion: undefined + } + + const initialTermsOfAccess: TermsOfAccess = + template.termsOfUse.termsOfAccess ?? defaultTermsOfAccess + const formContainerRef = useRef(null) + + const { handleUpdateTermsOfAccess, isLoading, error } = useUpdateTemplateTermsOfAccess({ + templateRepository, + onSuccess: () => { + toast.success(tTemplates('editTemplate.alerts.termsOfAccessUpdated')) + onSuccess() + } + }) + + const form = useForm({ + defaultValues: initialTermsOfAccess, + mode: 'onChange' + }) + + const { + control, + handleSubmit, + reset, + formState: { isDirty } + } = form + + useEffect(() => { + onFormStateChange?.(isDirty) + }, [isDirty, onFormStateChange]) + + useEffect(() => { + if (template.termsOfUse.termsOfAccess) { + reset(template.termsOfUse.termsOfAccess) + } + }, [template.termsOfUse.termsOfAccess, reset]) + + const fileAccessRequestValue = useWatch({ control, name: 'fileAccessRequest' }) + const termsOfAccessForRestrictedFilesValue = useWatch({ + control, + name: 'termsOfAccessForRestrictedFiles' + }) + const isRequestAccessEnabled = + fileAccessRequestValue === undefined ? true : Boolean(fileAccessRequestValue) + const isTermsOfAccessProvided = + typeof termsOfAccessForRestrictedFilesValue === 'string' && + termsOfAccessForRestrictedFilesValue.trim().length > 0 + + const termsOfAccessFields = useMemo(() => { + return Object.keys(initialTermsOfAccess) + .filter((fieldName) => fieldName !== 'fileAccessRequest') + .map((fieldName) => ({ + name: fieldName, + translationKey: + fieldName === 'termsOfAccessForRestrictedFiles' ? 'termsOfAccess' : fieldName, + required: false, + rows: 4, + type: 'textarea', + rules: {} + })) + .map((field) => { + if (field.name === 'termsOfAccessForRestrictedFiles') { + const required = !isRequestAccessEnabled + return { + ...field, + required, + rules: { + validate: (value: string | boolean | undefined) => + isRequestAccessEnabled || + (typeof value === 'string' && value.trim().length > 0) || + tDataset('termsTab.termsOfAccessRequiredWhenRequestDisabled') + } + } + } + return field + }) + }, [initialTermsOfAccess, isRequestAccessEnabled, tDataset]) + + return ( + + + {tDataset('termsTab.termsOfAccessInfo')} + + + + { + await handleUpdateTermsOfAccess(template.id, data) + })} + noValidate={true}> + + + + {tDataset('termsTab.requestAccess')} + + + + ( + + + + )} + /> + + + + {termsOfAccessFields.map((field) => ( + + + {tDataset(`termsTab.${field.translationKey}`)} + + ( + + + + + {field.required && ( + {error?.message} + )} + + + + )} + /> + + ))} + + {error && ( + + {error} + + )} + + + + {isLoading ? tShared('saving') : tShared('saveChanges')} + + {onCancel && ( + + {tShared('close')} + + )} + + + + + ) +} diff --git a/src/sections/templates/edit-template-terms/index.tsx b/src/sections/templates/edit-template-terms/index.tsx index 0f5846fe6..836b5961f 100644 --- a/src/sections/templates/edit-template-terms/index.tsx +++ b/src/sections/templates/edit-template-terms/index.tsx @@ -1,35 +1,39 @@ -// waiting to be implemented till Edit Template api support is ready - import { Link, useLocation, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { Accordion, Button, Breadcrumb, Alert } from '@iqss/dataverse-design-system' +import { Tabs, Breadcrumb, Alert } from '@iqss/dataverse-design-system' import { RouteWithParams } from '@/sections/Route.enum' import { useGetTemplate } from '@/templates/domain/hooks/useGetTemplate' import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' -import { License } from '@/sections/dataset/dataset-terms/License' -import { CustomTerms } from '@/sections/dataset/dataset-terms/CustomTerms' -import { TermsOfAccess } from '@/sections/dataset/dataset-terms/TermsOfAccess' -import { DatasetTermsRow } from '@/sections/dataset/dataset-terms/DatasetTermsRow' +import { LicenseRepository } from '@/licenses/domain/repositories/LicenseRepository' +import { EditTemplateLicenseTerms } from './EditTemplateLicenseTerms' +import { EditTemplateTermsOfAccess } from './EditTemplateTermsOfAccess' import styles from '../create-template/CreateTemplate.module.scss' +import termsStyles from './EditTemplateTerms.module.scss' import { EditTemplateTermsSkeleton } from './EditTemplateTermsSkeleton' interface EditTemplateTermsProps { collectionId: string templateId: number templateRepository: TemplateRepository + licenseRepository: LicenseRepository } export const EditTemplateTerms = ({ collectionId, templateId, - templateRepository + templateRepository, + licenseRepository }: EditTemplateTermsProps) => { const { t } = useTranslation('datasetTemplates') const { t: tDataset } = useTranslation('dataset') - const { t: tShared } = useTranslation('shared') const navigate = useNavigate() const location = useLocation() + const handleClose = () => + navigate(RouteWithParams.COLLECTION_TEMPLATES(collectionId), { + state: { fromEditTemplate: true } + }) + const { template, isLoadingTemplate, errorGetTemplate } = useGetTemplate({ templateRepository, templateId @@ -43,78 +47,54 @@ export const EditTemplateTerms = ({ } return ( - <> - - - - {collectionId} - - - {t('pageTitle')} - - - {tDataset('datasetActionButtons.editDataset.terms')} - - - - {tDataset('datasetActionButtons.editDataset.terms')} - - {showCreateSuccess && ( - - {t('createTemplate.alerts.success')} - - )} - {errorGetTemplate && ( - {tShared('errors.loadingDataErrorMessage')} - )} - {!isLoadingTemplate && template && ( - - - {tDataset('termsTab.licenseTitle')} - - - - - - - {tDataset('termsTab.termsTitle')} - - - - - - - )} - - - {tShared('saveChanges')} - - navigate(RouteWithParams.COLLECTION_TEMPLATES(collectionId))}> - {tShared('cancel')} - - - - > + + + + {collectionId} + + + {t('pageTitle')} + + {t('editTemplate.termsPageTitle')} + + + {t('editTemplate.termsPageTitle')} + + {showCreateSuccess && ( + + {t('createTemplate.alerts.success')} + + )} + {errorGetTemplate && {errorGetTemplate}} + {!isLoadingTemplate && template && ( + + + + + + + + + + + + + )} + ) } diff --git a/src/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.ts b/src/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.ts new file mode 100644 index 000000000..f6a24e156 --- /dev/null +++ b/src/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.ts @@ -0,0 +1,58 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' +import { updateTemplateLicenseTerms as updateTemplateLicenseTermsUseCase } from '@/templates/domain/useCases/updateTemplateLicenseTerms' +import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler' + +interface Options { + templateRepository: TemplateRepository + onSuccess: () => void +} + +interface ReturnType { + isLoading: boolean + error: string | null + handleUpdateLicenseTerms: ( + templateId: number, + payload: UpdateTemplateLicenseTermsInfo + ) => Promise +} + +export const useUpdateTemplateLicenseTerms = ({ + templateRepository, + onSuccess +}: Options): ReturnType => { + const { t } = useTranslation('datasetTemplates') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const handleUpdateLicenseTerms = async ( + templateId: number, + payload: UpdateTemplateLicenseTermsInfo + ): Promise => { + setIsLoading(true) + setError(null) + try { + await updateTemplateLicenseTermsUseCase(templateRepository, templateId, payload) + onSuccess() + return true + } catch (err) { + console.error('useUpdateTemplateLicenseTerms error:', err) + if (err instanceof WriteError) { + const handler = new JSDataverseWriteErrorHandler(err) + setError(handler.getReasonWithoutStatusCode() ?? handler.getErrorMessage()) + } else if (err instanceof Error && err.message) { + setError(err.message) + } else { + setError(t('editTemplate.errors.saveLicenseFailed')) + } + return false + } finally { + setIsLoading(false) + } + } + + return { isLoading, error, handleUpdateLicenseTerms } +} diff --git a/src/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.ts b/src/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.ts new file mode 100644 index 000000000..4e92ae862 --- /dev/null +++ b/src/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.ts @@ -0,0 +1,55 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' +import { updateTemplateTermsOfAccess as updateTemplateTermsOfAccessUseCase } from '@/templates/domain/useCases/updateTemplateTermsOfAccess' +import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler' + +interface Options { + templateRepository: TemplateRepository + onSuccess: () => void +} + +interface ReturnType { + isLoading: boolean + error: string | null + handleUpdateTermsOfAccess: (templateId: number, termsOfAccess: TermsOfAccess) => Promise +} + +export const useUpdateTemplateTermsOfAccess = ({ + templateRepository, + onSuccess +}: Options): ReturnType => { + const { t } = useTranslation('datasetTemplates') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const handleUpdateTermsOfAccess = async ( + templateId: number, + termsOfAccess: TermsOfAccess + ): Promise => { + setIsLoading(true) + setError(null) + try { + await updateTemplateTermsOfAccessUseCase(templateRepository, templateId, termsOfAccess) + onSuccess() + return true + } catch (err) { + console.error('useUpdateTemplateTermsOfAccess error:', err) + if (err instanceof WriteError) { + const handler = new JSDataverseWriteErrorHandler(err) + setError(handler.getReasonWithoutStatusCode() ?? handler.getErrorMessage()) + } else if (err instanceof Error && err.message) { + setError(err.message) + } else { + setError(t('editTemplate.errors.saveTermsOfAccessFailed')) + } + return false + } finally { + setIsLoading(false) + } + } + + return { isLoading, error, handleUpdateTermsOfAccess } +} diff --git a/src/sections/templates/edit-template/EditTemplateFactory.tsx b/src/sections/templates/edit-template/EditTemplateFactory.tsx new file mode 100644 index 000000000..f201d511b --- /dev/null +++ b/src/sections/templates/edit-template/EditTemplateFactory.tsx @@ -0,0 +1,58 @@ +import { ReactElement } from 'react' +import { useSearchParams, Navigate } from 'react-router-dom' +import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository' +import { MetadataBlockInfoJSDataverseRepository } from '@/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' +import { TemplateJSDataverseRepository } from '@/templates/infrastructure/repositories/TemplateJSDataverseRepository' +import { LicenseJSDataverseRepository } from '@/licenses/infrastructure/repositories/LicenseJSDataverseRepository' +import { QueryParamKey, Route, TemplateEditMode } from '@/sections/Route.enum' +import { EditTemplateMetadata } from '../edit-template-metadata/EditTemplateMetadata' +import { EditTemplateTerms } from '../edit-template-terms' + +const collectionRepository = new CollectionJSDataverseRepository() +const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() +const templateRepository = new TemplateJSDataverseRepository() +const licenseRepository = new LicenseJSDataverseRepository() + +export class EditTemplateFactory { + static create(): ReactElement { + return + } +} + +function EditTemplateDispatcher() { + const [searchParams] = useSearchParams() + const id = searchParams.get(QueryParamKey.ID) + const ownerId = searchParams.get(QueryParamKey.OWNER_ID) + const editMode = searchParams.get(QueryParamKey.EDIT_MODE) + + if (!id || !ownerId || !editMode) { + return + } + + const templateId = Number(id) + + if (editMode === TemplateEditMode.METADATA) { + return ( + + ) + } + + if (editMode === TemplateEditMode.LICENSE) { + return ( + + ) + } + + return +} diff --git a/src/sections/templates/useSetTemplateAsDefault.ts b/src/sections/templates/useSetTemplateAsDefault.ts new file mode 100644 index 000000000..c5dc3be04 --- /dev/null +++ b/src/sections/templates/useSetTemplateAsDefault.ts @@ -0,0 +1,74 @@ +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { toast } from 'react-toastify' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { setTemplateAsDefault } from '@/templates/domain/useCases/setTemplateAsDefault' +import { unsetTemplateAsDefault } from '@/templates/domain/useCases/unsetTemplateAsDefault' +import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler' + +interface UseSetTemplateAsDefaultProps { + collectionId: string + templateRepository: TemplateRepository +} + +interface UseSetTemplateAsDefaultResult { + handleSetTemplateAsDefault: (templateId: number) => Promise + handleUnsetTemplateAsDefault: () => Promise + isSettingDefault: boolean +} + +export const useSetTemplateAsDefault = ({ + collectionId, + templateRepository +}: UseSetTemplateAsDefaultProps): UseSetTemplateAsDefaultResult => { + const { t } = useTranslation('datasetTemplates') + const [isSettingDefault, setIsSettingDefault] = useState(false) + + const handleSetTemplateAsDefault = useCallback( + async (templateId: number) => { + setIsSettingDefault(true) + + try { + await setTemplateAsDefault(templateRepository, templateId, collectionId) + toast.success(t('alerts.setDefaultSuccess')) + return true + } catch (error) { + if (error instanceof WriteError) { + const handler = new JSDataverseWriteErrorHandler(error) + const reason = handler.getReasonWithoutStatusCode() ?? handler.getErrorMessage() + toast.error(reason) + } else { + toast.error(t('alerts.setDefaultError')) + } + return false + } finally { + setIsSettingDefault(false) + } + }, + [collectionId, t, templateRepository] + ) + + const handleUnsetTemplateAsDefault = useCallback(async () => { + setIsSettingDefault(true) + + try { + await unsetTemplateAsDefault(templateRepository, collectionId) + toast.success(t('alerts.unsetDefaultSuccess')) + return true + } catch (error) { + if (error instanceof WriteError) { + const handler = new JSDataverseWriteErrorHandler(error) + const reason = handler.getReasonWithoutStatusCode() ?? handler.getErrorMessage() + toast.error(reason) + } else { + toast.error(t('alerts.unsetDefaultError')) + } + return false + } finally { + setIsSettingDefault(false) + } + }, [collectionId, t, templateRepository]) + + return { handleSetTemplateAsDefault, handleUnsetTemplateAsDefault, isSettingDefault } +} diff --git a/src/stories/templates/TemplateMockRepository.ts b/src/stories/templates/TemplateMockRepository.ts index f0b2047d4..a9a6759bb 100644 --- a/src/stories/templates/TemplateMockRepository.ts +++ b/src/stories/templates/TemplateMockRepository.ts @@ -1,6 +1,9 @@ import { TemplateInfo } from '@/templates/domain/models/TemplateInfo' +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' import { Template } from '@/templates/domain/models/Template' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' import { TemplateMother } from '@tests/component/sections/templates/TemplateMother' import { FakerHelper } from '@tests/component/shared/FakerHelper' @@ -36,4 +39,51 @@ export class TemplateMockRepository implements TemplateRepository { }, FakerHelper.loadingTimout()) }) } + + setTemplateAsDefault(_templateId: number, _collectionIdOrAlias: number | string): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } + + unsetTemplateAsDefault(_collectionIdOrAlias: number | string): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } + + updateTemplateMetadata( + _templateId: number, + _payload: UpdateTemplateMetadataInfo, + _replace?: boolean + ): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } + + updateTemplateLicenseTerms( + _templateId: number, + _payload: UpdateTemplateLicenseTermsInfo + ): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } + + updateTemplateTermsOfAccess(_templateId: number, _termsOfAccess: TermsOfAccess): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, FakerHelper.loadingTimout()) + }) + } } diff --git a/src/templates/domain/hooks/useGetTemplatesByCollectionId.ts b/src/templates/domain/hooks/useGetTemplatesByCollectionId.ts index c7a3eb1c0..4af052916 100644 --- a/src/templates/domain/hooks/useGetTemplatesByCollectionId.ts +++ b/src/templates/domain/hooks/useGetTemplatesByCollectionId.ts @@ -53,10 +53,20 @@ export const useGetTemplatesByCollectionId = ({ void fetchDatasetTemplates() }, [fetchDatasetTemplates]) + const toggleDefaultTemplate = (templateId: number | null) => { + setDatasetTemplates((prev) => + prev.map((t) => ({ + ...t, + isDefault: t.id === templateId + })) + ) + } + return { datasetTemplates, isLoadingDatasetTemplates, errorGetDatasetTemplates, - fetchDatasetTemplates + fetchDatasetTemplates, + toggleDefaultTemplate } } diff --git a/src/templates/domain/models/UpdateTemplateLicenseTermsInfo.ts b/src/templates/domain/models/UpdateTemplateLicenseTermsInfo.ts new file mode 100644 index 000000000..cd7817ee8 --- /dev/null +++ b/src/templates/domain/models/UpdateTemplateLicenseTermsInfo.ts @@ -0,0 +1,6 @@ +import { CustomTerms } from '@/dataset/domain/models/Dataset' + +export interface UpdateTemplateLicenseTermsInfo { + name?: string + customTerms?: CustomTerms +} diff --git a/src/templates/domain/models/UpdateTemplateMetadataInfo.ts b/src/templates/domain/models/UpdateTemplateMetadataInfo.ts new file mode 100644 index 000000000..bba8ca56a --- /dev/null +++ b/src/templates/domain/models/UpdateTemplateMetadataInfo.ts @@ -0,0 +1,7 @@ +import { TemplateFieldInfo, TemplateInstructionInfo } from './TemplateInfo' + +export interface UpdateTemplateMetadataInfo { + name?: string + fields?: TemplateFieldInfo[] + instructions?: TemplateInstructionInfo[] +} diff --git a/src/templates/domain/repositories/TemplateRepository.ts b/src/templates/domain/repositories/TemplateRepository.ts index 9e653c281..cc19e5e1d 100644 --- a/src/templates/domain/repositories/TemplateRepository.ts +++ b/src/templates/domain/repositories/TemplateRepository.ts @@ -1,9 +1,24 @@ import { Template } from '@/templates/domain/models/Template' import { TemplateInfo } from '@/templates/domain/models/TemplateInfo' +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' export interface TemplateRepository { createTemplate: (template: TemplateInfo, collectionIdOrAlias: number | string) => Promise getTemplate: (templateId: number) => Promise getTemplatesByCollectionId: (collectionIdOrAlias: number | string) => Promise deleteTemplate: (templateId: number) => Promise + setTemplateAsDefault: (templateId: number, collectionIdOrAlias: number | string) => Promise + unsetTemplateAsDefault: (collectionIdOrAlias: number | string) => Promise + updateTemplateMetadata: ( + templateId: number, + payload: UpdateTemplateMetadataInfo, + replace?: boolean + ) => Promise + updateTemplateLicenseTerms: ( + templateId: number, + payload: UpdateTemplateLicenseTermsInfo + ) => Promise + updateTemplateTermsOfAccess: (templateId: number, termsOfAccess: TermsOfAccess) => Promise } diff --git a/src/templates/domain/useCases/setTemplateAsDefault.ts b/src/templates/domain/useCases/setTemplateAsDefault.ts new file mode 100644 index 000000000..bef69ecb1 --- /dev/null +++ b/src/templates/domain/useCases/setTemplateAsDefault.ts @@ -0,0 +1,9 @@ +import { TemplateRepository } from '../repositories/TemplateRepository' + +export function setTemplateAsDefault( + templateRepository: TemplateRepository, + templateId: number, + collectionIdOrAlias: number | string +): Promise { + return templateRepository.setTemplateAsDefault(templateId, collectionIdOrAlias) +} diff --git a/src/templates/domain/useCases/unsetTemplateAsDefault.ts b/src/templates/domain/useCases/unsetTemplateAsDefault.ts new file mode 100644 index 000000000..45decf4de --- /dev/null +++ b/src/templates/domain/useCases/unsetTemplateAsDefault.ts @@ -0,0 +1,8 @@ +import { TemplateRepository } from '../repositories/TemplateRepository' + +export function unsetTemplateAsDefault( + templateRepository: TemplateRepository, + collectionIdOrAlias: number | string +): Promise { + return templateRepository.unsetTemplateAsDefault(collectionIdOrAlias) +} diff --git a/src/templates/domain/useCases/updateTemplateLicenseTerms.ts b/src/templates/domain/useCases/updateTemplateLicenseTerms.ts new file mode 100644 index 000000000..34e7c4de1 --- /dev/null +++ b/src/templates/domain/useCases/updateTemplateLicenseTerms.ts @@ -0,0 +1,10 @@ +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' +import { TemplateRepository } from '../repositories/TemplateRepository' + +export function updateTemplateLicenseTerms( + templateRepository: TemplateRepository, + templateId: number, + payload: UpdateTemplateLicenseTermsInfo +): Promise { + return templateRepository.updateTemplateLicenseTerms(templateId, payload) +} diff --git a/src/templates/domain/useCases/updateTemplateMetadata.ts b/src/templates/domain/useCases/updateTemplateMetadata.ts new file mode 100644 index 000000000..96c84389c --- /dev/null +++ b/src/templates/domain/useCases/updateTemplateMetadata.ts @@ -0,0 +1,11 @@ +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' +import { TemplateRepository } from '../repositories/TemplateRepository' + +export function updateTemplateMetadata( + templateRepository: TemplateRepository, + templateId: number, + payload: UpdateTemplateMetadataInfo, + replace = true +): Promise { + return templateRepository.updateTemplateMetadata(templateId, payload, replace) +} diff --git a/src/templates/domain/useCases/updateTemplateTermsOfAccess.ts b/src/templates/domain/useCases/updateTemplateTermsOfAccess.ts new file mode 100644 index 000000000..5729314e9 --- /dev/null +++ b/src/templates/domain/useCases/updateTemplateTermsOfAccess.ts @@ -0,0 +1,10 @@ +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' +import { TemplateRepository } from '../repositories/TemplateRepository' + +export function updateTemplateTermsOfAccess( + templateRepository: TemplateRepository, + templateId: number, + termsOfAccess: TermsOfAccess +): Promise { + return templateRepository.updateTemplateTermsOfAccess(templateId, termsOfAccess) +} diff --git a/src/templates/infrastructure/repositories/TemplateJSDataverseRepository.ts b/src/templates/infrastructure/repositories/TemplateJSDataverseRepository.ts index de86ea299..39da20b4d 100644 --- a/src/templates/infrastructure/repositories/TemplateJSDataverseRepository.ts +++ b/src/templates/infrastructure/repositories/TemplateJSDataverseRepository.ts @@ -2,10 +2,18 @@ import { createTemplate, deleteTemplate, getTemplate, - getTemplatesByCollectionId + getTemplatesByCollectionId, + setTemplateAsDefault, + unsetTemplateAsDefault, + updateTemplateMetadata, + updateTemplateLicenseTerms, + updateTemplateTermsOfAccess } from '@iqss/dataverse-client-javascript' import { Template } from '@/templates/domain/models/Template' import { TemplateInfo } from '@/templates/domain/models/TemplateInfo' +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' import { TemplateRepository } from '../../domain/repositories/TemplateRepository' export class TemplateJSDataverseRepository implements TemplateRepository { @@ -27,4 +35,35 @@ export class TemplateJSDataverseRepository implements TemplateRepository { deleteTemplate(templateId: number): Promise { return deleteTemplate.execute(templateId) } + + setTemplateAsDefault(templateId: number, collectionIdOrAlias: number | string): Promise { + return setTemplateAsDefault.execute(templateId, collectionIdOrAlias) + } + + unsetTemplateAsDefault(collectionIdOrAlias: number | string): Promise { + return unsetTemplateAsDefault.execute(collectionIdOrAlias) + } + + updateTemplateMetadata( + templateId: number, + payload: UpdateTemplateMetadataInfo, + replace = true + ): Promise { + return updateTemplateMetadata.execute( + templateId, + payload as Parameters[1], + replace + ) + } + + updateTemplateLicenseTerms( + templateId: number, + payload: UpdateTemplateLicenseTermsInfo + ): Promise { + return updateTemplateLicenseTerms.execute(templateId, payload) + } + + updateTemplateTermsOfAccess(templateId: number, termsOfAccess: TermsOfAccess): Promise { + return updateTemplateTermsOfAccess.execute(templateId, termsOfAccess) + } } diff --git a/tests/component/sections/dataset/dataset-action-buttons/link-and-unlink-actions/LinkAndUnlinkActions.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/link-and-unlink-actions/LinkAndUnlinkActions.spec.tsx new file mode 100644 index 000000000..1bff138b6 --- /dev/null +++ b/tests/component/sections/dataset/dataset-action-buttons/link-and-unlink-actions/LinkAndUnlinkActions.spec.tsx @@ -0,0 +1,103 @@ +import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' +import { LinkAndUnlinkActions } from '@/sections/dataset/dataset-action-buttons/link-and-unlink-actions/LinkAndUnlinkActions' +import { CollectionSummaryMother } from '@tests/component/collection/domain/models/CollectionSummaryMother' +import { + DatasetMother, + DatasetVersionMother +} from '@tests/component/dataset/domain/models/DatasetMother' +import { WithRepositories } from '@tests/component/WithRepositories' + +const collectionRepository: CollectionRepository = {} as CollectionRepository +const datasetRepository: DatasetRepository = {} as DatasetRepository + +const dataset = DatasetMother.create({ + version: DatasetVersionMother.createReleased() +}) + +const linkedCollection = CollectionSummaryMother.create({ + id: 1, + displayName: 'Collection 1', + alias: 'collection-1' +}) + +const linkableCollection = CollectionSummaryMother.create({ + id: 3, + displayName: 'Collection 3', + alias: 'collection-3' +}) + +const mountAuthenticatedLinkAndUnlinkActions = () => + cy.mountAuthenticated( + + + + ) + +describe('LinkAndUnlinkActions', () => { + beforeEach(() => { + cy.viewport('macbook-15') + datasetRepository.link = cy.stub().as('linkDataset').resolves() + datasetRepository.unlink = cy.stub().as('unlinkDataset').resolves() + }) + + it('remounts the actions after linking a dataset successfully', () => { + datasetRepository.getDatasetLinkedCollections = cy + .stub() + .as('getDatasetLinkedCollections') + .onCall(0) + .resolves([]) + .onCall(1) + .resolves([]) + .onCall(2) + .resolves([linkedCollection]) + collectionRepository.getForLinking = cy.stub().resolves([linkableCollection]) + collectionRepository.getForUnlinking = cy.stub().resolves([linkedCollection]) + + mountAuthenticatedLinkAndUnlinkActions() + + cy.findByRole('button', { name: 'Unlink Dataset' }).should('not.exist') + + cy.findByRole('button', { name: 'Link Dataset' }).click() + + cy.findByRole('dialog') + .should('be.visible') + .within(() => { + cy.findByRole('textbox', { name: 'Your Collection' }).should('have.value', 'Collection 3') + }) + + cy.findByTestId('confirm-link-dataset-button').click() + + cy.get('@linkDataset').should('have.been.calledOnce') + cy.get('@getDatasetLinkedCollections').should('have.callCount', 3) + cy.findByRole('button', { name: 'Unlink Dataset' }).should('exist') + }) + + it('remounts the actions after unlinking a dataset successfully', () => { + datasetRepository.getDatasetLinkedCollections = cy + .stub() + .as('getDatasetLinkedCollections') + .onCall(0) + .resolves([linkedCollection]) + .onCall(1) + .resolves([]) + collectionRepository.getForLinking = cy.stub().resolves([linkableCollection]) + collectionRepository.getForUnlinking = cy.stub().resolves([linkedCollection]) + + mountAuthenticatedLinkAndUnlinkActions() + + cy.findByRole('button', { name: 'Unlink Dataset' }).should('exist').click() + + cy.findByRole('dialog') + .should('be.visible') + .within(() => { + cy.findByRole('textbox', { name: 'Your Collection' }).should('have.value', 'Collection 1') + }) + + cy.findByTestId('confirm-unlink-dataset-button').click() + + cy.get('@unlinkDataset').should('have.been.calledOnce') + cy.get('@getDatasetLinkedCollections').should('have.callCount', 2) + cy.findByRole('button', { name: 'Unlink Dataset' }).should('not.exist') + }) +}) diff --git a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx index f0a4a69d2..fd674a713 100644 --- a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react' +import { useLocation } from 'react-router-dom' import { EditTermsOfAccess } from '@/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess' import { DatasetProvider } from '@/sections/dataset/DatasetProvider' import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' @@ -10,16 +11,19 @@ import { TermsOfAccessMother, TermsOfUseMother } from '@tests/component/dataset/domain/models/TermsOfUseMother' -import { Dataset } from '@/dataset/domain/models/Dataset' -import { useLocation } from 'react-router-dom' +import { + Dataset, + DatasetPublishingStatus, + DatasetVersionNumber +} from '@/dataset/domain/models/Dataset' + +const datasetRepository: DatasetRepository = {} as DatasetRepository const LocationDisplay = () => { const location = useLocation() return {`${location.pathname}${location.search}`} } -const datasetRepository: DatasetRepository = {} as DatasetRepository - const mockDataset = DatasetMother.create({ id: 123, termsOfUse: TermsOfUseMother.withoutCustomTerms({ @@ -185,86 +189,23 @@ describe('EditTermsOfAccess', () => { cy.findByLabelText('Terms of Access for Restricted Files').should('exist') }) - it('treats undefined fileAccessRequest as enabled by default', () => { - const datasetWithUndefinedRequest = DatasetMother.create({ - termsOfUse: TermsOfUseMother.withoutCustomTerms({ - termsOfAccess: TermsOfAccessMother.create({ - fileAccessRequest: undefined as unknown as boolean, - termsOfAccessForRestrictedFiles: undefined - }) - }) - }) - - cy.customMount( - withProviders( - , - datasetWithUndefinedRequest - ) - ) - - cy.findByRole('button', { name: 'Save Changes' }).should('be.enabled') - }) - - it('reports dirty state changes via onFormStateChange', () => { - const onFormStateChange = cy.stub().as('onFormStateChange') - - cy.customMount( - withProviders( - , - mockDataset - ) - ) - - cy.findByLabelText('Terms of Access for Restricted Files').clear().type('Updated terms') - - cy.wrap(onFormStateChange).should('have.been.calledWith', true) - }) - - it('shows a saving state while update is in progress', () => { - let resolveUpdate: () => void - datasetRepository.updateTermsOfAccess = cy.stub().callsFake( - () => - new Cypress.Promise((resolve) => { - resolveUpdate = resolve - }) - ) - - cy.customMount( - withProviders(, mockDataset) - ) - - cy.findByRole('button', { name: 'Save Changes' }).click() - - cy.findByRole('button', { name: 'Saving' }).should('be.disabled') - - cy.wrap(null).then(() => resolveUpdate()) - - cy.findByText('The terms for this dataset have been updated.').should('exist') - }) - - describe('Cancel navigation', () => { - it('returns early and does not navigate when dataset is not loaded yet', () => { + describe('Cancel', () => { + it('does nothing when dataset is not loaded', () => { cy.customMount( - withLoadingDataset( - <> - - - > - ), - ['/edit-terms'] + <> + + + > ) - cy.findByTestId('location-display').should('contain', '/edit-terms') + cy.findByTestId('location-display').should('have.text', '/') cy.findByRole('button', { name: 'Cancel' }).click() - cy.findByTestId('location-display').should('contain', '/edit-terms') + cy.findByTestId('location-display').should('have.text', '/') }) - it('navigates to dataset page with DRAFT version query param when dataset is draft', () => { - const draftDataset = DatasetMother.create({ - persistentId: 'doi:10.5072/FK2/DRAFTPID', + it('navigates to the dataset page with DRAFT version param when publishingStatus is draft', () => { + const dataset = DatasetMother.create({ + persistentId: 'pid-123', version: DatasetVersionMother.createDraft() }) @@ -274,21 +215,25 @@ describe('EditTermsOfAccess', () => { >, - draftDataset + dataset ) ) cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByTestId('location-display').should( 'have.text', - '/datasets?persistentId=doi%3A10.5072%2FFK2%2FDRAFTPID&version=DRAFT' + '/datasets?persistentId=pid-123&version=DRAFT' ) }) - it('navigates to dataset page with numeric version query param when dataset is released', () => { - const releasedDataset = DatasetMother.create({ - persistentId: 'doi:10.5072/FK2/RELEASEDPID', - version: DatasetVersionMother.createReleased() + it('navigates to the dataset page with numeric version param when publishingStatus is not draft', () => { + const dataset = DatasetMother.create({ + persistentId: 'pid-999', + version: DatasetVersionMother.create({ + publishingStatus: DatasetPublishingStatus.RELEASED, + number: new DatasetVersionNumber(2, 7) + }) }) cy.customMount( @@ -297,71 +242,63 @@ describe('EditTermsOfAccess', () => { >, - releasedDataset + dataset ) ) cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByTestId('location-display').should( 'have.text', - '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=1.0' + '/datasets?persistentId=pid-999&version=2.7' ) }) - }) - - describe('Toast Notifications', () => { - it('displays success toast when terms of access are updated successfully', () => { - const termsOfAccess = TermsOfAccessMother.create({ - fileAccessRequest: false + it('treats undefined fileAccessRequest as enabled by default', () => { + const datasetWithUndefinedRequest = DatasetMother.create({ + termsOfUse: TermsOfUseMother.withoutCustomTerms({ + termsOfAccess: TermsOfAccessMother.create({ + fileAccessRequest: undefined as unknown as boolean, + termsOfAccessForRestrictedFiles: undefined + }) + }) }) - datasetRepository.updateTermsOfAccess = cy.stub().resolves() cy.customMount( withProviders( , - DatasetMother.create({ - id: 123, - termsOfUse: { termsOfAccess } - }) + datasetWithUndefinedRequest ) ) - cy.findByLabelText('Enable access request').check() - cy.findByLabelText('Terms of Access for Restricted Files').type('Please contact for access') - - cy.findByRole('button', { name: 'Save Changes' }).click() - - cy.findByText('The terms for this dataset have been updated.').should('exist') + cy.findByRole('button', { name: 'Save Changes' }).should('be.enabled') }) - it('displays success toast when request access checkbox is toggled', () => { - const termsOfAccess = TermsOfAccessMother.create({ - fileAccessRequest: true - }) - datasetRepository.updateTermsOfAccess = cy.stub().resolves() + it('reports dirty state changes via onFormStateChange', () => { + const onFormStateChange = cy.stub().as('onFormStateChange') cy.customMount( withProviders( - , - DatasetMother.create({ - id: 123, - termsOfUse: { termsOfAccess } - }) + , + mockDataset ) ) - cy.findByLabelText('Enable access request').should('be.checked') - cy.findByLabelText('Enable access request').uncheck() + cy.findByLabelText('Terms of Access for Restricted Files').clear().type('Updated terms') - cy.findByRole('button', { name: 'Save Changes' }).click() - - cy.findByText('The terms for this dataset have been updated.').should('exist') + cy.wrap(onFormStateChange).should('have.been.calledWith', true) }) - }) - describe('Error Handling', () => { - it('displays error when updating terms of access fails', () => { - datasetRepository.updateTermsOfAccess = cy.stub().rejects(new Error('Update failed')) + it('shows a saving state while update is in progress', () => { + let resolveUpdate: () => void + datasetRepository.updateTermsOfAccess = cy.stub().callsFake( + () => + new Cypress.Promise((resolve) => { + resolveUpdate = resolve + }) + ) cy.customMount( withProviders(, mockDataset) @@ -369,9 +306,141 @@ describe('EditTermsOfAccess', () => { cy.findByRole('button', { name: 'Save Changes' }).click() - cy.findByText( - /An error occurred while updating the dataset terms of access. Please try again./i - ).should('exist') + cy.findByRole('button', { name: 'Saving' }).should('be.disabled') + + cy.wrap(null).then(() => resolveUpdate()) + + cy.findByText('The terms for this dataset have been updated.').should('exist') + }) + + describe('Cancel navigation', () => { + it('returns early and does not navigate when dataset is not loaded yet', () => { + cy.customMount( + withLoadingDataset( + <> + + + > + ), + ['/edit-terms'] + ) + + cy.findByTestId('location-display').should('contain', '/edit-terms') + cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByTestId('location-display').should('contain', '/edit-terms') + }) + + it('navigates to dataset page with DRAFT version query param when dataset is draft', () => { + const draftDataset = DatasetMother.create({ + persistentId: 'doi:10.5072/FK2/DRAFTPID', + version: DatasetVersionMother.createDraft() + }) + + cy.customMount( + withProviders( + <> + + + >, + draftDataset + ) + ) + + cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FDRAFTPID&version=DRAFT' + ) + }) + + it('navigates to dataset page with numeric version query param when dataset is released', () => { + const releasedDataset = DatasetMother.create({ + persistentId: 'doi:10.5072/FK2/RELEASEDPID', + version: DatasetVersionMother.createReleased() + }) + + cy.customMount( + withProviders( + <> + + + >, + releasedDataset + ) + ) + + cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=1.0' + ) + }) + }) + + describe('Toast Notifications', () => { + it('displays success toast when terms of access are updated successfully', () => { + const termsOfAccess = TermsOfAccessMother.create({ + fileAccessRequest: false + }) + datasetRepository.updateTermsOfAccess = cy.stub().resolves() + + cy.customMount( + withProviders( + , + DatasetMother.create({ + id: 123, + termsOfUse: { termsOfAccess } + }) + ) + ) + + cy.findByLabelText('Enable access request').check() + cy.findByLabelText('Terms of Access for Restricted Files').type('Please contact for access') + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findByText('The terms for this dataset have been updated.').should('exist') + }) + + it('displays success toast when request access checkbox is toggled', () => { + const termsOfAccess = TermsOfAccessMother.create({ + fileAccessRequest: true + }) + datasetRepository.updateTermsOfAccess = cy.stub().resolves() + + cy.customMount( + withProviders( + , + DatasetMother.create({ + id: 123, + termsOfUse: { termsOfAccess } + }) + ) + ) + + cy.findByLabelText('Enable access request').should('be.checked') + cy.findByLabelText('Enable access request').uncheck() + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findByText('The terms for this dataset have been updated.').should('exist') + }) + }) + + describe('Error Handling', () => { + it('displays error when updating terms of access fails', () => { + datasetRepository.updateTermsOfAccess = cy.stub().rejects(new Error('Update failed')) + + cy.customMount( + withProviders(, mockDataset) + ) + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findByText( + /An error occurred while updating the dataset terms of access. Please try again./i + ).should('exist') + }) }) }) }) diff --git a/tests/component/sections/shared/template-metadata-form/TemplateMetadataForm.spec.tsx b/tests/component/sections/shared/template-metadata-form/TemplateMetadataForm.spec.tsx index ff005d060..8cf49d592 100644 --- a/tests/component/sections/shared/template-metadata-form/TemplateMetadataForm.spec.tsx +++ b/tests/component/sections/shared/template-metadata-form/TemplateMetadataForm.spec.tsx @@ -1,10 +1,11 @@ -import { createTemplate } from '@iqss/dataverse-client-javascript' import { useLocation } from 'react-router-dom' import { TemplateMetadataForm } from '@/sections/shared/form/TemplateMetadataForm/TemplateMetadataForm' import { MetadataBlockInfoRepository } from '@/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' import { TypeMetadataFieldOptions } from '@/metadata-block-info/domain/models/MetadataBlockInfo' +import { RouteWithParams, TemplateEditMode } from '@/sections/Route.enum' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { TemplateMother } from '../../templates/TemplateMother' const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository const templateRepository: TemplateRepository = {} as TemplateRepository @@ -13,7 +14,13 @@ const metadataBlocksInfo = MetadataBlockInfoMother.getAllBlocks() describe('TemplateMetadataForm', () => { const LocationDisplay = () => { const location = useLocation() - return {location.pathname} + return ( + + {location.pathname} + {location.search} + {JSON.stringify(location.state)} + + ) } beforeEach(() => { @@ -25,12 +32,29 @@ describe('TemplateMetadataForm', () => { cy.customMount( <> - > + >, + [RouteWithParams.TEMPLATES_CREATE('root')] + ) + + const mountEditTemplateMetadataForm = (template = TemplateMother.create({ id: 7 })) => + cy.customMount( + <> + + + >, + [RouteWithParams.TEMPLATES_EDIT('root', template.id, TemplateEditMode.METADATA)] ) const fillRequiredTemplateFields = () => { @@ -125,7 +149,7 @@ describe('TemplateMetadataForm', () => { }) it('should not enforce required validation when creating a template', () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + templateRepository.createTemplate = cy.stub().resolves() mountTemplateMetadataForm() @@ -137,8 +161,6 @@ describe('TemplateMetadataForm', () => { cy.findByText('Point of Contact E-mail is required').should('not.exist') cy.findByText('Description Text is required').should('not.exist') cy.findByText('Subject is required').should('not.exist') - - executeStub.restore() }) it('should show correct errors when filling inputs with invalid formats', () => { @@ -172,7 +194,8 @@ describe('TemplateMetadataForm', () => { }) it('should save custom instructions for template fields', () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + const createStub = cy.stub().resolves() + templateRepository.createTemplate = createStub mountTemplateMetadataForm() @@ -190,7 +213,7 @@ describe('TemplateMetadataForm', () => { cy.findByTestId('custom-instructions-toggle-authorName').should('not.exist') cy.findByRole('button', { name: 'Save + Add Terms' }).click() - cy.wrap(executeStub).should('have.been.calledOnce') + cy.wrap(createStub).should('have.been.calledOnce') }) it('should discard custom instructions when canceling edit', () => { @@ -219,7 +242,8 @@ describe('TemplateMetadataForm', () => { }) it('removes cleared custom instructions from the submit payload', () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + const createStub = cy.stub().resolves() + templateRepository.createTemplate = createStub mountTemplateMetadataForm() @@ -235,15 +259,18 @@ describe('TemplateMetadataForm', () => { cy.findByRole('button', { name: 'Save + Add Terms' }).click() - cy.wrap(executeStub).should('have.been.calledOnce') - cy.wrap(executeStub).then((stub) => { - const payload = stub.getCall(0).args[0] as { instructions?: unknown[] } + cy.wrap(createStub).should('have.been.calledOnce') + cy.wrap(createStub).then((stub) => { + const payload = (stub as unknown as sinon.SinonStub).getCall(0).args[0] as { + instructions?: unknown[] + } expect(payload.instructions).to.equal(undefined) }) }) it('removes only the cleared instruction when multiple exist', () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + const createStub = cy.stub().resolves() + templateRepository.createTemplate = createStub mountTemplateMetadataForm() @@ -263,9 +290,11 @@ describe('TemplateMetadataForm', () => { cy.findByRole('button', { name: 'Save + Add Terms' }).click() - cy.wrap(executeStub).should('have.been.calledOnce') - cy.wrap(executeStub).then((stub) => { - const payload = stub.getCall(0).args[0] as { instructions?: unknown[] } + cy.wrap(createStub).should('have.been.calledOnce') + cy.wrap(createStub).then((stub) => { + const payload = (stub as unknown as sinon.SinonStub).getCall(0).args[0] as { + instructions?: { instructionField: string }[] + } expect(payload.instructions).to.have.length(1) expect(payload.instructions?.[0]).to.have.property('instructionField', 'author') }) @@ -280,4 +309,99 @@ describe('TemplateMetadataForm', () => { cy.findByLabelText(/Template Name/).type('Valid Template Name') cy.findByText('Please add in a name for the dataset template.').should('not.exist') }) + + it('pre-fills existing custom instructions in edit mode', () => { + const template = TemplateMother.create({ + id: 7, + name: 'Existing Template', + instructions: [ + { + instructionField: 'title', + instructionText: 'Use the official dataset title' + }, + { + instructionField: 'author', + instructionText: 'List all contributing authors' + } + ] + }) + + mountEditTemplateMetadataForm(template) + + cy.findByTestId('custom-instructions-toggle-title') + .should('contain.text', 'Use the official dataset title') + .click() + cy.findByTestId('custom-instructions-input-title').should( + 'have.value', + 'Use the official dataset title' + ) + + cy.findByTestId('custom-instructions-toggle-author') + .should('contain.text', 'List all contributing authors') + .click() + cy.findByTestId('custom-instructions-input-author').should( + 'have.value', + 'List all contributing authors' + ) + }) + + it('navigates to edit template terms after creating the template', () => { + templateRepository.createTemplate = cy.stub().resolves() + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([ + TemplateMother.create({ id: 99, name: 'Different Template' }), + TemplateMother.create({ id: 42, name: ' test template ' }) + ]) + + mountTemplateMetadataForm() + + cy.findByLabelText(/Template Name/).type(' Test Template ') + cy.findByRole('button', { name: 'Save + Add Terms' }).click() + + cy.findByTestId('location-display').should('have.text', '/templates/edit') + cy.findByTestId('location-search-display').should( + 'have.text', + `?id=42&ownerId=root&editMode=${TemplateEditMode.LICENSE}` + ) + cy.findByTestId('location-state-display').should( + 'have.text', + JSON.stringify({ fromCreateTemplate: true }) + ) + }) + + it('does not navigate to edit terms when the created template cannot be resolved after submit', () => { + templateRepository.createTemplate = cy.stub().resolves() + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([TemplateMother.create({ id: 99, name: 'Different Template' })]) + + mountTemplateMetadataForm() + + cy.findByLabelText(/Template Name/).type('Test Template') + cy.findByRole('button', { name: 'Save + Add Terms' }).click() + + cy.findByTestId('location-display').should( + 'have.text', + RouteWithParams.TEMPLATES_CREATE('root') + ) + cy.findByTestId('location-search-display').should('have.text', '') + }) + + it('does not refresh templates or navigate when create submit fails', () => { + templateRepository.getTemplatesByCollectionId = cy.stub().resolves([]) + templateRepository.createTemplate = cy.stub().rejects(new Error('Failed to save')) + + mountTemplateMetadataForm() + + cy.findByLabelText(/Template Name/).type('Test Template') + cy.findByRole('button', { name: 'Save + Add Terms' }).click() + + cy.wrap(templateRepository.getTemplatesByCollectionId).should('have.been.calledOnce') + cy.findByTestId('location-display').should( + 'have.text', + RouteWithParams.TEMPLATES_CREATE('root') + ) + cy.findByRole('alert').should('contain.text', 'Failed to save') + }) }) diff --git a/tests/component/sections/templates/DatasetTemplates.spec.tsx b/tests/component/sections/templates/DatasetTemplates.spec.tsx index 47b9befbd..71f4c059c 100644 --- a/tests/component/sections/templates/DatasetTemplates.spec.tsx +++ b/tests/component/sections/templates/DatasetTemplates.spec.tsx @@ -2,6 +2,7 @@ import { DatasetTemplates } from '../../../../src/sections/templates/DatasetTemp import { CollectionRepository } from '../../../../src/collection/domain/repositories/CollectionRepository' import { TemplateRepository } from '../../../../src/templates/domain/repositories/TemplateRepository' import { MetadataBlockInfoRepository } from '../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { TemplateEditMode } from '../../../../src/sections/Route.enum' import { ReadError, WriteError } from '@iqss/dataverse-client-javascript' import { CollectionMother } from '../../collection/domain/models/CollectionMother' import { TemplateMother } from './TemplateMother' @@ -9,6 +10,8 @@ import { NotImplementedModalProvider } from '../../../../src/sections/not-implem import { MetadataBlockInfoMother } from '../../metadata-block-info/domain/models/MetadataBlockInfoMother' import { CitationMetadataBlockInfoMother } from '../../metadata-block-info/domain/models/CitationMetadataBlockInfoMother' import { UpwardHierarchyNodeMother } from '../../shared/hierarchy/domain/models/UpwardHierarchyNodeMother' +import { useLocation } from 'react-router-dom' +import { RouterInitialEntry } from '../../../support/commands' const collectionRepository: CollectionRepository = {} as CollectionRepository const templateRepository: TemplateRepository = {} as TemplateRepository @@ -35,6 +38,17 @@ const template = TemplateMother.create({ isDefault: false }) describe('Dataset Templates', () => { + const LocationDisplay = () => { + const location = useLocation() + return ( + <> + {location.pathname} + {location.search} + {JSON.stringify(location.state)} + > + ) + } + beforeEach(() => { collectionRepository.getById = cy.stub().resolves(collection) collectionRepository.getUserPermissions = cy.stub().resolves({ @@ -49,16 +63,20 @@ describe('Dataset Templates', () => { templateRepository.getTemplatesByCollectionId = cy.stub().resolves([]) }) - const mountDatasetTemplates = () => + const mountDatasetTemplates = (initialEntries: RouterInitialEntry[] = ['/root/templates']) => cy.customMount( - - - + <> + + + + + >, + initialEntries ) it('shows not found when the collection does not exist', () => { @@ -117,6 +135,30 @@ describe('Dataset Templates', () => { }) }) + it('shows the edit success toast and clears location state after returning from edit', () => { + templateRepository.getTemplatesByCollectionId = cy.stub().resolves([template]) + + mountDatasetTemplates([ + { + pathname: '/root/templates', + state: { fromEditTemplate: true } + } + ]) + + cy.findByRole('alert').should('contain.text', 'Template updated.') + cy.findByTestId('location-display').should('have.text', '/root/templates') + cy.findByTestId('location-state-display').should('have.text', 'null') + }) + + it('navigates to create template when clicking the create button', () => { + templateRepository.getTemplatesByCollectionId = cy.stub().resolves([template]) + + mountDatasetTemplates() + + cy.findByRole('button', { name: 'Create Dataset Template' }).click() + cy.findByTestId('location-display').should('have.text', '/create') + }) + it('shows Default as disabled and hides Make Default for the default template', () => { const [templateDefault, templateOther] = TemplateMother.createTemplates([ { id: 1, name: 'Template Default', isDefault: true }, @@ -136,6 +178,148 @@ describe('Dataset Templates', () => { }) }) + describe('Set/Unset Default Template', () => { + it('toggles a non-default template to default and updates buttons without refetching', () => { + const [templateDefault, templateOther] = TemplateMother.createTemplates([ + { id: 1, name: 'Template Default', isDefault: true }, + { id: 2, name: 'Template Other', isDefault: false } + ]) + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([templateDefault, templateOther]) + templateRepository.setTemplateAsDefault = cy.stub().resolves() + + mountDatasetTemplates() + + cy.findByText('Template Other') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).click() + }) + + cy.findByText( + /The template has been selected as the default template for this dataverse./i + ).should('exist') + + cy.findByText('Template Other') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).should('be.disabled') + cy.findByRole('button', { name: 'Make Default' }).should('not.exist') + }) + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).should('exist') + cy.findByRole('button', { name: 'Default' }).should('not.exist') + }) + + cy.wrap(templateRepository.getTemplatesByCollectionId).should('have.been.calledOnce') + }) + + it('unsets a default template and updates buttons without refetching', () => { + const [templateDefault, templateOther] = TemplateMother.createTemplates([ + { id: 1, name: 'Template Default', isDefault: true }, + { id: 2, name: 'Template Other', isDefault: false } + ]) + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([templateDefault, templateOther]) + templateRepository.unsetTemplateAsDefault = cy.stub().resolves() + + mountDatasetTemplates() + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).click({ force: true }) + }) + + cy.findByText( + /The template has been removed as the default template for this dataverse./i + ).should('exist') + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).should('exist') + }) + + cy.findByText('Template Other') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).should('exist') + }) + + cy.wrap(templateRepository.getTemplatesByCollectionId).should('have.been.calledOnce') + }) + + it('shows an error toast when setting default fails', () => { + const [templateDefault, templateOther] = TemplateMother.createTemplates([ + { id: 1, name: 'Template Default', isDefault: true }, + { id: 2, name: 'Template Other', isDefault: false } + ]) + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([templateDefault, templateOther]) + templateRepository.setTemplateAsDefault = cy + .stub() + .rejects(new WriteError('Set default failed')) + + mountDatasetTemplates() + + cy.findByText('Template Other') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).click() + }) + + cy.findByText(/Set default failed/i).should('exist') + + cy.findByText('Template Other') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).should('exist') + }) + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).should('be.disabled') + }) + }) + + it('shows an error toast when unsetting default fails', () => { + const [templateDefault, templateOther] = TemplateMother.createTemplates([ + { id: 1, name: 'Template Default', isDefault: true }, + { id: 2, name: 'Template Other', isDefault: false } + ]) + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([templateDefault, templateOther]) + templateRepository.unsetTemplateAsDefault = cy.stub().rejects(new Error('Unset failed')) + + mountDatasetTemplates() + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).click({ force: true }) + }) + + cy.findByText(/Something went wrong removing the default template. Try again later./i).should( + 'exist' + ) + + cy.findByText('Template Default') + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).should('be.disabled') + }) + }) + }) + it('hides the edit dropdown when the user cannot edit the collection', () => { collectionRepository.getUserPermissions = cy.stub().resolves({ canAddCollection: false, @@ -319,6 +503,7 @@ describe('Dataset Templates', () => { }) const rootTemplate = TemplateMother.create({ + id: 1, name: 'Template Root', collectionAlias: 'root', usageCount: 0 @@ -333,6 +518,28 @@ describe('Dataset Templates', () => { cy.findByRole('button', { name: 'Delete' }).should('exist').and('not.be.disabled') }) + it('navigates to edit template metadata from the edit dropdown', () => { + cy.findByRole('button', { name: 'Edit Template' }).click() + cy.findByText('Metadata').click() + + cy.findByTestId('location-display').should('have.text', '/templates/edit') + cy.findByTestId('location-search-display').should( + 'have.text', + `?id=1&ownerId=root&editMode=${TemplateEditMode.METADATA}` + ) + }) + + it('navigates to edit template terms from the edit dropdown', () => { + cy.findByRole('button', { name: 'Edit Template' }).click() + cy.findByText('Terms').click() + + cy.findByTestId('location-display').should('have.text', '/templates/edit') + cy.findByTestId('location-search-display').should( + 'have.text', + `?id=1&ownerId=root&editMode=${TemplateEditMode.LICENSE}` + ) + }) + it('disables delete when the template has been used in a dataset', () => { const usedTemplate = TemplateMother.create({ name: 'Used Template', diff --git a/tests/component/sections/templates/create-template/CreateTemplate.spec.tsx b/tests/component/sections/templates/create-template/CreateTemplate.spec.tsx index 5e5116d18..303d808b6 100644 --- a/tests/component/sections/templates/create-template/CreateTemplate.spec.tsx +++ b/tests/component/sections/templates/create-template/CreateTemplate.spec.tsx @@ -2,14 +2,34 @@ import { CreateTemplate } from '../../../../../src/sections/templates/create-tem import { CollectionRepository } from '../../../../../src/collection/domain/repositories/CollectionRepository' import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { TemplateRepository } from '../../../../../src/templates/domain/repositories/TemplateRepository' +import { RouteWithParams } from '../../../../../src/sections/Route.enum' import { CollectionMother } from '../../../collection/domain/models/CollectionMother' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { UpwardHierarchyNodeMother } from '../../../shared/hierarchy/domain/models/UpwardHierarchyNodeMother' const collectionRepository: CollectionRepository = {} as CollectionRepository const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository const templateRepository: TemplateRepository = {} as TemplateRepository -const collection = CollectionMother.create({ name: 'Root', id: 'root' }) +const root = UpwardHierarchyNodeMother.createCollection({ name: 'Root', id: 'root' }) +const subcollection = UpwardHierarchyNodeMother.createCollection({ + name: 'Subcollection', + id: 'subcollection', + parent: root +}) +const dataset = UpwardHierarchyNodeMother.createDataset({ + name: 'Dataset', + id: 'dataset-id', + persistentId: 'doi:10.5072/FK2/ABC123', + version: 'DRAFT', + parent: subcollection +}) + +const collection = CollectionMother.create({ + name: 'Current Collection', + id: 'root', + hierarchy: dataset +}) const collectionMetadataBlocksInfo = MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue() @@ -58,6 +78,29 @@ describe('Create Template', () => { cy.findByTestId('custom-instructions-toggle-title').should('exist') }) + it('renders the breadcrumb trail, heading, and create form', () => { + mountCreateTemplate() + + cy.findByRole('link', { name: 'Root' }).should('have.attr', 'href', '/collections') + cy.findByRole('link', { name: 'Subcollection' }).should( + 'have.attr', + 'href', + '/collections/subcollection' + ) + cy.findByRole('link', { name: 'Dataset' }).should( + 'have.attr', + 'href', + '/datasets?persistentId=doi:10.5072/FK2/ABC123&version=DRAFT' + ) + cy.findByRole('link', { name: 'Dataset Templates' }).should( + 'have.attr', + 'href', + RouteWithParams.COLLECTION_TEMPLATES('root') + ) + cy.findByLabelText(/Template Name/).should('exist') + cy.findByRole('button', { name: 'Save + Add Terms' }).should('exist') + }) + it('should render asterisks tips under template name', () => { mountCreateTemplate() diff --git a/tests/component/sections/templates/create-template/useSubmitTemplate.spec.tsx b/tests/component/sections/templates/create-template/useSubmitTemplate.spec.tsx index 3f8c657e3..9231aa6a0 100644 --- a/tests/component/sections/templates/create-template/useSubmitTemplate.spec.tsx +++ b/tests/component/sections/templates/create-template/useSubmitTemplate.spec.tsx @@ -2,8 +2,10 @@ import { act, renderHook } from '@testing-library/react' import { I18nextProvider } from 'react-i18next' import i18next, { i18n as I18nInstance } from 'i18next' import { initReactI18next } from 'react-i18next' -import { createTemplate, WriteError } from '@iqss/dataverse-client-javascript' +import { WriteError } from '@iqss/dataverse-client-javascript' import { TemplateInfo, TemplateInstructionInfo } from '@/templates/domain/models/TemplateInfo' +import { UpdateTemplateMetadataInfo } from '@/templates/domain/models/UpdateTemplateMetadataInfo' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' import { SubmissionStatus, useSubmitTemplate @@ -25,6 +27,12 @@ const templatePayloadWithInstructions: TemplateInfo = { instructions: templateInstructions } +const updatePayload: UpdateTemplateMetadataInfo = { + name: 'Renamed Template', + fields: [], + instructions: [] +} + const createI18n = (): I18nInstance => { const instance = i18next.createInstance() void instance.use(initReactI18next).init({ @@ -40,6 +48,11 @@ const createI18n = (): I18nInstance => { errors: { saveFailed: 'Save failed.' } + }, + editTemplate: { + errors: { + saveMetadataFailed: 'Update failed.' + } } } } @@ -49,6 +62,8 @@ const createI18n = (): I18nInstance => { return instance } +const createRepository = (): TemplateRepository => ({} as TemplateRepository) + describe('useSubmitTemplate', () => { let i18n: I18nInstance @@ -56,78 +71,180 @@ describe('useSubmitTemplate', () => { i18n = createI18n() }) - it('should submit the template successfully', async () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + describe('create mode', () => { + it('submits the template successfully', async () => { + const repository = createRepository() + repository.createTemplate = cy.stub().resolves() + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'create', + templateRepository: repository, + collectionId: 'root' + }), + { + wrapper: ({ children }) => {children} + } + ) - const { result } = renderHook(() => useSubmitTemplate('root'), { - wrapper: ({ children }) => {children} - }) + expect(result.current.submissionStatus).to.equal(SubmissionStatus.NotSubmitted) - expect(result.current.submissionStatus).to.equal(SubmissionStatus.NotSubmitted) - expect(result.current.submitError).to.equal(null) + await act(async () => { + const didSubmit = await result.current.submitTemplate(templatePayload) + expect(didSubmit).to.equal(true) + }) - await act(async () => { - const didSubmit = await result.current.submitTemplate(templatePayload) - expect(didSubmit).to.equal(true) + expect(result.current.submissionStatus).to.equal(SubmissionStatus.SubmitComplete) + expect(result.current.submitError).to.equal(null) + expect(repository.createTemplate).to.have.been.calledWith(templatePayload, 'root') }) - expect(result.current.submissionStatus).to.equal(SubmissionStatus.SubmitComplete) - expect(result.current.submitError).to.equal(null) - cy.wrap(executeStub).should('have.been.calledWith', templatePayload, 'root') - - executeStub.restore() - }) + it('submits the template with instructions', async () => { + const repository = createRepository() + repository.createTemplate = cy.stub().resolves() + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'create', + templateRepository: repository, + collectionId: 'root' + }), + { + wrapper: ({ children }) => {children} + } + ) - it('should handle WriteError responses', async () => { - const executeStub = cy.stub(createTemplate, 'execute').rejects(new WriteError('Write error')) + await act(async () => { + await result.current.submitTemplate(templatePayloadWithInstructions) + }) - const { result } = renderHook(() => useSubmitTemplate('root'), { - wrapper: ({ children }) => {children} + expect(repository.createTemplate).to.have.been.calledWith( + templatePayloadWithInstructions, + 'root' + ) }) - await act(async () => { - const didSubmit = await result.current.submitTemplate(templatePayload) - expect(didSubmit).to.equal(false) + it('surfaces a WriteError reason as the submit error', async () => { + const repository = createRepository() + repository.createTemplate = cy.stub().rejects(new WriteError('Write error')) + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'create', + templateRepository: repository, + collectionId: 'root' + }), + { + wrapper: ({ children }) => {children} + } + ) + + await act(async () => { + const didSubmit = await result.current.submitTemplate(templatePayload) + expect(didSubmit).to.equal(false) + }) + + expect(result.current.submissionStatus).to.equal(SubmissionStatus.Errored) + expect(result.current.submitError).to.equal('Write error') }) - expect(result.current.submissionStatus).to.equal(SubmissionStatus.Errored) - expect(result.current.submitError).to.equal('Write error') + it('falls back to a generic message for non-WriteError, non-Error rejections', async () => { + const repository = createRepository() + repository.createTemplate = cy.stub().rejects('Error') + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'create', + templateRepository: repository, + collectionId: 'root' + }), + { + wrapper: ({ children }) => {children} + } + ) + + await act(async () => { + await result.current.submitTemplate(templatePayload) + }) - executeStub.restore() + expect(result.current.submitError).to.equal('Save failed.') + }) }) - it('should handle non-WriteError responses with a default message', async () => { - const executeStub = cy.stub(createTemplate, 'execute').rejects('Error') + describe('edit mode', () => { + it('updates the template metadata successfully', async () => { + const repository = createRepository() + repository.updateTemplateMetadata = cy.stub().resolves() + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'edit', + templateRepository: repository, + templateId: 42 + }), + { + wrapper: ({ children }) => {children} + } + ) - const { result } = renderHook(() => useSubmitTemplate('root'), { - wrapper: ({ children }) => {children} - }) + await act(async () => { + const didSubmit = await result.current.submitTemplate(updatePayload) + expect(didSubmit).to.equal(true) + }) - await act(async () => { - const didSubmit = await result.current.submitTemplate(templatePayload) - expect(didSubmit).to.equal(false) + expect(result.current.submissionStatus).to.equal(SubmissionStatus.SubmitComplete) + expect(repository.updateTemplateMetadata).to.have.been.calledWith(42, updatePayload, true) }) - expect(result.current.submissionStatus).to.equal(SubmissionStatus.Errored) - expect(result.current.submitError).to.equal('Save failed.') - - executeStub.restore() - }) + it('uses the edit-mode error message for non-WriteError exceptions without messages', async () => { + const repository = createRepository() + repository.updateTemplateMetadata = cy.stub().rejects('weird') + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'edit', + templateRepository: repository, + templateId: 42 + }), + { + wrapper: ({ children }) => {children} + } + ) - it('should submit template with instructions', async () => { - const executeStub = cy.stub(createTemplate, 'execute').resolves() + await act(async () => { + await result.current.submitTemplate(updatePayload) + }) - const { result } = renderHook(() => useSubmitTemplate('root'), { - wrapper: ({ children }) => {children} + expect(result.current.submitError).to.equal('Update failed.') }) - await act(async () => { - const didSubmit = await result.current.submitTemplate(templatePayloadWithInstructions) - expect(didSubmit).to.equal(true) - }) + it('passes through a regular Error message in edit mode', async () => { + const repository = createRepository() + repository.updateTemplateMetadata = cy.stub().rejects(new Error('Network down')) + + const { result } = renderHook( + () => + useSubmitTemplate({ + mode: 'edit', + templateRepository: repository, + templateId: 42 + }), + { + wrapper: ({ children }) => {children} + } + ) - cy.wrap(executeStub).should('have.been.calledWith', templatePayloadWithInstructions, 'root') + await act(async () => { + await result.current.submitTemplate(updatePayload) + }) - executeStub.restore() + expect(result.current.submitError).to.equal('Network down') + }) }) }) diff --git a/tests/component/sections/templates/edit-template-metadata/EditTemplateMetadata.spec.tsx b/tests/component/sections/templates/edit-template-metadata/EditTemplateMetadata.spec.tsx new file mode 100644 index 000000000..d59744956 --- /dev/null +++ b/tests/component/sections/templates/edit-template-metadata/EditTemplateMetadata.spec.tsx @@ -0,0 +1,134 @@ +import { ReadError } from '@iqss/dataverse-client-javascript' +import { EditTemplateMetadata } from '@/sections/templates/edit-template-metadata/EditTemplateMetadata' +import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' +import { MetadataBlockInfoRepository } from '@/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { CollectionMother } from '../../../collection/domain/models/CollectionMother' +import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { TemplateMother } from '../TemplateMother' + +const collectionRepository: CollectionRepository = {} as CollectionRepository +const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository +const templateRepository: TemplateRepository = {} as TemplateRepository + +const collection = CollectionMother.create({ name: 'Root', id: 'root' }) +const collectionMetadataBlocksInfo = + MetadataBlockInfoMother.getByCollectionIdDisplayedOnCreateTrue() + +const template = TemplateMother.create({ id: 1, name: 'Existing Template' }) + +describe('EditTemplateMetadata', () => { + beforeEach(() => { + collectionRepository.getById = cy.stub().resolves(collection) + metadataBlockInfoRepository.getByCollectionId = cy.stub().resolves(collectionMetadataBlocksInfo) + templateRepository.getTemplate = cy.stub().resolves(template) + templateRepository.getTemplatesByCollectionId = cy.stub().resolves([template]) + }) + + const mountEditTemplateMetadata = () => + cy.customMount( + + ) + + it('shows page not found when the owner collection does not exist', () => { + collectionRepository.getById = cy.stub().resolves(null) + + mountEditTemplateMetadata() + + cy.findByTestId('not-found-page').should('exist') + }) + + it('shows the loading skeleton while loading', () => { + const delayedTime = 200 + templateRepository.getTemplate = cy.stub().callsFake(() => { + return Cypress.Promise.delay(delayedTime).then(() => template) + }) + + mountEditTemplateMetadata() + + cy.clock() + cy.findByTestId('edit-template-metadata-skeleton').should('exist') + + cy.tick(delayedTime) + cy.findByTestId('edit-template-metadata-skeleton').should('not.exist') + }) + + it('renders the page title and breadcrumb', () => { + mountEditTemplateMetadata() + + cy.findByRole('heading', { name: /Edit Template Metadata/i }).should('exist') + cy.findByRole('link', { name: /Dataset Templates/i }).should('exist') + }) + + it('pre-fills the template name with the existing value', () => { + mountEditTemplateMetadata() + + cy.findByLabelText(/Template Name/).should('have.value', 'Existing Template') + }) + + it('renders Save Changes and Cancel buttons', () => { + mountEditTemplateMetadata() + + cy.findByRole('button', { name: 'Save Changes' }).should('exist') + cy.findByRole('button', { name: 'Cancel' }).should('exist') + }) + + it('shows an error message when the template fails to load', () => { + templateRepository.getTemplate = cy.stub().rejects(new ReadError('Template fetch boom')) + + mountEditTemplateMetadata() + + cy.findByText(/Template fetch boom/i).should('exist') + }) + + it('shows the fallback loading-template error when no template is returned', () => { + templateRepository.getTemplate = cy.stub().resolves(null) + + mountEditTemplateMetadata() + + cy.findByRole('alert').should( + 'contain.text', + 'Something went wrong loading the template. Try again later.' + ) + cy.findByLabelText(/Template Name/).should('not.exist') + }) + + it('calls updateTemplateMetadata with the edited name', () => { + templateRepository.updateTemplateMetadata = cy.stub().resolves() + + mountEditTemplateMetadata() + + cy.findByLabelText(/Template Name/) + .clear() + .type('Renamed Template') + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(templateRepository.updateTemplateMetadata).should('have.been.calledOnce') + cy.wrap(templateRepository.updateTemplateMetadata).then((stub) => { + const args = (stub as unknown as sinon.SinonStub).getCall(0).args as [ + number, + { name: string } + ] + expect(args[0]).to.equal(1) + expect(args[1].name).to.equal('Renamed Template') + }) + }) + + it('shows the validation error when the template name is cleared', () => { + mountEditTemplateMetadata() + + cy.findByLabelText(/Template Name/).clear() + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findAllByText(/Please add in a name for the dataset template./i).should( + 'have.length.at.least', + 1 + ) + }) +}) diff --git a/tests/component/sections/templates/edit-template-terms/EditTemplateLicenseTerms.spec.tsx b/tests/component/sections/templates/edit-template-terms/EditTemplateLicenseTerms.spec.tsx new file mode 100644 index 000000000..01bcedaec --- /dev/null +++ b/tests/component/sections/templates/edit-template-terms/EditTemplateLicenseTerms.spec.tsx @@ -0,0 +1,195 @@ +import { LicenseRepository } from '@/licenses/domain/repositories/LicenseRepository' +import { License } from '@/licenses/domain/models/License' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { EditTemplateLicenseTerms } from '@/sections/templates/edit-template-terms/EditTemplateLicenseTerms' +import { TemplateMother } from '../TemplateMother' + +const licenseRepository: LicenseRepository = {} as LicenseRepository +const templateRepository: TemplateRepository = {} as TemplateRepository + +const mockLicenses: License[] = [ + { + id: 1, + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: '', + active: true, + isDefault: true, + sortOrder: 0 + }, + { + id: 2, + name: 'CC BY 4.0', + uri: 'http://creativecommons.org/licenses/by/4.0', + iconUri: '', + active: true, + isDefault: false, + sortOrder: 2 + } +] + +describe('EditTemplateLicenseTerms', () => { + beforeEach(() => { + licenseRepository.getAvailableStandardLicenses = cy.stub().resolves(mockLicenses) + }) + + const mountEditTemplateLicenseTerms = ( + onSuccess: () => void = cy.stub().as('onSuccess'), + template = TemplateMother.create({ + id: 5, + name: 'Tpl', + license: mockLicenses[0], + termsOfUse: { termsOfAccess: { fileAccessRequest: false } } + }) + ) => + cy.customMount( + + ) + + it('renders the license selector and Save Changes button', () => { + mountEditTemplateLicenseTerms() + + cy.findByRole('button', { name: 'Save Changes' }).should('exist') + }) + + it('submits a license update with the selected license name', () => { + templateRepository.updateTemplateLicenseTerms = cy.stub().resolves() + const onSuccess = cy.stub() + + mountEditTemplateLicenseTerms(onSuccess) + + // Wait for licenses to load and form to revalidate + cy.findByRole('option', { name: 'CC0 1.0' }).should('exist') + cy.findByRole('button', { name: 'Save Changes' }).should('not.be.disabled').click() + + cy.wrap(templateRepository.updateTemplateLicenseTerms).should('have.been.calledOnce') + cy.wrap(templateRepository.updateTemplateLicenseTerms).then((stub) => { + const args = (stub as unknown as sinon.SinonStub).getCall(0).args as [ + number, + { name?: string } + ] + expect(args[0]).to.equal(5) + expect(args[1].name).to.match(/CC0 1\.0|CC BY 4\.0/) + }) + cy.wrap(onSuccess).should('have.been.calledOnce') + }) + + it('shows an error alert when license update fails', () => { + templateRepository.updateTemplateLicenseTerms = cy + .stub() + .rejects(new Error('Cannot update license')) + + mountEditTemplateLicenseTerms() + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findByText(/Cannot update license/i).should('exist') + }) + + it('renders a Close button when onCancel is provided', () => { + cy.customMount( + + ) + + cy.findByRole('button', { name: /Close/i }).should('exist') + }) + + it('calls onCancel when Close is clicked', () => { + const onCancel = cy.stub().as('onCancel') + + cy.customMount( + + ) + + cy.findByRole('button', { name: /Close/i }).click() + + cy.get('@onCancel').should('have.been.calledOnce') + }) + + it('renders the custom terms fields when the template uses custom terms', () => { + mountEditTemplateLicenseTerms( + cy.stub(), + TemplateMother.create({ + id: 5, + name: 'Tpl', + termsOfUse: { + termsOfAccess: { fileAccessRequest: false }, + customTerms: { + termsOfUse: 'Existing custom terms', + confidentialityDeclaration: 'Confidentiality', + specialPermissions: 'Permissions', + restrictions: 'Restrictions', + citationRequirements: 'Citations', + depositorRequirements: 'Depositor requirements', + conditions: 'Conditions', + disclaimer: 'Disclaimer' + } + } + }) + ) + + cy.findByTestId('customTerms.termsOfUse').should('have.value', 'Existing custom terms') + cy.findByTestId('customTerms.confidentialityDeclaration').should( + 'have.value', + 'Confidentiality' + ) + }) + + it('submits custom terms when the custom terms option is selected', () => { + templateRepository.updateTemplateLicenseTerms = cy.stub().resolves() + + mountEditTemplateLicenseTerms( + cy.stub(), + TemplateMother.create({ + id: 5, + name: 'Tpl', + termsOfUse: { + termsOfAccess: { fileAccessRequest: false }, + customTerms: { + termsOfUse: 'Existing custom terms', + confidentialityDeclaration: '', + specialPermissions: '', + restrictions: '', + citationRequirements: '', + depositorRequirements: '', + conditions: '', + disclaimer: '' + } + } + }) + ) + + cy.findByTestId('customTerms.termsOfUse').clear().type('Updated custom terms') + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(templateRepository.updateTemplateLicenseTerms).should('have.been.calledWith', 5, { + customTerms: { + termsOfUse: 'Updated custom terms', + confidentialityDeclaration: '', + specialPermissions: '', + restrictions: '', + citationRequirements: '', + depositorRequirements: '', + conditions: '', + disclaimer: '' + } + }) + }) +}) diff --git a/tests/component/sections/templates/edit-template-terms/EditTemplateTerms.spec.tsx b/tests/component/sections/templates/edit-template-terms/EditTemplateTerms.spec.tsx new file mode 100644 index 000000000..23d27b05a --- /dev/null +++ b/tests/component/sections/templates/edit-template-terms/EditTemplateTerms.spec.tsx @@ -0,0 +1,112 @@ +import { useLocation } from 'react-router-dom' +import { EditTemplateTerms } from '@/sections/templates/edit-template-terms' +import { LicenseRepository } from '@/licenses/domain/repositories/LicenseRepository' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { License } from '@/licenses/domain/models/License' +import { TemplateMother } from '../TemplateMother' + +const templateRepository: TemplateRepository = {} as TemplateRepository +const licenseRepository: LicenseRepository = {} as LicenseRepository + +const mockLicenses: License[] = [ + { + id: 1, + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: '', + active: true, + isDefault: true, + sortOrder: 0 + } +] + +const template = TemplateMother.create({ + id: 5, + name: 'Tpl', + license: mockLicenses[0], + termsOfUse: { + termsOfAccess: { + fileAccessRequest: true, + termsOfAccessForRestrictedFiles: 'Existing terms' + } + } +}) + +const LocationDisplay = () => { + const location = useLocation() + return {location.pathname} +} + +describe('EditTemplateTerms', () => { + beforeEach(() => { + templateRepository.getTemplate = cy.stub().resolves(template) + licenseRepository.getAvailableStandardLicenses = cy.stub().resolves(mockLicenses) + }) + + const mountEditTemplateTerms = ( + initialEntries = ['/templates/edit?id=5&ownerId=root&editMode=LICENSE'] + ) => + cy.customMount( + <> + + + >, + initialEntries + ) + + it('shows the loading skeleton while the template is loading', () => { + const delayedTime = 200 + templateRepository.getTemplate = cy.stub().callsFake(() => { + return Cypress.Promise.delay(delayedTime).then(() => template) + }) + + mountEditTemplateTerms() + + cy.clock() + cy.findByTestId('edit-template-terms-skeleton').should('exist') + + cy.tick(delayedTime) + cy.findByTestId('edit-template-terms-skeleton').should('not.exist') + }) + + it('renders the page title and tabs', () => { + mountEditTemplateTerms() + + cy.findByRole('heading', { name: /Edit Template Terms/i }).should('exist') + cy.findByRole('tab', { name: /Dataset Terms/i }).should('exist') + cy.findByRole('tab', { name: /Terms of Access/i }).should('exist') + }) + + it('shows the success alert after navigating from template creation', () => { + mountEditTemplateTerms([ + { + pathname: '/templates/edit', + search: '?id=5&ownerId=root&editMode=LICENSE', + state: { fromCreateTemplate: true } + } + ]) + + cy.findByText(/Template has been created/i).should('exist') + }) + + it('shows an error alert when the template fetch fails', () => { + templateRepository.getTemplate = cy.stub().rejects(new Error('Template fetch boom')) + + mountEditTemplateTerms() + + cy.findByText(/Something went wrong getting the template\. Try again later\./i).should('exist') + }) + + it('navigates back to the templates list when Close is clicked', () => { + mountEditTemplateTerms() + + cy.findByTestId('location-display').should('have.text', '/templates/edit') + cy.findByRole('button', { name: 'Close' }).click() + cy.findByTestId('location-display').should('have.text', '/root/templates') + }) +}) diff --git a/tests/component/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.spec.tsx b/tests/component/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.spec.tsx new file mode 100644 index 000000000..73b04fe1d --- /dev/null +++ b/tests/component/sections/templates/edit-template-terms/EditTemplateTermsOfAccess.spec.tsx @@ -0,0 +1,200 @@ +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { EditTemplateTermsOfAccess } from '@/sections/templates/edit-template-terms/EditTemplateTermsOfAccess' +import { TemplateMother } from '../TemplateMother' + +const templateRepository: TemplateRepository = {} as TemplateRepository + +const baseTemplate = TemplateMother.create({ + id: 9, + name: 'Tpl', + termsOfUse: { + termsOfAccess: { + fileAccessRequest: true, + termsOfAccessForRestrictedFiles: 'Existing terms' + } + } +}) + +describe('EditTemplateTermsOfAccess', () => { + const mountEditTemplateTermsOfAccess = (onSuccess: () => void = cy.stub()) => + cy.customMount( + + ) + + it('pre-fills the request-access checkbox and terms textarea', () => { + mountEditTemplateTermsOfAccess() + + cy.findByLabelText(/Enable access request/i).should('be.checked') + cy.findByText(/Terms of Access for Restricted Files/i).should('exist') + }) + + it('falls back to default terms of access when the template has none', () => { + cy.customMount( + + ) + + cy.findByLabelText(/Enable access request/i).should('not.be.checked') + cy.findByLabelText(/Terms of Access for Restricted Files/i).should('have.value', '') + cy.findByRole('button', { name: 'Save Changes' }).should('be.disabled') + }) + + it('submits the terms of access', () => { + templateRepository.updateTemplateTermsOfAccess = cy.stub().resolves() + const onSuccess = cy.stub() + + mountEditTemplateTermsOfAccess(onSuccess) + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(templateRepository.updateTemplateTermsOfAccess).should('have.been.calledOnce') + cy.wrap(templateRepository.updateTemplateTermsOfAccess).then((stub) => { + const args = (stub as unknown as sinon.SinonStub).getCall(0).args as [ + number, + { fileAccessRequest: boolean } + ] + expect(args[0]).to.equal(9) + expect(args[1].fileAccessRequest).to.equal(true) + }) + cy.wrap(onSuccess).should('have.been.calledOnce') + }) + + it('disables Save when request-access is off and terms are empty', () => { + cy.customMount( + + ) + + cy.findByRole('button', { name: 'Save Changes' }).should('be.disabled') + }) + + it('shows the required message when request-access is off and terms are cleared', () => { + cy.customMount( + + ) + + cy.findByLabelText(/Terms of Access for Restricted Files/i) + .clear() + .blur() + cy.findByText( + 'Add information about terms of access for restricted files when request access is disabled.' + ).should('exist') + cy.findByRole('button', { name: 'Save Changes' }).should('be.disabled') + }) + + it('renders a Close button when onCancel is provided', () => { + cy.customMount( + + ) + + cy.findByRole('button', { name: /Close/i }).should('exist') + }) + + it('calls onCancel when Close is clicked', () => { + const onCancel = cy.stub().as('onCancel') + + cy.customMount( + + ) + + cy.findByRole('button', { name: /Close/i }).click() + + cy.get('@onCancel').should('have.been.calledOnce') + }) + + it('shows an error message when the update fails', () => { + templateRepository.updateTemplateTermsOfAccess = cy.stub().rejects(new Error('TOA boom')) + + mountEditTemplateTermsOfAccess() + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.findByText(/TOA boom/i).should('exist') + }) + + it('shows the saving label while the update is in flight', () => { + templateRepository.updateTemplateTermsOfAccess = cy + .stub() + .callsFake(() => Cypress.Promise.delay(200)) + + mountEditTemplateTermsOfAccess() + + cy.findByRole('button', { name: 'Save Changes' }).click() + cy.findByRole('button', { name: 'Saving' }).should('be.disabled') + }) + + it('enables Save and submits when request access is off but terms are provided', () => { + templateRepository.updateTemplateTermsOfAccess = cy.stub().resolves() + + cy.customMount( + + ) + + cy.findByRole('button', { name: 'Save Changes' }).should('be.disabled') + cy.findByLabelText(/Terms of Access for Restricted Files/i).type('Provide contact details') + cy.findByRole('button', { name: 'Save Changes' }).should('be.enabled').click() + + cy.wrap(templateRepository.updateTemplateTermsOfAccess).should('have.been.calledWith', 9, { + fileAccessRequest: false, + termsOfAccessForRestrictedFiles: 'Provide contact details' + }) + }) +}) diff --git a/tests/component/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.spec.tsx b/tests/component/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.spec.tsx new file mode 100644 index 000000000..405946b26 --- /dev/null +++ b/tests/component/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms.spec.tsx @@ -0,0 +1,109 @@ +import { act, renderHook } from '@testing-library/react' +import { I18nextProvider } from 'react-i18next' +import i18next, { i18n as I18nInstance } from 'i18next' +import { initReactI18next } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { UpdateTemplateLicenseTermsInfo } from '@/templates/domain/models/UpdateTemplateLicenseTermsInfo' +import { useUpdateTemplateLicenseTerms } from '@/sections/templates/edit-template-terms/useUpdateTemplateLicenseTerms' + +const payload: UpdateTemplateLicenseTermsInfo = { name: 'CC0 1.0' } + +const createI18n = (): I18nInstance => { + const instance = i18next.createInstance() + void instance.use(initReactI18next).init({ + lng: 'en', + fallbackLng: 'en', + ns: ['datasetTemplates'], + defaultNS: 'datasetTemplates', + initImmediate: false, + resources: { + en: { + datasetTemplates: { + editTemplate: { errors: { saveLicenseFailed: 'Update failed.' } } + } + } + } + }) + return instance +} + +const createRepository = (): TemplateRepository => ({} as TemplateRepository) + +describe('useUpdateTemplateLicenseTerms', () => { + let i18n: I18nInstance + + beforeEach(() => { + i18n = createI18n() + }) + + it('updates license/terms and calls onSuccess', async () => { + const repository = createRepository() + repository.updateTemplateLicenseTerms = cy.stub().resolves() + const onSuccess = cy.stub() + + const { result } = renderHook( + () => useUpdateTemplateLicenseTerms({ templateRepository: repository, onSuccess }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + const ok = await result.current.handleUpdateLicenseTerms(7, payload) + expect(ok).to.equal(true) + }) + + expect(repository.updateTemplateLicenseTerms).to.have.been.calledWith(7, payload) + expect(onSuccess).to.have.been.calledOnce + expect(result.current.error).to.equal(null) + expect(result.current.isLoading).to.equal(false) + }) + + it('surfaces a WriteError reason', async () => { + const repository = createRepository() + repository.updateTemplateLicenseTerms = cy.stub().rejects(new WriteError('Bad payload')) + + const { result } = renderHook( + () => useUpdateTemplateLicenseTerms({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + const ok = await result.current.handleUpdateLicenseTerms(7, payload) + expect(ok).to.equal(false) + }) + + expect(result.current.error).to.equal('Bad payload') + }) + + it('surfaces a regular Error message', async () => { + const repository = createRepository() + repository.updateTemplateLicenseTerms = cy.stub().rejects(new Error('Network error')) + + const { result } = renderHook( + () => useUpdateTemplateLicenseTerms({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + await result.current.handleUpdateLicenseTerms(7, payload) + }) + + expect(result.current.error).to.equal('Network error') + }) + + it('falls back to the generic message for non-Error rejections', async () => { + const repository = createRepository() + repository.updateTemplateLicenseTerms = cy.stub().rejects('weird') + + const { result } = renderHook( + () => useUpdateTemplateLicenseTerms({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + await result.current.handleUpdateLicenseTerms(7, payload) + }) + + expect(result.current.error).to.equal('Update failed.') + }) +}) diff --git a/tests/component/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.spec.tsx b/tests/component/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.spec.tsx new file mode 100644 index 000000000..15d0a464f --- /dev/null +++ b/tests/component/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess.spec.tsx @@ -0,0 +1,111 @@ +import { act, renderHook } from '@testing-library/react' +import { I18nextProvider } from 'react-i18next' +import i18next, { i18n as I18nInstance } from 'i18next' +import { initReactI18next } from 'react-i18next' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' +import { useUpdateTemplateTermsOfAccess } from '@/sections/templates/edit-template-terms/useUpdateTemplateTermsOfAccess' + +const termsOfAccess: TermsOfAccess = { fileAccessRequest: true } + +const createI18n = (): I18nInstance => { + const instance = i18next.createInstance() + void instance.use(initReactI18next).init({ + lng: 'en', + fallbackLng: 'en', + ns: ['datasetTemplates'], + defaultNS: 'datasetTemplates', + initImmediate: false, + resources: { + en: { + datasetTemplates: { + editTemplate: { errors: { saveTermsOfAccessFailed: 'Update failed.' } } + } + } + } + }) + return instance +} + +const createRepository = (): TemplateRepository => ({} as TemplateRepository) + +describe('useUpdateTemplateTermsOfAccess', () => { + let i18n: I18nInstance + + beforeEach(() => { + i18n = createI18n() + }) + + it('updates terms of access and calls onSuccess', async () => { + const repository = createRepository() + repository.updateTemplateTermsOfAccess = cy.stub().resolves() + const onSuccess = cy.stub() + + const { result } = renderHook( + () => useUpdateTemplateTermsOfAccess({ templateRepository: repository, onSuccess }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + const ok = await result.current.handleUpdateTermsOfAccess(7, termsOfAccess) + expect(ok).to.equal(true) + }) + + expect(repository.updateTemplateTermsOfAccess).to.have.been.calledWith(7, termsOfAccess) + expect(onSuccess).to.have.been.calledOnce + expect(result.current.error).to.equal(null) + }) + + it('surfaces a WriteError reason', async () => { + const repository = createRepository() + repository.updateTemplateTermsOfAccess = cy.stub().rejects(new WriteError('Forbidden')) + + const { result } = renderHook( + () => + useUpdateTemplateTermsOfAccess({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + const ok = await result.current.handleUpdateTermsOfAccess(7, termsOfAccess) + expect(ok).to.equal(false) + }) + + expect(result.current.error).to.equal('Forbidden') + }) + + it('surfaces a regular Error message', async () => { + const repository = createRepository() + repository.updateTemplateTermsOfAccess = cy.stub().rejects(new Error('Boom')) + + const { result } = renderHook( + () => + useUpdateTemplateTermsOfAccess({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + await result.current.handleUpdateTermsOfAccess(7, termsOfAccess) + }) + + expect(result.current.error).to.equal('Boom') + }) + + it('falls back to the generic message for non-Error rejections', async () => { + const repository = createRepository() + repository.updateTemplateTermsOfAccess = cy.stub().rejects('weird') + + const { result } = renderHook( + () => + useUpdateTemplateTermsOfAccess({ templateRepository: repository, onSuccess: cy.stub() }), + { wrapper: ({ children }) => {children} } + ) + + await act(async () => { + await result.current.handleUpdateTermsOfAccess(7, termsOfAccess) + }) + + expect(result.current.error).to.equal('Update failed.') + }) +}) diff --git a/tests/component/sections/templates/useSetTemplateAsDefault.spec.tsx b/tests/component/sections/templates/useSetTemplateAsDefault.spec.tsx new file mode 100644 index 000000000..bebb5189c --- /dev/null +++ b/tests/component/sections/templates/useSetTemplateAsDefault.spec.tsx @@ -0,0 +1,157 @@ +import { renderHook, act, waitFor } from '@testing-library/react' +import { WriteError } from '@iqss/dataverse-client-javascript' +import { toast } from 'react-toastify' +import { TemplateRepository } from '@/templates/domain/repositories/TemplateRepository' +import { useSetTemplateAsDefault } from '@/sections/templates/useSetTemplateAsDefault' + +describe('useSetTemplateAsDefault', () => { + let templateRepository: TemplateRepository + const collectionId = 'test-collection' + + beforeEach(() => { + templateRepository = {} as TemplateRepository + cy.stub(toast, 'success') + cy.stub(toast, 'error') + }) + + it('should initialize with default state', async () => { + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + await waitFor(() => { + expect(result.current.isSettingDefault).to.deep.equal(false) + expect(typeof result.current.handleSetTemplateAsDefault).to.deep.equal('function') + expect(typeof result.current.handleUnsetTemplateAsDefault).to.deep.equal('function') + }) + }) + + describe('handleSetTemplateAsDefault', () => { + it('should successfully set template as default', async () => { + templateRepository.setTemplateAsDefault = cy.stub().resolves() + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + let success: boolean | undefined + + await act(async () => { + success = await result.current.handleSetTemplateAsDefault(123) + }) + + expect(success).to.equal(true) + expect(templateRepository.setTemplateAsDefault).to.have.been.calledWith(123, collectionId) + expect(toast.success).to.have.been.calledOnce + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + + it('should handle WriteError and show the reason', async () => { + templateRepository.setTemplateAsDefault = cy + .stub() + .rejects(new WriteError('Permission denied')) + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + let success: boolean | undefined + + await act(async () => { + success = await result.current.handleSetTemplateAsDefault(123) + }) + + expect(success).to.equal(false) + expect(toast.error).to.have.been.calledWith('Permission denied') + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + + it('should handle WriteError and fall back to the error message when no reason is present', async () => { + const error = new WriteError() + + templateRepository.setTemplateAsDefault = cy.stub().rejects(error) + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + await act(async () => { + await result.current.handleSetTemplateAsDefault(123) + }) + + expect(toast.error).to.have.been.calledWith(error.message) + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + + it('should handle unknown errors and show generic error toast', async () => { + templateRepository.setTemplateAsDefault = cy.stub().rejects(new Error('Network error')) + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + await act(async () => { + await result.current.handleSetTemplateAsDefault(123) + }) + + expect(toast.error).to.have.been.calledOnce + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + }) + + describe('handleUnsetTemplateAsDefault', () => { + it('should successfully unset template as default', async () => { + templateRepository.unsetTemplateAsDefault = cy.stub().resolves() + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + let success: boolean | undefined + + await act(async () => { + success = await result.current.handleUnsetTemplateAsDefault() + }) + + expect(success).to.equal(true) + expect(templateRepository.unsetTemplateAsDefault).to.have.been.calledWith(collectionId) + expect(toast.success).to.have.been.calledOnce + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + + it('should handle WriteError and show the reason', async () => { + templateRepository.unsetTemplateAsDefault = cy + .stub() + .rejects(new WriteError('Not authorized')) + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + let success: boolean | undefined + + await act(async () => { + success = await result.current.handleUnsetTemplateAsDefault() + }) + + expect(success).to.equal(false) + expect(toast.error).to.have.been.calledWith('Not authorized') + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + + it('should handle unknown errors and show generic error toast', async () => { + templateRepository.unsetTemplateAsDefault = cy.stub().rejects(new Error('Network error')) + + const { result } = renderHook(() => + useSetTemplateAsDefault({ collectionId, templateRepository }) + ) + + await act(async () => { + await result.current.handleUnsetTemplateAsDefault() + }) + + expect(toast.error).to.have.been.calledOnce + expect(result.current.isSettingDefault).to.deep.equal(false) + }) + }) +}) diff --git a/tests/e2e-integration/e2e/sections/templates/DatasetTemplates.spec.tsx b/tests/e2e-integration/e2e/sections/templates/DatasetTemplates.spec.tsx index 694ef000f..328d1793e 100644 --- a/tests/e2e-integration/e2e/sections/templates/DatasetTemplates.spec.tsx +++ b/tests/e2e-integration/e2e/sections/templates/DatasetTemplates.spec.tsx @@ -4,7 +4,7 @@ import { DatasetHelper } from '@tests/e2e-integration/shared/datasets/DatasetHel const CREATE_TEMPLATE_PAGE_URL = `${FRONTEND_BASE_PATH}/root/templates/create` const TEMPLATES_PAGE_URL = `${FRONTEND_BASE_PATH}/root/templates` - +const CREATE_DATASET_PAGE_URL = `${FRONTEND_BASE_PATH}/datasets/root/create` describe('Dataset Templates', () => { let templateName: string @@ -62,7 +62,7 @@ describe('Dataset Templates', () => { cy.findByRole('button', { name: 'Save + Add Terms' }).click() cy.findByText(/Success! Template has been created./i).should('exist') - cy.findByTestId('cancel-edit-template-terms-button').click() + cy.findByTestId('cancel-edit-template-license-terms-button').click() cy.url().should('include', TEMPLATES_PAGE_URL) cy.findByText(templateName) @@ -108,7 +108,7 @@ describe('Dataset Templates', () => { }) cy.findByRole('button', { name: 'Save + Add Terms' }).click() - cy.findByTestId('cancel-edit-template-terms-button').click() + cy.findByTestId('cancel-edit-template-license-terms-button').click() cy.url().should('include', TEMPLATES_PAGE_URL) cy.findByText(templateName) @@ -122,4 +122,136 @@ describe('Dataset Templates', () => { cy.findByText(/Template deleted./i).should('exist') }) + + it('toggles a template as default via the UI', () => { + cy.visit(CREATE_TEMPLATE_PAGE_URL) + + cy.findByLabelText(/Template Name/).type(templateName, { force: true }) + cy.findByLabelText(/^Title/i).type('Default Template Title', { force: true }) + + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).type('Test Author', { force: true }) + }) + + cy.findByText('Point of Contact') + .closest('.row') + .within(() => { + cy.findByLabelText(/^E-mail/i).type('test@example.com', { force: true }) + }) + + cy.findByText('Description') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Text/i).type('Template description', { force: true }) + }) + + cy.findByText('Subject') + .closest('.row') + .within(() => { + cy.findByLabelText('Toggle options menu').click({ force: true }) + cy.findByLabelText('Agricultural Sciences').click({ force: true }) + }) + + cy.findByRole('button', { name: 'Save + Add Terms' }).click() + cy.findByText(/Success! Template has been created./i).should('exist') + cy.findByTestId('cancel-edit-template-license-terms-button').click() + cy.url().should('include', TEMPLATES_PAGE_URL) + + cy.findByText(templateName) + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).click() + }) + + cy.findByText( + /The template has been selected as the default template for this dataverse./i + ).should('exist') + + cy.findByText(templateName) + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).should('be.disabled') + cy.findByRole('button', { name: 'Make Default' }).should('not.exist') + }) + + cy.findByText(templateName) + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Default' }).click({ force: true }) + }) + + cy.findByText( + /The template has been removed as the default template for this dataverse./i + ).should('exist') + + cy.findByText(templateName) + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).should('exist') + }) + }) + + it('sets a template as default and verifies it is auto-selected when creating a dataset', () => { + cy.visit(CREATE_TEMPLATE_PAGE_URL) + + cy.findByLabelText(/Template Name/).type(templateName, { force: true }) + cy.findByLabelText(/^Title/i).type('Auto Selected Title', { force: true }) + + cy.findByText('Author') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Name/i).type('Test Author', { force: true }) + }) + + cy.findByText('Point of Contact') + .closest('.row') + .within(() => { + cy.findByLabelText(/^E-mail/i).type('test@example.com', { force: true }) + }) + + cy.findByText('Description') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Text/i).type('Auto selected description', { force: true }) + }) + + cy.findByText('Subject') + .closest('.row') + .within(() => { + cy.findByLabelText('Toggle options menu').click({ force: true }) + cy.findByLabelText('Agricultural Sciences').click({ force: true }) + }) + + cy.findByRole('button', { name: 'Save + Add Terms' }).click() + cy.findByText(/Success! Template has been created./i).should('exist') + cy.findByTestId('cancel-edit-template-license-terms-button').click() + + cy.findByText(templateName) + .closest('tr') + .within(() => { + cy.findByRole('button', { name: 'Make Default' }).click() + }) + + cy.findByText( + /The template has been selected as the default template for this dataverse./i + ).should('exist') + + cy.visit(CREATE_DATASET_PAGE_URL) + cy.wait(3_000) + + cy.findByTestId('dataset-template-select').within(() => { + cy.findByText(templateName).should('exist') + cy.findByText('None').should('not.exist') + }) + + cy.findByLabelText(/^Title/i).should('have.value', 'Auto Selected Title') + + cy.findByText('Description') + .closest('.row') + .within(() => { + cy.findByLabelText(/^Text/i).should('have.value', 'Auto selected description') + }) + }) }) diff --git a/tests/e2e-integration/e2e/sections/templates/EditDatasetTemplate.spec.tsx b/tests/e2e-integration/e2e/sections/templates/EditDatasetTemplate.spec.tsx new file mode 100644 index 000000000..255c76e12 --- /dev/null +++ b/tests/e2e-integration/e2e/sections/templates/EditDatasetTemplate.spec.tsx @@ -0,0 +1,101 @@ +import { TestsUtils } from '../../../shared/TestsUtils' +import { DatasetHelper } from '@tests/e2e-integration/shared/datasets/DatasetHelper' +import { FRONTEND_BASE_PATH } from '@tests/e2e-integration/shared/basePath' + +const TEMPLATES_PAGE_URL = `${FRONTEND_BASE_PATH}/root/templates` +const editUrl = (id: number, mode: 'METADATA' | 'LICENSE') => + `${FRONTEND_BASE_PATH}/templates/edit?id=${id}&ownerId=root&editMode=${mode}` + +describe('Edit Dataset Template', () => { + let templateId: number | undefined + + beforeEach(() => { + TestsUtils.login().then((token) => { + cy.wrap(TestsUtils.setup(token)) + }) + + cy.wrap(null).then(async () => { + const created = await DatasetHelper.createTemplate() + templateId = created.id + }) + }) + + afterEach(() => { + cy.wrap(null).then(async () => { + if (!templateId) return + await DatasetHelper.deleteDatasetTemplate(templateId).catch(() => {}) + templateId = undefined + }) + }) + + it('opens the edit-metadata page from the templates listing dropdown', () => { + cy.visit(TEMPLATES_PAGE_URL) + + cy.findAllByText('Dataset Template One') + .first() + .closest('tr') + .within(() => { + cy.findByRole('button', { name: /Edit Template/i }).click({ force: true }) + }) + + cy.findByText('Metadata').click({ force: true }) + + cy.url().should('match', /\/templates\/edit\?.*editMode=METADATA/) + cy.findByRole('heading', { name: /Edit Template Metadata/i }).should('exist') + }) + + it('updates the template name and redirects to the templates listing', () => { + const renamedTemplate = `Renamed Template ${Date.now()}` + + cy.wrap(null).then(() => { + if (!templateId) throw new Error('Template not created') + cy.visit(editUrl(templateId, 'METADATA')) + }) + + cy.findByLabelText(/Template Name/).should('have.value', 'Dataset Template One') + cy.findByLabelText(/Template Name/) + .clear() + .type(renamedTemplate) + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.url().should('include', TEMPLATES_PAGE_URL) + cy.findByText(renamedTemplate).should('exist') + }) + + it('renders the terms page with two horizontal tabs', () => { + cy.wrap(null).then(() => { + if (!templateId) throw new Error('Template not created') + cy.visit(editUrl(templateId, 'LICENSE')) + }) + + cy.findByRole('tab', { name: /Dataset Terms/i }).should('exist') + cy.findByRole('tab', { name: /Restricted Files \+ Terms of Access/i }).should('exist') + }) + + it('updates the terms-of-access and redirects to the templates listing', () => { + cy.wrap(null).then(() => { + if (!templateId) throw new Error('Template not created') + cy.visit(editUrl(templateId, 'LICENSE')) + }) + + cy.findByRole('tab', { name: /Restricted Files \+ Terms of Access/i }).click() + + cy.findByLabelText(/Enable access request/i).check({ force: true }) + + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.url().should('include', TEMPLATES_PAGE_URL) + }) + + it('cancels editing and returns to the templates listing', () => { + cy.wrap(null).then(() => { + if (!templateId) throw new Error('Template not created') + cy.visit(editUrl(templateId, 'METADATA')) + }) + + cy.findByTestId('cancel-edit-template-metadata-button').click() + + cy.url().should('include', TEMPLATES_PAGE_URL) + }) +}) diff --git a/tests/e2e-integration/shared/datasets/DatasetHelper.ts b/tests/e2e-integration/shared/datasets/DatasetHelper.ts index 57e3647d4..6f653c4d2 100644 --- a/tests/e2e-integration/shared/datasets/DatasetHelper.ts +++ b/tests/e2e-integration/shared/datasets/DatasetHelper.ts @@ -298,6 +298,17 @@ export class DatasetHelper extends DataverseApiHelper { ) } + static async setTemplateAsDefault( + templateId: number, + collectionAlias = ROOT_COLLECTION_ALIAS + ): Promise { + return this.request(`/dataverses/${collectionAlias}/defaultTemplate/${templateId}`, 'PUT') + } + + static async unsetTemplateAsDefault(collectionAlias = ROOT_COLLECTION_ALIAS): Promise { + return this.request(`/dataverses/${collectionAlias}/defaultTemplate`, 'DELETE') + } + static async deleteDatasetTemplate(templateId: number): Promise { try { return await this.request(`/admin/template/${templateId}`, 'DELETE')