diff --git a/CHANGELOG.md b/CHANGELOG.md index 82cddf042..3b16353ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,29 @@ changes. ### Added +### Fixed + +### Changed + +### Removed + +## [v2.0.20](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.20) 2025-04-16 + +### Added + - Add Proposal discussion context that manages username [Issue 3341](https://github.com/IntersectMBO/govtool/issues/3341) - Add epochParams and ada holder balance to Proposal Discussion Pillar [Issue 2243](https://github.com/IntersectMBO/govtool/issues/2243) +- Add skeleton element to the drep and governance action cards on validation [Issue 3247](https://github.com/IntersectMBO/govtool/issues/3247) +- Add mock for the authors field in governance metadata [Issue 3307](https://github.com/IntersectMBO/govtool/issues/3307) +- Add uncontrolled image input to improve performance of large base64 encoded image strings +- Add snackbar to pdf ### Fixed - Fix scroll on a drawer on smaller resolution - Fix incorrect routing on connecting wallet on budget discussion page +- Fix missing validation on failed image sha generation [Issue 3246](https://github.com/IntersectMBO/govtool/issues/3246) +- Fix validating drep metadata ### Changed @@ -55,275 +71,6 @@ changes. ### Changed -- Bump CSL to v14 [Issue 3037](https://github.com/IntersectMBO/govtool/issues/3037) - -### Removed - -## [v2.0.17](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.17) 2025-03-18 - -### Added - -### Fixed - -- allow casting same vote with a different rationale [Issue 3191](https://github.com/IntersectMBO/govtool/issues/3191) - -### Changed - -### Removed - -## [v2.0.16](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.16) 2025-03-17 - -### Added - -- Add support for preprod in matomo analytics [Issue 3173](https://github.com/IntersectMBO/govtool/issues/3173) - -### Fixed - -- hotfix for ada handle and payment address validation order [Issue 3155](https://github.com/IntersectMBO/govtool/issues/3155) -- fix proposal list performance by pre-filtering active proposals [Issue 3190](https://github.com/IntersectMBO/govtool/issues/3190) -- Fix wrong prefix of script based dreps in CIP-129 standard [Issue 3203](https://github.com/IntersectMBO/govtool/issues/3203) - -### Changed - -- Exclude network total stake and info from network metrics [Issue 3189](https://github.com/IntersectMBO/govtool/issues/3189) -- Change restriction level for re-voting on governance actions [Issue 3191](https://github.com/IntersectMBO/govtool/issues/3191) - -### Removed - -## [v2.0.15](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.15) 2025-03-11 - -### Added - -- Add support for ada handle in drep payment address [Issue 3155](https://github.com/IntersectMBO/govtool/issues/3155) -- Improve numerical data formatting in drep directory [Issue 3148](https://github.com/IntersectMBO/govtool/issues/3148) - -### Fixed - -### Changed - -### Removed - -## [v2.0.14](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.14) 2025-03-06 - -### Added - -- Add image tag for DRep in GovTool metadata [Issue 3137](https://github.com/IntersectMBO/govtool/issues/3137) - -### Fixed - -- Fix calculating withdrawals when rewards records are empty for a given stake key [Issue 3134](https://github.com/IntersectMBO/govtool/issues/3134) - -### Changed - -### Removed - -- Remove ratification threshold for Info Action for Consitutional Committee [Issue 3108](https://github.com/IntersectMBO/govtool/issues/3108) - -## [v2.0.13](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.13) 2025-02-27 - -### Added - -### Fixed - -- Fix responsive error on menu [Issue 3055](https://github.com/IntersectMBO/govtool/issues/3055) -- Fix wrong placement of nav items in disconnected menu [Issue 3057](https://github.com/IntersectMBO/govtool/issues/3057) -- Fix missing subtraction withdrawals from ada holder balance [Issue 3061](https://github.com/IntersectMBO/govtool/issues/3061) -- Fix displaying voting power on direct voter cards - -### Changed - -- Change drep details and governance action header components to follow accessibility standards [Issue 3065](https://github.com/IntersectMBO/govtool/issues/3065) - -### Removed - -## [v2.0.12](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.12) 2025-02-21 - -### Added - -- Add metadata url and hash to drep details [Issue 2911](https://github.com/IntersectMBO/govtool/issues/2911) -- Add CC votes percentages, not voted and Ratification threshold -- Add support for submitting all 7 governance action types [Issue 2258](https://github.com/IntersectMBO/govtool/issues/2258) -- Add workflow to automatically update any of the @intersect.mbo package [Issue 2968](https://github.com/IntersectMBO/govtool/issues/2968) -- Add Propose Governance Action button in governance actions dashboard [Issue 1188](https://github.com/IntersectMBO/govtool/issues/1188) -- Add click handlers to non-interactive elements [Issue 2929](https://github.com/IntersectMBO/govtool/issues/2929) -- Allow searching for yourself in DRep Directory [Issue 2993](https://github.com/IntersectMBO/govtool/issues/2993) -- Add mathematical styling for governance actions [Issue 2984](https://github.com/IntersectMBO/govtool/issues/2984) -- Add script to update GovTool version -- Add `isStakeKeyRegistered` for the usage by pillars [Issue 2384](https://github.com/IntersectMBO/govtool/issues/2384) -- Add server side compression for large assets -- Add outcomes - -### Fixed - -- Fix calculating votes counting for governance actions -- Fix crashing backend on unhandled missing proposal from vote [Issue 2920](https://github.com/IntersectMBO/govtool/issues/2920) -- Remove abstain votes (not auto abstain) from total DRep stake -- Fix counting committee members [Issue 2948](https://github.com/IntersectMBO/govtool/issues/2948) -- Fix refetching DRep list on every enter [Issue 2994](https://github.com/IntersectMBO/govtool/issues/2994) -- Fix displaying helper buttons on governance action card details [Issue 3022](https://github.com/IntersectMBO/govtool/issues/3022) - -### Changed - -- Change threshold visual representation in governance action votes -- Resize governance action details columns -- Update @intersect.mbo/pdf-ui to v0.6.0 -- Bump actions/cache to v4 across workflows -- Unify ADA Format across the application [Issue 3031](https://github.com/IntersectMBO/govtool/issues/3031) -- Change default filtering for DRep directory to show the active DReps [Issue 3035](https://github.com/IntersectMBO/govtool/issues/3035) - -### Removed - -- Remove abstain from total DRep votes calculation - -## [v2.0.11](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.11) 2025-02-04 - -### Added - -- - -### Fixed - -- Fix displaying DRep with doNotList property as string -- Handle exception when no index is provided to /proposal/get endpoint [Issue 1841](https://github.com/IntersectMBO/govtool/issues/1841) -- Fix displaying vote pill on voted on cards -- Fix incorrect link to learn more about direct voters [Issue 2647](https://github.com/IntersectMBO/govtool/issues/2647) -- Fix missing No DRep found message on DRep Directory [Issue 2889](https://github.com/IntersectMBO/govtool/issues/2889) -- Fix displaying givenName placeholder instead of actual value on DRep card [Issue 2888](https://github.com/IntersectMBO/govtool/issues/2888) -- Fix executing insertBefore on undefined node [Issue 2878](https://github.com/IntersectMBO/govtool/issues/2878) - -### Changed - -- Change votes representation on Governance Actions [Issue 2880](https://github.com/IntersectMBO/govtool/issues/2880) -- Change vote rationale character limit to 10000 [Issue 2891](https://github.com/IntersectMBO/govtool/issues/2891) -- Move ratification threshold label below the voter type [Issue 2893](https://github.com/IntersectMBO/govtool/issues/2893) - -### Removed - -- Remove redundant sentry reports on handled wallet exceptions [Issue 2680](https://github.com/IntersectMBO/govtool/issues/2680) - -## [v2.0.10](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.10) 2025-01-29 - -### Added - -- Add exception handler on stake key voting power query execution [Issue 2757](https://github.com/IntersectMBO/govtool/issues/2757) -- Add script hash to new consitution governance action [Issue 2745](https://github.com/IntersectMBO/govtool/issues/2745) - -### Fixed - -- - -### Changed - -- Bump @intersect.mbo/pdf-ui to v0.5.11 - -### Removed - -- - -## [v2.0.9](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.9) 2025-01-24 - -### Added - -- - -### Fixed - -- Fix opening IPFS links [Issue 2711](https://github.com/IntersectMBO/govtool/issues/2711) - -### Changed - -- Change labelling of governance action metadata anchor -- Change labelling and order of new constitution governance action details - -### Removed - -- - -## [v2.0.8](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.8) 2025-01-23 - -### Added - -- Add share DRep button to every DRep instead of only our own [Issue 2686](https://github.com/IntersectMBO/govtool/issues/2686) -- Show metadata anchor in Governance Action Details [Issue 2178](https://github.com/IntersectMBO/govtool/issues/2178) -- Handle unexpected drep info query result [Issue 2676](https://github.com/IntersectMBO/govtool/issues/2676) - -### Fixed - -- Fix usage of trim on missing label -- Fix blank screen when registering as a DRep [Issue 2408](https://github.com/IntersectMBO/govtool/issues/2408) -- Fix type mismatch between sql and haskell code for stake key address -- Handle missing api key exception [Issue 2683](https://github.com/IntersectMBO/govtool/issues/2683) - -### Changed - -- Bump cardano-node to 10.1.4 -- Make CIP-129 governance identifiers the default ones - -### Removed - -- Remove logging to sentry for DRep registration transaction [Issue 2681](https://github.com/IntersectMBO/govtool/issues/2681) -- Remove logging to sentry when delegation transaction fails [Issue 2682](https://github.com/IntersectMBO/govtool/issues/2682) - -## [v2.0.7](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.7) 2025-01-20 - -### Added - -- - -### Fixed - -- Fix calculating DRep activity -- Fix fetching Governance Actions being navigated from dashboard - -### Changed - -- Bump @intersect.mbo/pdf-ui to v0.5.7 - -### Removed - -- - -## [v2.0.6](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.6) 2025-01-16 - -### Added - -- Add support for base64 encoded images [Issue 2633](https://github.com/IntersectMBO/govtool/issues/2633) -- Add searching for metadata [Issue 2634](https://github.com/IntersectMBO/govtool/issues/2634) -- Allow delegation to inactive DRep [Issue 2589](https://github.com/IntersectMBO/govtool/issues/2589) - -### Fixed - -- Fix searching by full DRep IDs on wrong prefix cut [Issue 2639](https://github.com/IntersectMBO/govtool/issues/2639) -- Trim whitespace from search bar input [Issue 2472](https://github.com/IntersectMBO/govtool/issues/2472) - -### Changed - -- - -### Removed - -- - -## [v2.0.5](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.5) 2025-01-10 - -### Added - -- - -### Fixed - -- Fix counting submitted votes [Issue 2609](https://github.com/IntersectMBO/govtool/issues/2609) -- Fix opening relative paths in external links -- Fix passing random sorting to governance actions on disconnected wallet - -### Changed - -- Bump @intersect.mbo/pdf-ui to v0.5.6 - -### Removed - -- - ## [v2.0.4](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.4) 2025-01-07 ### Added diff --git a/govtool/backend/Dockerfile b/govtool/backend/Dockerfile index 86b27282b..5dbddfbff 100644 --- a/govtool/backend/Dockerfile +++ b/govtool/backend/Dockerfile @@ -4,4 +4,4 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.19/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.20/x/vva-be/build/vva-be/vva-be /usr/local/bin diff --git a/govtool/backend/Dockerfile.qovery b/govtool/backend/Dockerfile.qovery index 57d62b307..e920201fc 100644 --- a/govtool/backend/Dockerfile.qovery +++ b/govtool/backend/Dockerfile.qovery @@ -4,7 +4,7 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.19/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.20/x/vva-be/build/vva-be/vva-be /usr/local/bin # Expose the necessary port EXPOSE 9876 diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal index 1c4e91fa6..745f3bc87 100644 --- a/govtool/backend/vva-be.cabal +++ b/govtool/backend/vva-be.cabal @@ -1,6 +1,6 @@ cabal-version: 3.6 name: vva-be -version: 2.0.19 +version: 2.0.20 -- A short (one-line) description of the package. -- synopsis: diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 8aa3981da..8c3cb8244 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -1,21 +1,21 @@ { "name": "@govtool/frontend", - "version": "2.0.19", + "version": "2.0.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@govtool/frontend", - "version": "2.0.19", + "version": "2.0.20", "hasInstallScript": true, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "1.3.0", + "@intersect.mbo/govtool-outcomes-pillar-ui": "1.4.0", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "0.7.0-beta-16", + "@intersect.mbo/pdf-ui": "0.7.0-beta-19", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", @@ -43,6 +43,7 @@ "react-query": "^3.39.3", "react-router-dom": "^6.13.0", "rehype-katex": "^7.0.1", + "remark-breaks": "^4.0.0", "remark-math": "^6.0.0", "storybook-addon-manual-mocks": "^1.0.3", "storybook-addon-module-mock": "^1.3.4", @@ -3389,9 +3390,9 @@ } }, "node_modules/@intersect.mbo/govtool-outcomes-pillar-ui": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.3.0.tgz", - "integrity": "sha512-6+H+QG8kyM2UUEycNsjrF1K5+UGUw6+wy7gRxlyOtFjIrZ9CUdTfwwyD1hrh+g55awZ4t+EmQgYB00An0iXOag==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.4.0.tgz", + "integrity": "sha512-iSg6FzeUW4F3KSQDcHCytggCG68Q4OKUzQa8yNMSPVAuM6w5I6LhaqEYRO8x02G8WPyE+8W+Q9m3SCFea59osg==", "license": "ISC", "dependencies": { "@fontsource/poppins": "^5.0.14", @@ -3423,9 +3424,9 @@ "license": "ISC" }, "node_modules/@intersect.mbo/pdf-ui": { - "version": "0.7.0-beta-16", - "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-16.tgz", - "integrity": "sha512-2I7BSg+5FDGhlvwLdZk0teVhtPMRQ1uDWKQiwobIF41cM0b+iT2CJMDL5MJk6tsM2/2JBbOEev9VrsHSanrCVA==", + "version": "0.7.0-beta-19", + "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-19.tgz", + "integrity": "sha512-7VxTZos1biINze2eijXX1qJ5zSlAEIbtbCbH12+9NPSfkRFHty4pZphgCka0/res/mI/wU6TNcweHnomjyJ2vQ==", "dependencies": { "@emurgo/cardano-serialization-lib-asmjs": "^12.0.0-beta.2", "@fontsource/poppins": "^5.0.14", @@ -20740,6 +20741,32 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -20843,6 +20870,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", @@ -27052,6 +27092,20 @@ "node": ">=4" } }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-math": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index e64dd9cd9..d4c6527cd 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -1,7 +1,7 @@ { "name": "@govtool/frontend", "private": true, - "version": "2.0.19", + "version": "2.0.20", "type": "module", "scripts": { "build": "vite build", @@ -27,9 +27,9 @@ "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "1.3.0", + "@intersect.mbo/govtool-outcomes-pillar-ui": "1.4.0", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "0.7.0-beta-16", + "@intersect.mbo/pdf-ui": "0.7.0-beta-19", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", @@ -57,6 +57,7 @@ "react-query": "^3.39.3", "react-router-dom": "^6.13.0", "rehype-katex": "^7.0.1", + "remark-breaks": "^4.0.0", "remark-math": "^6.0.0", "storybook-addon-manual-mocks": "^1.0.3", "storybook-addon-module-mock": "^1.3.4", diff --git a/govtool/frontend/src/components/molecules/DRepDataForm.tsx b/govtool/frontend/src/components/molecules/DRepDataForm.tsx index 3fda92496..6b2a65506 100644 --- a/govtool/frontend/src/components/molecules/DRepDataForm.tsx +++ b/govtool/frontend/src/components/molecules/DRepDataForm.tsx @@ -14,7 +14,7 @@ import { Rules } from "@consts"; import { useScreenDimension, useTranslation } from "@hooks"; import { DRepDataFormValues } from "@/types/dRep"; -import { ControlledField } from "../organisms"; +import { ControlledField, UncontrolledImageInput } from "../organisms"; const MAX_NUMBER_OF_LINKS = 7; @@ -106,9 +106,9 @@ export const DRepDataForm = ({ control, errors, register, watch }: Props) => { title={t("forms.dRepData.image")} subtitle={t("forms.dRepData.imageHelpfulText")} /> - diff --git a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx index 46e0ff880..a7cdebf6a 100644 --- a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx +++ b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx @@ -1,4 +1,4 @@ -import { Avatar, Box, SxProps } from "@mui/material"; +import { Avatar, Box, Skeleton, SxProps } from "@mui/material"; import { Typography } from "@atoms"; import { MetadataValidationStatus } from "@models"; @@ -11,10 +11,11 @@ import { Share } from "./Share"; import { useScreenDimension } from "@/hooks"; type DataMissingHeaderProps = { - isDataMissing: MetadataValidationStatus | null; + isDataMissing?: MetadataValidationStatus; title?: string; titleStyle?: SxProps; isDRep?: boolean; + isValidating?: boolean; image?: string | null; shareLink?: string; }; @@ -23,6 +24,7 @@ export const DataMissingHeader = ({ title, isDataMissing, titleStyle, + isValidating, isDRep, image, shareLink, @@ -53,39 +55,49 @@ export const DataMissingHeader = ({ display: "flex", }} > - {isDRep && ( - + ) : ( + + ))} + {isValidating ? ( + + ) : ( + + {(isDataMissing && + getMetadataDataMissingStatusTranslation(isDataMissing)) || + title} + )} - - {(isDataMissing && - getMetadataDataMissingStatusTranslation( - isDataMissing as MetadataValidationStatus, - )) || - title} - {screenWidth >= 1020 && ( - + )} ); diff --git a/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx index eb3d170aa..dfdec6678 100644 --- a/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx +++ b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx @@ -1,4 +1,4 @@ -import { Box, Link, SxProps } from "@mui/material"; +import { Box, Link, Skeleton, SxProps } from "@mui/material"; import { Typography } from "@atoms"; import { useTranslation } from "@hooks"; @@ -9,12 +9,14 @@ import { LINKS } from "@/consts/links"; export const DataMissingInfoBox = ({ isDataMissing, isInProgress, + isValidating, isSubmitted, isDrep = false, sx, }: { - isDataMissing: MetadataValidationStatus | null; + isDataMissing?: MetadataValidationStatus; isInProgress?: boolean; + isValidating?: boolean; isSubmitted?: boolean; isDrep?: boolean; sx?: SxProps; @@ -63,36 +65,58 @@ export const DataMissingInfoBox = ({ ...sx, }} > - - {gaMetadataErrorMessage} - - - {gaMetadataErrorDescription} - - openInNewTab(LINKS.DREP_ERROR_CONDITIONS)} - sx={{ - fontFamily: "Poppins", - fontSize: "16px", - lineHeight: "24px", - cursor: "pointer", - }} - > - {t("learnMore")} - + {isValidating ? ( + + ) : ( + + {gaMetadataErrorMessage} + + )} + {isValidating ? ( + + ) : ( + + {gaMetadataErrorDescription} + + )} + {isValidating ? ( + + ) : ( + openInNewTab(LINKS.DREP_ERROR_CONDITIONS)} + sx={{ + fontFamily: "Poppins", + fontSize: "16px", + lineHeight: "24px", + cursor: "pointer", + }} + > + {t("learnMore")} + + )} ) : null; }; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx index 68d880aed..47c98f6be 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { Box } from "@mui/material"; +import { Box, Skeleton } from "@mui/material"; import { Button } from "@atoms"; import { @@ -8,7 +8,6 @@ import { GovernanceActionCardStatePill, GovernanceActionsDatesBox, } from "@molecules"; - import { useScreenDimension, useTranslation } from "@hooks"; import { encodeCIP129Identifier, @@ -23,8 +22,6 @@ type ActionTypeProps = Omit< | "yesVotes" | "noVotes" | "abstainVotes" - | "metadataHash" - | "url" | "id" | "details" | "rationale" @@ -32,24 +29,25 @@ type ActionTypeProps = Omit< > & { onClick?: () => void; inProgress?: boolean; + isValidating?: boolean; + metadataStatus?: MetadataValidationStatus; }; -export const GovernanceActionCard: FC = ({ ...props }) => { - const { - abstract, - type, - inProgress = false, - expiryDate, - expiryEpochNo, - onClick, - createdDate, - createdEpochNo, - txHash, - index, - metadataStatus, - metadataValid, - title, - } = props; +export const GovernanceActionCard: FC = ({ + abstract, + type, + inProgress = false, + expiryDate, + expiryEpochNo, + onClick, + createdDate, + createdEpochNo, + txHash, + index, + title, + isValidating, + metadataStatus, +}) => { const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); @@ -71,10 +69,10 @@ export const GovernanceActionCard: FC = ({ ...props }) => { justifyContent: "space-between", boxShadow: "0px 4px 15px 0px #DDE3F5", borderRadius: "20px", - backgroundColor: !metadataValid + backgroundColor: metadataStatus ? "rgba(251, 235, 235, 0.50)" : "rgba(255, 255, 255, 0.3)", - ...(!metadataValid && { + ...(!!metadataStatus && { border: "1px solid #F6D5D5", }), ...(inProgress && { @@ -92,8 +90,9 @@ export const GovernanceActionCard: FC = ({ ...props }) => { - {!metadataStatus && ( + {!!metadataStatus && ( = ({ ...props }) => { dataTestId="governance-action-abstract" isSliderCard isMarkdown + isValidating={isValidating} /> )} = ({ ...props }) => { textVariant="pill" dataTestId={`${getProposalTypeNoEmptySpaces(type)}-type`} isSliderCard + isValidating={isValidating} /> = ({ ...props }) => { expiryEpochNo={expiryEpochNo} createdEpochNo={createdEpochNo} isSliderCard + isValidating={isValidating} /> = ({ ...props }) => { dataTestId={`${cip129GovernanceActionId}-id`} isCopyButton isSliderCard + isValidating={isValidating} /> = ({ ...props }) => { isCopyButton isSliderCard isSemiTransparent + isValidating={isValidating} /> = ({ ...props }) => { bgcolor: "white", }} > - + {isValidating ? ( + + ) : ( + + )} ); diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx index 9084ffa0c..bbd5a2382 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx @@ -1,9 +1,10 @@ import { PropsWithChildren } from "react"; -import { Box } from "@mui/material"; +import { Box, Skeleton } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import Markdown from "react-markdown"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; +import remarkBreaks from "remark-breaks"; import "katex/dist/katex.min.css"; import { Typography, Tooltip, CopyButton, TooltipProps } from "@atoms"; @@ -19,6 +20,7 @@ type BaseProps = { tooltipProps?: Omit; marginBottom?: number; isSemiTransparent?: boolean; + isValidating?: boolean; }; type VariantProps = BaseProps & { @@ -40,6 +42,7 @@ export const GovernanceActionCardElement = ({ marginBottom, isMarkdown = false, isSemiTransparent = false, + isValidating = false, }: VariantProps) => { const { openModal } = useModal(); @@ -115,6 +118,7 @@ export const GovernanceActionCardElement = ({ fontWeight: 400, lineHeight: "24px", maxWidth: "auto", + whiteSpace: "pre-wrap", }} > {children} @@ -131,7 +135,7 @@ export const GovernanceActionCardElement = ({ const renderMarkdown = () => ( {text.toString()} @@ -171,37 +175,45 @@ export const GovernanceActionCardElement = ({ overflow={isSliderCard ? "hidden" : "visible"} > - - {label} - + {isValidating ? ( + + ) : ( + + {label} + + )} {renderTooltip()} - - {textVariant === "pill" - ? renderPillText() - : isMarkdown && !isSliderCard - ? renderMarkdown() - : renderStandardText()} - {renderCopyButton()} - {renderLinkButton()} - + {isValidating ? ( + + ) : ( + + {textVariant === "pill" + ? renderPillText() + : isMarkdown && !isSliderCard + ? renderMarkdown() + : renderStandardText()} + {renderCopyButton()} + {renderLinkButton()} + + )} ); }; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx index 7666296fc..49d6ae1e5 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx @@ -1,4 +1,4 @@ -import { Box } from "@mui/material"; +import { Box, Skeleton } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Tooltip, Typography } from "@atoms"; @@ -8,12 +8,14 @@ import { MetadataValidationStatus } from "@/models"; type GovernanceActionCardHeaderProps = { title?: string; - isDataMissing: MetadataValidationStatus | null; + isDataMissing?: MetadataValidationStatus; + isValidating?: boolean; }; export const GovernanceActionCardHeader = ({ title, isDataMissing, + isValidating, }: GovernanceActionCardHeaderProps) => { const { t } = useTranslation(); @@ -27,24 +29,28 @@ export const GovernanceActionCardHeader = ({ }} data-testid="governance-action-card-header" > - - {(isDataMissing && - getMetadataDataMissingStatusTranslation( - isDataMissing as MetadataValidationStatus, - )) || - title} - + {isValidating ? ( + + ) : ( + + {(isDataMissing && + getMetadataDataMissingStatusTranslation( + isDataMissing as MetadataValidationStatus, + )) || + title} + + )} {isDataMissing && typeof isDataMissing === "string" && ( { const { t } = useTranslation(); const { screenWidth } = useScreenDimension(); @@ -38,98 +40,104 @@ export const GovernanceActionsDatesBox = ({ mb: isSliderCard ? "20px" : "32px", }} > - - - , - , - ]} - /> - - - + ) : ( + <> + - - - - - , - , - ]} - /> - - - + + , + , + ]} + /> + + + + + + - - + > + + , + , + ]} + /> + + + + + + + )} ); }; diff --git a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx index 6a25e7cb6..59d84bfc9 100644 --- a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx @@ -22,9 +22,16 @@ import { VotedProposal } from "@/models"; type Props = { votedProposal: VotedProposal; inProgress?: boolean; + isValidating?: boolean; + metadataStatus?: MetadataValidationStatus; }; -export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { +export const GovernanceVotedOnCard = ({ + votedProposal, + inProgress, + isValidating, + metadataStatus, +}: Props) => { const navigate = useNavigate(); const { proposal, vote } = votedProposal; const { @@ -34,8 +41,6 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { expiryDate, expiryEpochNo, index, - metadataStatus, - metadataValid, txHash, type, title, @@ -62,13 +67,13 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { justifyContent: "space-between", boxShadow: "0px 4px 15px 0px #DDE3F5", borderRadius: "20px", - backgroundColor: !metadataValid + backgroundColor: !metadataStatus ? "rgba(251, 235, 235, 0.50)" : "rgba(255, 255, 255, 0.3)", // TODO: To decide if voted on cards can be actually in progress border: inProgress ? "1px solid #FFCBAD" - : !metadataValid + : !metadataStatus ? "1px solid #F6D5D5" : "1px solid #C0E4BA", }} @@ -85,6 +90,7 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { { textVariant="twoLines" dataTestId="governance-action-abstract" isSliderCard + isValidating={isValidating} /> { textVariant="pill" dataTestId={`${getProposalTypeNoEmptySpaces(type)}-type`} isSliderCard + isValidating={isValidating} /> { expiryEpochNo={expiryEpochNo} createdEpochNo={createdEpochNo} isSliderCard + isValidating={isValidating} /> { dataTestId={`${cip129GovernanceActionId}-id`} isCopyButton isSliderCard + isValidating={isValidating} /> { isCopyButton isSliderCard isSemiTransparent + isValidating={isValidating} /> {vote && ( ( ({ control, name, errors, rules, ...props }, ref) => { const errorMessage = get(errors, name)?.message as string; - const renderInput = useCallback( ({ field }: RenderInputProps) => ( (); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + setIsValidating(true); + + const { status: validationStatus } = await validateMetadata({ + standard: MetadataStandard.CIP119, + url: url ?? "", + hash: metadataHash ?? "", + }); + + metadataStatus.current = validationStatus; + setIsValidating(false); + }; + validate(); + }, []); return ( - + {isValidating ? ( + + ) : ( + + )} - - {metadataStatus - ? getMetadataDataMissingStatusTranslation(metadataStatus) - : ellipsizeText(givenName ?? "", 25)} - + {isValidating ? ( + + ) : ( + + {metadataStatus.current + ? getMetadataDataMissingStatusTranslation( + metadataStatus.current, + ) + : ellipsizeText(givenName ?? "", 25)} + + )} - { - navigator.clipboard.writeText(cip129Identifier); - addSuccessAlert(t("alerts.copiedToClipboard")); - e.stopPropagation(); - }} - sx={{ - gap: 1, - width: "250px", - maxWidth: { - xxs: "200px", - xs: "100%", - }, - "&:hover": { - opacity: 0.6, - transition: "opacity 0.3s", - }, - display: "flex", - flexDirection: "row", - }} - > - + ) : ( + { + navigator.clipboard.writeText(cip129Identifier); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }} + sx={{ + gap: 1, + width: "250px", + maxWidth: { + xxs: "200px", + xs: "100%", + }, + "&:hover": { + opacity: 0.6, + transition: "opacity 0.3s", + }, + display: "flex", + flexDirection: "row", + }} > - {cip129Identifier} - - - - { - navigator.clipboard.writeText(view); - addSuccessAlert(t("alerts.copiedToClipboard")); - e.stopPropagation(); - }} - sx={{ - gap: 1, - width: "250px", - maxWidth: { - xxs: "200px", - xs: "100%", - }, - "&:hover": { - opacity: 0.6, - transition: "opacity 0.3s", - }, - }} - > - - (CIP-105){" "} - {view} + {cip129Identifier} + + + + )} + {isValidating ? ( + + ) : ( + { + navigator.clipboard.writeText(view); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }} + sx={{ + gap: 1, + width: "250px", + maxWidth: { + xxs: "200px", + xs: "100%", + }, + "&:hover": { + opacity: 0.6, + transition: "opacity 0.3s", + }, + }} + > + + (CIP-105){" "} + + {view} + - - - + + + )} @@ -233,20 +281,28 @@ export const DRepCard = ({ flexDirection: "column", }} > - - {t("votingPower")} - - - ₳ {correctDRepDirectoryFormat(votingPower)} - + {isValidating ? ( + + ) : ( + + {t("votingPower")} + + )} + {isValidating ? ( + + ) : ( + + ₳ {correctDRepDirectoryFormat(votingPower)} + + )} ({ borderColor: palette.lightBlue })} /> - - {t("status")} - + {isValidating ? ( + + ) : ( + + {t("status")} + + )} - + {isValidating ? ( + + ) : ( + + )} {isMe && ( - {type === "DRep" && ( - - )} + {type === "DRep" && + (isValidating ? ( + + ) : ( + + ))} {["Active", "Inactive"].includes(status) && isConnected && onDelegate && !isMyDrep && - !isInProgress && ( + !isInProgress && + (isValidating ? ( + + ) : ( - )} - {["Active", "Inactive"].includes(status) && !isConnected && ( - - )} + ))} + {["Active", "Inactive"].includes(status) && + !isConnected && + (isValidating ? ( + + ) : ( + + ))} diff --git a/govtool/frontend/src/components/organisms/DRepDetailsCard.tsx b/govtool/frontend/src/components/organisms/DRepDetailsCard.tsx index 0a0798d60..a220bbbb0 100644 --- a/govtool/frontend/src/components/organisms/DRepDetailsCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepDetailsCard.tsx @@ -1,5 +1,5 @@ -import { PropsWithChildren } from "react"; -import { Box, ButtonBase, Link } from "@mui/material"; +import { PropsWithChildren, useEffect, useRef, useState } from "react"; +import { Box, ButtonBase, Link, Skeleton } from "@mui/material"; import { Button, ExternalModalButton, StatusPill, Typography } from "@atoms"; import { ICONS, PATHS } from "@consts"; @@ -11,8 +11,9 @@ import { encodeCIP129Identifier, testIdFromLabel, } from "@utils"; -import { DRepData } from "@/models"; +import { DRepData, MetadataStandard } from "@/models"; import { DRepDetailsCardHeader } from "./DRepDetailsCardHeader"; +import { useValidateMutation } from "@/hooks/mutations"; type DRepDetailsProps = { dRepData: DRepData; @@ -36,7 +37,6 @@ export const DRepDetailsCard = ({ const { delegate, isDelegating } = useDelegateTodRep(); const { - metadataStatus, motivations, objectives, paymentAddress, @@ -51,6 +51,26 @@ export const DRepDetailsCard = ({ metadataHash, } = dRepData; + const [isValidating, setIsValidating] = useState(false); + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + setIsValidating(true); + + const { status: metadataValidationStatus } = await validateMetadata({ + standard: MetadataStandard.CIP119, + url: url ?? "", + hash: metadataHash ?? "", + }); + + metadataStatus.current = metadataValidationStatus; + setIsValidating(false); + }; + validate(); + }, []); + const groupedReferences = references?.reduce>( (acc, reference) => { const type = reference["@type"]; @@ -96,16 +116,19 @@ export const DRepDetailsCard = ({ dRepData={dRepData} isMe={isMe} isMyDrep={isMyDrep} + isValidating={isValidating} + metadataStatus={metadataStatus.current} /> {/* ERROR MESSAGES */} - {metadataStatus && ( + {metadataStatus.current && ( )} - {metadataStatus && !!url && ( + {metadataStatus.current && !!url && ( )} {/* ERROR MESSAGES END */} - + - + delegate(dRepData.drepId)} - size="extraLarge" - sx={{ width: "100%", maxWidth: screenWidth < 1024 ? "100%" : 286 }} - variant="contained" - > - {t("delegate")} - - )} - {!isConnected && ["Active", "Inactive"].includes(status) && ( - - )} + {!isValidating && + isConnected && + ["Active", "Inactive"].includes(status) && + !isMyDrep && ( + + )} + {!isValidating && + !isConnected && + ["Active", "Inactive"].includes(status) && ( + + )} {/* BUTTONS END */} {/* CIP-119 DATA */} - {!metadataStatus && ( + {!metadataStatus.current && ( <> { if (!children && !text) return null; const dataTestIdInfoItemCategoryPrefix = "info-item"; @@ -319,30 +359,49 @@ const DRepDetailsInfoItem = ({ }, }} > - - + ) : ( + - {label} - - + + {label} + + + )}
- {text && ( - - {text} - + {isValidating ? ( + + ) : ( + <> + {text && ( + + {text} + + )} + {children} + )} - {children}
); diff --git a/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx b/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx index 0ac2edc94..992ef2afc 100644 --- a/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx +++ b/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx @@ -11,18 +11,22 @@ type DRepDetailsProps = { dRepData: DRepData; isMe?: boolean; isMyDrep?: boolean; + isValidating?: boolean; + metadataStatus?: MetadataValidationStatus; }; export const DRepDetailsCardHeader = ({ dRepData, isMe, isMyDrep, + isValidating, + metadataStatus, }: DRepDetailsProps) => { const { t } = useTranslation(); const navigate = useNavigate(); const { screenWidth } = useScreenDimension(); - const { givenName, metadataStatus, image } = dRepData; + const { givenName, image } = dRepData; const navigateToEditDRep = () => { navigate(PATHS.editDrepMetadata, { @@ -84,6 +88,7 @@ export const DRepDetailsCardHeader = ({ image={image} isDataMissing={metadataStatus} titleStyle={{ wordBreak: "break-word", whiteSpace: "wrap" }} + isValidating={isValidating} /> ); diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx index 589a5d8df..0d81759b8 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx @@ -1,11 +1,11 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; import { IMAGES, PATHS } from "@consts"; import { PendingTransaction } from "@context"; import { useGetDRepDetailsQuery, useTranslation } from "@hooks"; -import { CurrentDelegation, VoterInfo } from "@models"; +import { CurrentDelegation, MetadataStandard, VoterInfo } from "@models"; import { DashboardActionCard, DashboardActionCardProps, @@ -21,6 +21,7 @@ import { AutomatedVotingOptionDelegationId, } from "@/types/automatedVotingOptions"; import { LINKS } from "@/consts/links"; +import { useValidateMutation } from "@/hooks/mutations"; type DelegateDashboardCardProps = { currentDelegation: CurrentDelegation; @@ -43,6 +44,22 @@ export const DelegateDashboardCard = ({ delegateTx?.resourceId ?? currentDelegation?.dRepHash, ); + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + const { status } = await validateMetadata({ + standard: MetadataStandard.CIP119, + url: myDRepDelegationData?.url ?? "", + hash: myDRepDelegationData?.metadataHash ?? "", + }); + + metadataStatus.current = status; + }; + validate(); + }, []); + const learnMoreButton = { children: t("learnMore"), dataTestId: "delegate-learn-more-button", @@ -117,7 +134,7 @@ export const DelegateDashboardCard = ({ navigate( PATHS.dashboardDRepDirectoryDRep.replace( ":dRepId", - displayedDelegationId || "", + displayedDelegationId ?? "", ), { state: { enteredFromWithinApp: true } }, ), @@ -153,10 +170,8 @@ export const DelegateDashboardCard = ({ drepName={ isLoading ? "Loading..." - : myDRepDelegationData?.metadataStatus - ? getMetadataDataMissingStatusTranslation( - myDRepDelegationData.metadataStatus, - ) + : metadataStatus.current + ? getMetadataDataMissingStatusTranslation(metadataStatus.current) : myDRepDelegationData?.givenName ?? "" } dRepId={displayedDelegationId} diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx index d5ffa61c2..5db1bd7cc 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNavigate, useLocation, @@ -19,7 +19,8 @@ import { import { getFullGovActionId, getShortenedGovActionId } from "@utils"; import { GovernanceActionDetailsCard } from "@organisms"; import { Breadcrumbs } from "@molecules"; -import { ProposalData, ProposalVote } from "@/models"; +import { MetadataStandard, ProposalData, ProposalVote } from "@/models"; +import { useValidateMutation } from "@/hooks/mutations"; type DashboardGovernanceActionDetailsState = { proposal?: ProposalData; @@ -51,6 +52,26 @@ export const DashboardGovernanceActionDetails = () => { const proposal = (data ?? state)?.proposal; const vote = (data ?? state)?.vote; + const [isValidating, setIsValidating] = useState(false); + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + setIsValidating(true); + + const { status } = await validateMetadata({ + standard: MetadataStandard.CIP108, + url: proposal?.url ?? "", + hash: proposal?.metadataHash ?? "", + }); + + metadataStatus.current = status; + setIsValidating(false); + }; + validate(); + }, []); + useEffect(() => { const isProposalNotFound = (error as AxiosError)?.response?.data === @@ -75,7 +96,7 @@ export const DashboardGovernanceActionDetails = () => { elementOne={t("govActions.title")} elementOnePath={PATHS.dashboardGovernanceActions} elementTwo={proposal?.title ?? ""} - isDataMissing={proposal?.metadataStatus ?? null} + isDataMissing={metadataStatus?.current ?? null} /> { }} onClick={() => navigate( - state && state.openedFromCategoryPage + state?.openedFromCategoryPage ? generatePath(PATHS.dashboardGovernanceActionsCategory, { category: state?.proposal?.type, }) @@ -127,12 +148,13 @@ export const DashboardGovernanceActionDetails = () => { isVoter={ voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter } - isDataMissing={proposal.metadataStatus ?? null} + isDataMissing={metadataStatus?.current} isInProgress={ pendingTransaction.vote?.resourceId === fullProposalId?.replace("#", "") } isDashboard + isValidating={isValidating} /> ) : ( diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx index 552f63061..465be1aff 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx @@ -3,8 +3,7 @@ import { Box, Typography, CircularProgress } from "@mui/material"; import { useCardano } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; -import { GovernanceVotedOnCard } from "@molecules"; -import { Slider } from "@organisms"; +import { Slider, ValidatedGovernanceVotedOnCard } from "@organisms"; import { getFullGovActionId, getProposalTypeLabel } from "@utils"; import { VotedProposal } from "@/models"; @@ -74,7 +73,7 @@ export const DashboardGovernanceActionsVotedOn = ({ key={`${action?.proposal.id}${action.vote?.vote}`} style={{ overflow: "visible", width: "auto" }} > - { @@ -63,6 +65,7 @@ export const GovernanceActionDetailsCard = ({ isOneColumn={isOneColumn} isSubmitted={isVoteSubmitted} proposal={proposal} + isValidating={isValidating} /> ( type GovernanceActionDetailsCardDataProps = { isDashboard?: boolean; - isDataMissing: MetadataValidationStatus | null; + isDataMissing?: MetadataValidationStatus; isInProgress?: boolean; isOneColumn: boolean; isSubmitted?: boolean; + isValidating?: boolean; proposal: ProposalData; }; @@ -72,6 +73,7 @@ export const GovernanceActionDetailsCardData = ({ isInProgress, isOneColumn, isSubmitted, + isValidating, proposal: { abstract, createdDate, @@ -243,11 +245,13 @@ export const GovernanceActionDetailsCardData = ({ > @@ -256,25 +260,31 @@ export const GovernanceActionDetailsCardData = ({ text={label} textVariant="pill" dataTestId={`${getProposalTypeNoEmptySpaces(label)}-type`} + isValidating={isValidating} /> - {isDataMissing && ( - - )} + {isDataMissing && + (isValidating ? ( + + ) : ( + + ))} 1600 ? "longText" : "oneLine"} + isValidating={isValidating} /> 1600 ? "longText" : "oneLine"} isSemiTransparent + isValidating={isValidating} /> {tabs?.length === 1 ? ( @@ -322,7 +333,8 @@ export const GovernanceActionDetailsCardData = ({ ))} )} - {details && + {!isValidating && + details && type === GovernanceActionType.TreasuryWithdrawals && Array.isArray(details) && details.map((withdrawal) => ( @@ -333,7 +345,7 @@ export const GovernanceActionDetailsCardData = ({ /> ))} {/* NewConstitution metadata hash and url is visible in details tab */} - {type !== GovernanceActionType.NewConstitution && ( + {!isValidating && type !== GovernanceActionType.NewConstitution && ( <> 1600 ? "longText" : "oneLine"} dataTestId="anchor-url" isLinkButton + isValidating={isValidating} /> 1600 ? "longText" : "oneLine"} dataTestId="anchor-hash" isCopyButton + isValidating={isValidating} /> )} @@ -361,7 +375,10 @@ const ReasoningTabContent = ({ abstract, motivation, rationale, -}: Pick) => { + isValidating, +}: Pick & { + isValidating?: boolean; +}) => { const { t } = useTranslation(); return ( @@ -372,6 +389,7 @@ const ReasoningTabContent = ({ textVariant="longText" dataTestId="abstract" isMarkdown + isValidating={isValidating} /> ); @@ -394,7 +414,9 @@ const ReasoningTabContent = ({ const HardforkDetailsTabContent = ({ details, prevGovActionId, -}: Pick & { prevGovActionId: string | null }) => { +}: Pick & { + prevGovActionId: string | null; +}) => { const { epochParams } = useAppContext(); const { t } = useTranslation(); diff --git a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx index 3a603668b..768bdc609 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx @@ -6,9 +6,8 @@ import { PATHS } from "@consts"; import { useCardano } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; import { ProposalData } from "@models"; -import { GovernanceActionCard } from "@molecules"; import { getProposalTypeTitle, getFullGovActionId } from "@utils"; -import { Slider } from "@organisms"; +import { Slider, ValidatedGovernanceActionCard } from "@organisms"; type GovernanceActionsToVoteProps = { filters: string[]; @@ -58,7 +57,7 @@ export const GovernanceActionsToVote = ({ width: "auto", }} > - { + const { + field: { onChange }, + fieldState, + } = useController({ + name, + control, + rules, + }); + + const inputRef = useRef(null); + + return ( +
+ { + const value = inputRef.current?.value; + if (value !== undefined) { + onChange(value); + } + }} + placeholder={placeholder} + data-testid={dataTestId} + defaultValue="" + style={{ + width: "100%", + fontSize: "inherit", + padding: "8px 16px", + borderRadius: "50px", + height: "50px", + border: "1px solid", + borderColor: fieldState.error?.message ? "red" : "#6F99FF", + backgroundColor: fieldState.error?.message ? "#FAEAEB" : "white", + boxSizing: "border-box", + margin: 0, + display: "block", + minWidth: 0, + lineHeight: "1.4375em", + WebkitTapHighlightColor: "transparent", + animationDuration: "10ms", + WebkitAnimationDuration: "10ms", + animationName: "mui-auto-fill-cancel", + WebkitAnimationName: "mui-auto-fill-cancel", + outline: "none", + }} + /> + {fieldState.error && ( + + )} +
+ ); +}; diff --git a/govtool/frontend/src/components/organisms/ValidatedGovernanceActionCard.tsx b/govtool/frontend/src/components/organisms/ValidatedGovernanceActionCard.tsx new file mode 100644 index 000000000..df6a8991a --- /dev/null +++ b/govtool/frontend/src/components/organisms/ValidatedGovernanceActionCard.tsx @@ -0,0 +1,54 @@ +import { useState, useRef, useEffect } from "react"; + +import { useValidateMutation } from "@/hooks/mutations"; +import { MetadataStandard, ProposalData } from "@/models"; +import { GovernanceActionCard } from "../molecules"; + +type ActionTypeProps = Omit< + ProposalData, + | "yesVotes" + | "noVotes" + | "abstainVotes" + | "id" + | "details" + | "rationale" + | "motivation" +> & { + onClick?: () => void; + inProgress?: boolean; +}; +export const ValidatedGovernanceActionCard = ({ + url, + metadataHash, + ...props +}: ActionTypeProps) => { + const [isValidating, setIsValidating] = useState(false); + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + setIsValidating(true); + + const { status } = await validateMetadata({ + standard: MetadataStandard.CIP108, + url: url ?? "", + hash: metadataHash ?? "", + }); + + metadataStatus.current = status; + setIsValidating(false); + }; + validate(); + }, []); + + return ( + + ); +}; diff --git a/govtool/frontend/src/components/organisms/ValidatedGovernanceVotedOnCard.tsx b/govtool/frontend/src/components/organisms/ValidatedGovernanceVotedOnCard.tsx new file mode 100644 index 000000000..ec551d4c7 --- /dev/null +++ b/govtool/frontend/src/components/organisms/ValidatedGovernanceVotedOnCard.tsx @@ -0,0 +1,43 @@ +import { useState, useRef, useEffect } from "react"; + +import { useValidateMutation } from "@/hooks/mutations"; +import { MetadataStandard, VotedProposal } from "@/models"; +import { GovernanceVotedOnCard } from "../molecules"; + +type Props = { + votedProposal: VotedProposal; + inProgress?: boolean; +}; +export const ValidatedGovernanceVotedOnCard = ({ + votedProposal, + inProgress, +}: Props) => { + const [isValidating, setIsValidating] = useState(false); + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + setIsValidating(true); + + const { status } = await validateMetadata({ + standard: MetadataStandard.CIP108, + url: votedProposal.proposal.url, + hash: votedProposal.proposal.metadataHash, + }); + + metadataStatus.current = status; + setIsValidating(false); + }; + validate(); + }, []); + + return ( + + ); +}; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index 4e4df0284..f1c136904 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -2,6 +2,8 @@ export * from "./AutomatedVotingOptions"; export * from "./ChooseStakeKeyPanel"; export * from "./ControlledField"; export * from "./CreateGovernanceActionSteps"; +export * from "./DRepCard"; +export * from "./DRepDetailsCard"; export * from "./DashboardCards"; export * from "./DashboardCards"; export * from "./DashboardDrawerMobile"; @@ -11,18 +13,19 @@ export * from "./DashboardGovernanceActionsVotedOn"; export * from "./DashboardTopNav"; export * from "./Drawer"; export * from "./DrawerMobile"; -export * from "./DRepCard"; -export * from "./DRepDetailsCard"; export * from "./EditDRepInfoSteps"; -export * from "./Modal"; export * from "./Footer"; export * from "./GovernanceActionDetailsCard"; export * from "./GovernanceActionDetailsCardData"; export * from "./GovernanceActionsToVote"; export * from "./Hero"; export * from "./HomeCards"; +export * from "./Modal"; export * from "./RegisterAsDRepSteps"; export * from "./Slider"; export * from "./TopNav"; +export * from "./UncontrolledImageInput"; +export * from "./ValidatedGovernanceActionCard"; +export * from "./ValidatedGovernanceVotedOnCard"; export * from "./VoteContext"; export * from "./WrongRouteInfo"; diff --git a/govtool/frontend/src/consts/dRepActions/fields.ts b/govtool/frontend/src/consts/dRepActions/fields.ts index e8273cdb1..367ef4468 100644 --- a/govtool/frontend/src/consts/dRepActions/fields.ts +++ b/govtool/frontend/src/consts/dRepActions/fields.ts @@ -3,6 +3,7 @@ import { IMAGE_REGEX, URL_REGEX, isReceivingAddress, + isValidImageUrl, isValidURLLength, } from "@/utils"; @@ -76,5 +77,6 @@ export const Rules = { value: IMAGE_REGEX, message: i18n.t("registration.fields.validations.image"), }, + validate: isValidImageUrl, }, }; diff --git a/govtool/frontend/src/context/governanceAction.test.tsx b/govtool/frontend/src/context/governanceAction.test.tsx index 3eb4e06b4..0ecfd2cb2 100644 --- a/govtool/frontend/src/context/governanceAction.test.tsx +++ b/govtool/frontend/src/context/governanceAction.test.tsx @@ -80,7 +80,7 @@ describe("GovernanceActionProvider", () => { const hash = await createHash(jsonld!); expect(hash).toBeDefined(); expect(hash).toBe( - "816b63124f5c5d5bdfc016ad0aea238baf374fecbdadd389eab2dab94bc2383c", + "9f7b2f83113e003d36231bd815cafbcc677c3a9513281d751a241720893cc877", ); }; test(); diff --git a/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts b/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts index d9fc1a1ec..74f5f0b06 100644 --- a/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts +++ b/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts @@ -1,17 +1,27 @@ -import { useMutation } from "react-query"; +import { useMutation, useQueryClient } from "react-query"; import { postValidate } from "@services"; import { MUTATION_KEYS } from "@consts"; import { MetadataValidationDTO } from "@models"; export const useValidateMutation = () => { - const { data, isLoading, mutateAsync } = useMutation({ - mutationFn: (body: MetadataValidationDTO) => postValidate(body), + const queryClient = useQueryClient(); + + const { data, isLoading } = useMutation({ + mutationFn: (body: MetadataValidationDTO) => + postValidate(body), mutationKey: [MUTATION_KEYS.postValidateKey], }); + const validateMetadata = async (body: MetadataValidationDTO) => + queryClient.fetchQuery({ + queryKey: [MUTATION_KEYS.postValidateKey, body.hash, body.url], + queryFn: () => postValidate(body), + cacheTime: 20 * 1000, // 20 seconds + }); + return { - validateMetadata: mutateAsync, + validateMetadata, validationStatus: data, isValidating: isLoading, }; diff --git a/govtool/frontend/src/i18n/locales/en.json b/govtool/frontend/src/i18n/locales/en.json index 78ba4d111..7f25ca853 100644 --- a/govtool/frontend/src/i18n/locales/en.json +++ b/govtool/frontend/src/i18n/locales/en.json @@ -388,7 +388,8 @@ "errors": { "tooLongUrl": "Url must be less than 128 bytes", "mustBeStakeAddress": "It must be reward address in bech32 format", - "mustBeReceivingAddress": "Invalid payment address" + "mustBeReceivingAddress": "Invalid payment address", + "couldNotGenerateImageSha": "Could not generate image sha" } }, "proposalDiscussion": { diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index f298b41f0..bc374ccad 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -1,4 +1,3 @@ -import { MetadataValidationStatus } from "@models"; import { GovernanceActionType } from "@/types/governanceAction"; export type EpochParams = { @@ -176,8 +175,6 @@ export type DRepData = DrepDataDTO & { qualifications: string | null; references: Reference[]; doNotList: boolean; - metadataStatus: MetadataValidationStatus | null; - metadataValid: boolean; imageUrl: string | null; // either base64 for IPFS image or URL for regular image image: string | null; @@ -210,7 +207,7 @@ export type SubmittedVotesData = { protocolParams: EpochParams | null; }; -export type ProposalDataDTO = { +export type ProposalData = { createdDate: string; createdEpochNo: number; details?: ActionDetailsType; @@ -232,21 +229,11 @@ export type ProposalDataDTO = { protocolParams: EpochParams | null; } & SubmittedVotesData; -export type ProposalData = ProposalDataDTO & { - metadataStatus: MetadataValidationStatus | null; - metadataValid: boolean; -}; - export type NewConstitutionAnchor = { dataHash: string; url: string; }; -export type VotedProposalDTO = { - vote: ProposalVote | null; - proposal: ProposalDataDTO; -}; - export type VotedProposal = { vote: ProposalVote | null; proposal: ProposalData; diff --git a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx index 9084bf070..fb366fc67 100644 --- a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx +++ b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx @@ -8,7 +8,6 @@ import { useCardano, useDataActionsBar } from "@context"; import { DataActionsBar, EmptyStateGovernanceActionsCategory, - GovernanceActionCard, } from "@molecules"; import { useFetchNextPageDetector, @@ -23,6 +22,7 @@ import { getProposalTypeLabel, removeDuplicatedProposals, } from "@utils"; +import { ValidatedGovernanceActionCard } from "@/components/organisms"; export const DashboardGovernanceActionsCategory = () => { const { category } = useParams(); @@ -133,7 +133,7 @@ export const DashboardGovernanceActionsCategory = () => { > {mappedData.map((item) => ( - { ); const proposal = (data ?? state)?.proposal; + const metadataStatus = useRef(); + const { validateMetadata } = useValidateMutation(); + + useEffect(() => { + const validate = async () => { + const { status } = await validateMetadata({ + standard: MetadataStandard.CIP108, + url: proposal?.url ?? "", + hash: proposal?.metadataHash ?? "", + }); + + metadataStatus.current = status; + }; + validate(); + }, [proposal?.url, proposal?.metadataHash]); + useEffect(() => { const isProposalNotFound = (error as AxiosError)?.response?.data === @@ -105,7 +122,7 @@ export const GovernanceActionDetails = () => { elementOne={t("govActions.title")} elementOnePath={PATHS.governanceActions} elementTwo={proposal?.title ?? ""} - isDataMissing={proposal?.metadataStatus ?? null} + isDataMissing={metadataStatus?.current ?? null} /> { }} onClick={() => navigate( - state && state.openedFromCategoryPage + state?.openedFromCategoryPage ? generatePath(PATHS.governanceActionsCategory, { category: state?.proposal?.type, }) @@ -144,7 +161,7 @@ export const GovernanceActionDetails = () => { ) : proposal ? ( diff --git a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx index 51d982f79..7eda23034 100644 --- a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx +++ b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx @@ -8,9 +8,8 @@ import { useCardano, useDataActionsBar } from "@context"; import { DataActionsBar, EmptyStateGovernanceActionsCategory, - GovernanceActionCard, } from "@molecules"; -import { Footer, TopNav } from "@organisms"; +import { Footer, TopNav, ValidatedGovernanceActionCard } from "@organisms"; import { useGetProposalsInfiniteQuery, useFetchNextPageDetector, @@ -139,7 +138,7 @@ export const GovernanceActionsCategory = () => { > {mappedData.map((item) => ( - { saveScrollPosition(); diff --git a/govtool/frontend/src/pages/ProposalDiscussion.tsx b/govtool/frontend/src/pages/ProposalDiscussion.tsx index b69836df9..396b6eee8 100644 --- a/govtool/frontend/src/pages/ProposalDiscussion.tsx +++ b/govtool/frontend/src/pages/ProposalDiscussion.tsx @@ -6,6 +6,7 @@ import { useCardano, useGovernanceActions, useProposalDiscussion, + useSnackbar, } from "@/context"; import { useValidateMutation } from "@/hooks/mutations"; import { useScreenDimension } from "@/hooks/useScreenDimension"; @@ -30,6 +31,7 @@ export const ProposalDiscussionPillar = () => { const { fetchDRepVotingPowerList } = useGetDRepVotingPowerList(); const { username, setUsername } = useProposalDiscussion(); const { votingPower } = useGetAdaHolderVotingPowerQuery(context.stakeKey); + const snackbarContext = useSnackbar(); return ( { setUsername={setUsername} epochParams={epochParams} votingPower={votingPower} + {...snackbarContext} /> diff --git a/govtool/frontend/src/services/requests/getDRepList.ts b/govtool/frontend/src/services/requests/getDRepList.ts index b61707411..12270b7ea 100644 --- a/govtool/frontend/src/services/requests/getDRepList.ts +++ b/govtool/frontend/src/services/requests/getDRepList.ts @@ -3,7 +3,6 @@ import { type DRepStatus, type DRepListSort, DRepData, - DrepDataDTO, } from "@models"; import { API } from "../API"; import { dRepSearchPhraseProcessor, mapDtoToDrep } from "@/utils"; @@ -27,7 +26,7 @@ export const getDRepList = async ({ }: GetDRepListArguments): Promise> => { const searchPhrase = await dRepSearchPhraseProcessor(rawSearchPhrase); - const response = await API.get>("/drep/list", { + const response = await API.get>("/drep/list", { params: { page, pageSize, diff --git a/govtool/frontend/src/services/requests/getDRepVotes.ts b/govtool/frontend/src/services/requests/getDRepVotes.ts index 1b2bbf1e6..008e720bf 100644 --- a/govtool/frontend/src/services/requests/getDRepVotes.ts +++ b/govtool/frontend/src/services/requests/getDRepVotes.ts @@ -1,6 +1,5 @@ -import { VotedProposal, VotedProposalDTO } from "@models"; +import { VotedProposal } from "@models"; import { API } from "../API"; -import { mapDtoToProposal } from "@/utils"; type GetDRepVotesParams = { type?: string[]; @@ -17,14 +16,7 @@ export const getDRepVotes = async ({ }): Promise => { const urlBase = `/drep/getVotes/${dRepID}`; - const { data } = await API.get(urlBase, { params }); + const { data } = await API.get(urlBase, { params }); - const validatedData = await Promise.all( - data.map(async (votedProposal) => ({ - ...votedProposal, - proposal: await mapDtoToProposal(votedProposal.proposal), - })) - ); - - return validatedData; + return data; }; diff --git a/govtool/frontend/src/services/requests/getProposal.ts b/govtool/frontend/src/services/requests/getProposal.ts index c82aa6199..65744d6d0 100644 --- a/govtool/frontend/src/services/requests/getProposal.ts +++ b/govtool/frontend/src/services/requests/getProposal.ts @@ -1,5 +1,5 @@ -import { VotedProposal, VotedProposalDTO } from "@/models"; -import { decodeCIP129Identifier, mapDtoToProposal } from "@/utils"; +import { VotedProposal } from "@/models"; +import { decodeCIP129Identifier } from "@/utils"; import { API } from "../API"; @@ -15,12 +15,9 @@ export const getProposal = async ( const encodedHash = encodeURIComponent(proposalId); - const { data } = await API.get( + const { data } = await API.get( `/proposal/get/${encodedHash}?drepId=${drepId}`, ); - return { - ...data, - proposal: await mapDtoToProposal(data.proposal), - }; + return data; }; diff --git a/govtool/frontend/src/services/requests/getProposals.ts b/govtool/frontend/src/services/requests/getProposals.ts index 9d09751be..765fd211c 100644 --- a/govtool/frontend/src/services/requests/getProposals.ts +++ b/govtool/frontend/src/services/requests/getProposals.ts @@ -1,11 +1,7 @@ -import { Infinite, ProposalData, ProposalDataDTO } from "@models"; +import { Infinite, ProposalData } from "@models"; import { API } from "../API"; -import { - decodeCIP129Identifier, - getFullGovActionId, - mapDtoToProposal, -} from "@/utils"; +import { decodeCIP129Identifier, getFullGovActionId } from "@/utils"; export type GetProposalsArguments = { dRepID?: string; @@ -34,7 +30,7 @@ export const getProposals = async ({ return rawSearchPhrase; })(); - const response = await API.get>("/proposal/list", { + const response = await API.get>("/proposal/list", { params: { page, pageSize, @@ -47,14 +43,5 @@ export const getProposals = async ({ }, }); - const validatedResponse = { - ...response.data, - elements: await Promise.all( - response.data.elements.map((proposalDTO) => - mapDtoToProposal(proposalDTO), - ), - ), - }; - - return validatedResponse; + return response.data; }; diff --git a/govtool/frontend/src/stories/DRepDetailsCard.stories.ts b/govtool/frontend/src/stories/DRepDetailsCard.stories.ts index c91b396d3..17754f8c9 100644 --- a/govtool/frontend/src/stories/DRepDetailsCard.stories.ts +++ b/govtool/frontend/src/stories/DRepDetailsCard.stories.ts @@ -1,6 +1,6 @@ import { ComponentProps } from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { DRepData, DRepStatus, MetadataValidationStatus } from "@models"; +import { DRepData, DRepStatus } from "@models"; import { DRepDetailsCard } from "@organisms"; const meta = { @@ -22,33 +22,40 @@ const meta = { votingPower: 1000000, paymentAddress: "examplePaymentAddress", givenName: "John Smith", - objectives: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ornare pellentesque hendrerit. Pellentesque et placerat ex. Curabitur vitae pharetra ligula. Nullam euismod, odio sit amet suscipit facilisis, neque erat ultricies velit, sed rutrum neque nisi ac dui. Donec lobortis metus pulvinar varius gravida. Duis blandit, tortor non placerat commodo, metus lorem aliquam augue, eget ultricies nunc massa eu arcu. Etiam pellentesque urna nisl, facilisis placerat elit congue quis. Nulla quis dolor ac eros ullamcorper convallis ac ut enim. Proin faucibus urna at mi blandit, ut gravida sapien lacinia. Sed pretium, magna non tempor sollicitudin, tellus odio efficitur enim, ut feugiat nisi ligula non dolor. Fusce volutpat condimentum arcu, eu tempus neque convallis non. Suspendisse mattis sit amet libero et fringilla. Suspendisse eget erat eu nisl feugiat varius. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur vehicula eleifend lectus, vel eleifend felis vestibulum.", - motivations: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras semper tortor ullamcorper volutpat vehicula. Duis varius orci a elit luctus, in fringilla nisl fringilla. Fusce pellentesque convallis dapibus. In hac habitasse platea dictumst. Nunc efficitur ipsum at ipsum blandit, ac eleifend purus pulvinar. Pellentesque orci quam, interdum eget massa id, sollicitudin lacinia turpis. Nullam lectus quam, congue commodo sollicitudin in, pretium sit amet metus. Integer pretium, odio eu dictum posuere.", - qualifications: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc porta iaculis sodales. Praesent non nisi fermentum, porta sem in, porta arcu. In dignissim pulvinar est eu dignissim. Duis vitae vehicula dui. Praesent posuere egestas lacus, at pulvinar elit tempus ut. Etiam vulputate, lorem in accumsan.", - references: [{ - "@type": "Link", - label: "Link Reference", - uri: "https://example.com/", - }, { - "@type": "Link", - label: "Another Link Reference", - uri: "https://example.com/", - }, { - "@type": "Identity", - label: "Identity Reference", - uri: "https://example.com/", - }, { - "@type": "GovernanceMetadata", - label: "GovernanceMetadata Reference", - uri: "https://example.com/", - }, { - "@type": "Other", - label: "Other Reference", - uri: "https://example.com/", - }], + objectives: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ornare pellentesque hendrerit. Pellentesque et placerat ex. Curabitur vitae pharetra ligula. Nullam euismod, odio sit amet suscipit facilisis, neque erat ultricies velit, sed rutrum neque nisi ac dui. Donec lobortis metus pulvinar varius gravida. Duis blandit, tortor non placerat commodo, metus lorem aliquam augue, eget ultricies nunc massa eu arcu. Etiam pellentesque urna nisl, facilisis placerat elit congue quis. Nulla quis dolor ac eros ullamcorper convallis ac ut enim. Proin faucibus urna at mi blandit, ut gravida sapien lacinia. Sed pretium, magna non tempor sollicitudin, tellus odio efficitur enim, ut feugiat nisi ligula non dolor. Fusce volutpat condimentum arcu, eu tempus neque convallis non. Suspendisse mattis sit amet libero et fringilla. Suspendisse eget erat eu nisl feugiat varius. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur vehicula eleifend lectus, vel eleifend felis vestibulum.", + motivations: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras semper tortor ullamcorper volutpat vehicula. Duis varius orci a elit luctus, in fringilla nisl fringilla. Fusce pellentesque convallis dapibus. In hac habitasse platea dictumst. Nunc efficitur ipsum at ipsum blandit, ac eleifend purus pulvinar. Pellentesque orci quam, interdum eget massa id, sollicitudin lacinia turpis. Nullam lectus quam, congue commodo sollicitudin in, pretium sit amet metus. Integer pretium, odio eu dictum posuere.", + qualifications: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc porta iaculis sodales. Praesent non nisi fermentum, porta sem in, porta arcu. In dignissim pulvinar est eu dignissim. Duis vitae vehicula dui. Praesent posuere egestas lacus, at pulvinar elit tempus ut. Etiam vulputate, lorem in accumsan.", + references: [ + { + "@type": "Link", + label: "Link Reference", + uri: "https://example.com/", + }, + { + "@type": "Link", + label: "Another Link Reference", + uri: "https://example.com/", + }, + { + "@type": "Identity", + label: "Identity Reference", + uri: "https://example.com/", + }, + { + "@type": "GovernanceMetadata", + label: "GovernanceMetadata Reference", + uri: "https://example.com/", + }, + { + "@type": "Other", + label: "Other Reference", + uri: "https://example.com/", + }, + ], doNotList: false, - metadataStatus: null, - metadataValid: true, } as DRepData, }, tags: ["autodocs"], @@ -93,12 +100,3 @@ export const UserNotConnected: Story = { isConnected: false, }, }; - -export const InvalidData: Story = { - args: { - dRepData: { - ...meta.args.dRepData, - metadataStatus: MetadataValidationStatus.INCORRECT_FORMAT, - }, - }, -}; diff --git a/govtool/frontend/src/stories/GovernanceAction.stories.ts b/govtool/frontend/src/stories/GovernanceAction.stories.ts index 3119c83c5..085e69df4 100644 --- a/govtool/frontend/src/stories/GovernanceAction.stories.ts +++ b/govtool/frontend/src/stories/GovernanceAction.stories.ts @@ -1,4 +1,3 @@ -import { MetadataValidationStatus } from "@models"; import { expect, screen, @@ -16,6 +15,7 @@ import { } from "@utils"; import { GovernanceActionCard } from "@/components/molecules"; import { GovernanceActionType } from "@/types/governanceAction"; +import { MetadataValidationStatus } from "@/models"; const meta = { title: "Example/GovernanceActionCard", @@ -30,8 +30,8 @@ export default meta; type Story = StoryObj; -const commonArgs = { - about: "About this Governance Action", +const commonArgs: Story["args"] = { + abstract: "About this Governance Action", createdDate: "1970-01-01T00:00:00Z", createdEpochNo: 302, expiryDate: "1970-02-01T00:00:00Z", @@ -42,8 +42,6 @@ const commonArgs = { title: "Example title", txHash: "sad78afdsf7jasd98d", type: GovernanceActionType.InfoAction, - metadataValid: true, - metadataStatus: null, dRepYesVotes: 1, dRepNoVotes: 0, dRepAbstainVotes: 0, @@ -56,6 +54,8 @@ const commonArgs = { protocolParams: null, prevGovActionIndex: null, prevGovActionTxHash: null, + metadataHash: "exampleMetadataHash", + url: "https://exampleMetadataUrl.com", }; const cip129GovActionId = encodeCIP129Identifier({ @@ -102,10 +102,7 @@ export const GovernanceActionCardComponent: Story = { }; export const GovernanceActionCardIsLoading: Story = { - args: { - ...commonArgs, - inProgress: true, - }, + args: { ...commonArgs, inProgress: true }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); await expect(canvas.getByText(/in progress/i)).toBeVisible(); @@ -116,7 +113,6 @@ export const GovernanceActionCardDataMissing: Story = { args: { ...commonArgs, metadataStatus: MetadataValidationStatus.URL_NOT_FOUND, - metadataValid: false, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -135,8 +131,7 @@ export const GovernanceActionCardDataMissing: Story = { export const GovernanceActionCardIncorectFormat: Story = { args: { ...commonArgs, - metadataStatus: MetadataValidationStatus.INVALID_JSONLD, - metadataValid: false, + metadataStatus: MetadataValidationStatus.INCORRECT_FORMAT, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -158,7 +153,6 @@ export const GovernanceActionCardNotVerifiable: Story = { args: { ...commonArgs, metadataStatus: MetadataValidationStatus.INVALID_HASH, - metadataValid: false, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); diff --git a/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts b/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts index 9d62c35f9..67e6001f7 100644 --- a/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts +++ b/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts @@ -57,8 +57,6 @@ const commonArgs = { prevGovActionIndex: null, prevGovActionTxHash: null, metadataHash: "exampleMetadataHash", - metadataStatus: null, - metadataValid: true, references: [ { "@type": "Reference", @@ -118,6 +116,7 @@ async function assertGovActionDetails( export const GovernanceActionDetailsCardComponent: Story = { args: { ...commonArgs, + isDataMissing: undefined, proposal: { ...commonArgs.proposal, abstract: "Example about section", @@ -159,6 +158,7 @@ export const GovernanceActionDetailsDrep: Story = { ...commonArgs, isDashboard: true, isVoter: true, + isDataMissing: undefined, proposal: { ...commonArgs.proposal, abstract: "Example about section", diff --git a/govtool/frontend/src/stories/GovernanceActionVoted.stories.ts b/govtool/frontend/src/stories/GovernanceActionVoted.stories.ts index 6b43f2452..d5eb8de53 100644 --- a/govtool/frontend/src/stories/GovernanceActionVoted.stories.ts +++ b/govtool/frontend/src/stories/GovernanceActionVoted.stories.ts @@ -4,6 +4,7 @@ import { expect, userEvent, waitFor, within, screen } from "@storybook/test"; import { GovernanceVotedOnCard } from "@molecules"; import { formatDisplayDate, getProposalTypeNoEmptySpaces } from "@/utils"; import { GovernanceActionType } from "@/types/governanceAction"; +import { MetadataValidationStatus } from "@/models/metadataValidation"; const meta = { title: "Example/GovernanceVotedOnCard", @@ -68,8 +69,6 @@ export const GovernanceVotedOnCardComponent: Story = { proposal: { createdEpochNo: 232, expiryEpochNo: 323, - metadataStatus: null, - metadataValid: true, createdDate: "1970-01-01T00:00:00Z", expiryDate: "1970-02-01T00:00:00Z", id: "exampleId", @@ -115,8 +114,6 @@ export const GovernanceVotedOnCardAbstain: Story = { proposal: { createdEpochNo: 232, expiryEpochNo: 323, - metadataStatus: null, - metadataValid: true, createdDate: "1970-01-01T00:00:00Z", expiryDate: "1970-02-01T00:00:00Z", id: "exampleId", @@ -163,8 +160,6 @@ export const GovernanceVotedOnCardYes: Story = { proposal: { createdEpochNo: 232, expiryEpochNo: 323, - metadataStatus: null, - metadataValid: true, createdDate: "1970-01-01T00:00:00Z", expiryDate: "1970-02-01T00:00:00Z", id: "exampleId", @@ -211,8 +206,6 @@ export const GovernanceVotedOnCardNo: Story = { proposal: { createdEpochNo: 232, expiryEpochNo: 323, - metadataStatus: null, - metadataValid: true, createdDate: "1970-01-01T00:00:00Z", expiryDate: "1970-02-01T00:00:00Z", id: "exampleId", @@ -242,3 +235,91 @@ export const GovernanceVotedOnCardNo: Story = { expect(canvas.getByText(/no/i)).toBeInTheDocument(); }, }; + +export const GovernanceVotedOnCardDataFormattedIncorrectly: Story = { + args: { + votedProposal: { + vote: { + date: new Date().toLocaleDateString(), + drepId: "drep1_exampledrepid1231231", + epochNo: 222, + metadataHash: "ababa1ababab1abababa1ababab1ababa1aba1", + proposalId: "exampleproposalid12dsadasdasda", + url: "https://exampleurl.com", + vote: "no", + txHash: "dwq78dqw78qwd78wdq78dqw78dqw", + }, + proposal: { + createdEpochNo: 232, + expiryEpochNo: 323, + createdDate: "1970-01-01T00:00:00Z", + expiryDate: "1970-02-01T00:00:00Z", + id: "exampleId", + type: GovernanceActionType.InfoAction, + index: 1, + txHash: "exampleHash", + url: "https://example.com", + metadataHash: "exampleHash", + dRepYesVotes: 1, + dRepNoVotes: 0, + dRepAbstainVotes: 2, + poolYesVotes: 1, + poolNoVotes: 0, + poolAbstainVotes: 2, + ccYesVotes: 1, + ccNoVotes: 0, + ccAbstainVotes: 2, + protocolParams: null, + prevGovActionIndex: null, + prevGovActionTxHash: null, + }, + }, + metadataStatus: MetadataValidationStatus.INCORRECT_FORMAT, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + expect(canvas.getByText(/Data Formatted Incorrectly/i)).toBeInTheDocument(); + }, +}; + +export const GovernanceVotedOnCardValidating: Story = { + args: { + votedProposal: { + vote: { + date: new Date().toLocaleDateString(), + drepId: "drep1_exampledrepid1231231", + epochNo: 222, + metadataHash: "ababa1ababab1abababa1ababab1ababa1aba1", + proposalId: "exampleproposalid12dsadasdasda", + url: "https://exampleurl.com", + vote: "no", + txHash: "dwq78dqw78qwd78wdq78dqw78dqw", + }, + proposal: { + createdEpochNo: 232, + expiryEpochNo: 323, + createdDate: "1970-01-01T00:00:00Z", + expiryDate: "1970-02-01T00:00:00Z", + id: "exampleId", + type: GovernanceActionType.InfoAction, + index: 1, + txHash: "exampleHash", + url: "https://example.com", + metadataHash: "exampleHash", + dRepYesVotes: 1, + dRepNoVotes: 0, + dRepAbstainVotes: 2, + poolYesVotes: 1, + poolNoVotes: 0, + poolAbstainVotes: 2, + ccYesVotes: 1, + ccNoVotes: 0, + ccAbstainVotes: 2, + protocolParams: null, + prevGovActionIndex: null, + prevGovActionTxHash: null, + }, + }, + isValidating: true, + }, +}; diff --git a/govtool/frontend/src/utils/generateJsonld.ts b/govtool/frontend/src/utils/generateJsonld.ts index b2481c762..1ed0700ba 100644 --- a/govtool/frontend/src/utils/generateJsonld.ts +++ b/govtool/frontend/src/utils/generateJsonld.ts @@ -19,6 +19,7 @@ export const generateJsonld = async < const doc = { "@context": context, hashAlgorithm: "blake2b-256", + authors: [], body, }; diff --git a/govtool/frontend/src/utils/generateMetadataBody.ts b/govtool/frontend/src/utils/generateMetadataBody.ts index 03ef7a68a..bc35bcbd0 100644 --- a/govtool/frontend/src/utils/generateMetadataBody.ts +++ b/govtool/frontend/src/utils/generateMetadataBody.ts @@ -17,47 +17,51 @@ export const generateMetadataBody = async ({ data, acceptedKeys, }: MetadataConfig) => { - const filteredData = Object.entries(data) - .filter(([key, value]) => value && acceptedKeys.includes(key)) - .map(([key, value]) => [key, value]); - - const references = data?.references - ? // uri should not be optional. It is just not yet supported on govtool - (data.references as Array>) - .filter((link) => link.uri) - .map((link) => ({ - "@type": link["@type"] ?? "Other", - label: link.label ?? "Label", - uri: link.uri, - })) - : undefined; - - const isUrl = (url?: unknown) => URL_REGEX.test(url as string); - let image; - - if (isUrl(data?.image)) { - image = { - "@type": "ImageObject", - contentUrl: data.image, - sha256: await getImageSha(data.image as string), - }; - } else { - image = data?.image - ? { - "@type": "ImageObject", - contentUrl: data.image, - } + try { + const filteredData = Object.entries(data) + .filter(([key, value]) => value && acceptedKeys.includes(key)) + .map(([key, value]) => [key, value]); + + const references = data?.references + ? // uri should not be optional. It is just not yet supported on govtool + (data.references as Array>) + .filter((link) => link.uri) + .map((link) => ({ + "@type": link["@type"] ?? "Other", + label: link.label ?? "Label", + uri: link.uri, + })) : undefined; - } - const body = Object.fromEntries(filteredData); - if (references?.length) { - body.references = references; - } + const isUrl = (url?: unknown) => URL_REGEX.test(url as string); + let image; - if (image) { - body.image = image; - } + if (isUrl(data?.image)) { + image = { + "@type": "ImageObject", + contentUrl: data.image, + sha256: await getImageSha(data.image as string), + }; + } else { + image = data?.image + ? { + "@type": "ImageObject", + contentUrl: data.image, + } + : undefined; + } - return body; + const body = Object.fromEntries(filteredData); + if (references?.length) { + body.references = references; + } + + if (image) { + body.image = image; + } + + return body; + } catch (error) { + console.error({ error }); + } }; diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts index dfeaca81f..38c403872 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -27,7 +27,6 @@ export * from "./jsonUtils"; export * from "./localStorage"; export * from "./mapArrayToObjectByKeys"; export * from "./mapDtoToDrep"; -export * from "./mapDtoToProposal"; export * from "./numberValidation"; export * from "./openInNewTab"; export * from "./removeDuplicatedProposals"; diff --git a/govtool/frontend/src/utils/isValidFormat.ts b/govtool/frontend/src/utils/isValidFormat.ts index b203fcee7..dbb5a1c84 100644 --- a/govtool/frontend/src/utils/isValidFormat.ts +++ b/govtool/frontend/src/utils/isValidFormat.ts @@ -5,6 +5,7 @@ import { } from "@emurgo/cardano-serialization-lib-asmjs"; import i18n from "@/i18n"; import { adaHandleService } from "@/services/AdaHandle"; +import { getImageSha } from "./getImageSha"; export const URL_REGEX = /^(?:(?:https?:\/\/)?(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?:\/[^\s]*)?)|(?:ipfs:\/\/(?:[a-zA-Z0-9]+(?:\/[a-zA-Z0-9._-]+)*))$|^$/; @@ -76,3 +77,15 @@ export async function isDRepView(view?: string) { } return i18n.t("forms.errors.mustBeDRepView"); } + +export async function isValidImageUrl(url: string) { + if (!url.length) return false; + try { + if (URL_REGEX.test(url)) { + await getImageSha(url); + } + return true; + } catch (error) { + return i18n.t("forms.errors.couldNotGenerateImageSha"); + } +} diff --git a/govtool/frontend/src/utils/mapDtoToDrep.ts b/govtool/frontend/src/utils/mapDtoToDrep.ts index b0f1e4b1f..985bd67b9 100644 --- a/govtool/frontend/src/utils/mapDtoToDrep.ts +++ b/govtool/frontend/src/utils/mapDtoToDrep.ts @@ -1,30 +1,11 @@ -import { - DRepData, - DRepMetadata, - DrepDataDTO, - MetadataStandard, -} from "@/models"; -import { postValidate } from "@/services"; +import { DRepData } from "@/models"; import { fixViewForScriptBasedDRep } from "./dRep"; const imageFetchDefaultOptions: RequestInit = { mode: "no-cors", }; -export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { - const emptyMetadata = { - paymentAddress: null, - givenName: "", - imageUrl: null, - objectives: null, - motivations: null, - qualifications: null, - references: [], - doNotList: false, - metadataStatus: null, - metadataValid: true, - }; - +export const mapDtoToDrep = async (dto: DRepData): Promise => { // DBSync contains wrong representation of DRep view for script based DReps const view = fixViewForScriptBasedDRep(dto.view, dto.isScriptBased); @@ -62,27 +43,8 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { } }); } - - if (dto.metadataHash && dto.url) { - const validationResponse = await postValidate({ - url: dto.url, - hash: dto.metadataHash, - standard: MetadataStandard.CIP119, - }); - return { - ...dto, - ...emptyMetadata, - ...validationResponse.metadata, - metadataStatus: validationResponse.status || null, - metadataValid: validationResponse.valid, - image: isIPFSImage ? base64Image : dto.imageUrl, - view, - }; - } - return { ...dto, - ...emptyMetadata, view, image: isIPFSImage ? base64Image : dto.imageUrl, }; diff --git a/govtool/frontend/src/utils/mapDtoToProposal.ts b/govtool/frontend/src/utils/mapDtoToProposal.ts deleted file mode 100644 index 9f3635350..000000000 --- a/govtool/frontend/src/utils/mapDtoToProposal.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - MetadataStandard, - ProposalData, - ProposalDataDTO, - ProposalMetadata, -} from "@/models"; -import { postValidate } from "@/services"; - -export const mapDtoToProposal = async ( - dto: ProposalDataDTO, -): Promise => { - if (dto.url && dto.metadataHash) { - const validationResponse = await postValidate({ - url: dto.url, - hash: dto.metadataHash, - standard: MetadataStandard.CIP108, - }); - - return { - ...dto, - title: dto.title || validationResponse.metadata?.title, - abstract: dto.abstract || validationResponse.metadata?.abstract, - motivation: dto.motivation || validationResponse.metadata?.motivation, - rationale: dto.rationale || validationResponse.metadata?.rationale, - references: validationResponse.metadata?.references || [], - metadataStatus: validationResponse.status || null, - metadataValid: validationResponse.valid, - }; - } - - return { - ...dto, - metadataStatus: null, - metadataValid: true, - }; -}; diff --git a/govtool/frontend/src/utils/tests/dRep.test.ts b/govtool/frontend/src/utils/tests/dRep.test.ts index 66f41fe63..00a06a4f2 100644 --- a/govtool/frontend/src/utils/tests/dRep.test.ts +++ b/govtool/frontend/src/utils/tests/dRep.test.ts @@ -15,8 +15,6 @@ const EXAMPLE_DREP: DRepData = { type: "DRep" as TDRepType, givenName: "name", references: [], - metadataStatus: null, - metadataValid: true, latestRegistrationDate: "2024-07-10", paymentAddress: null, objectives: null, diff --git a/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts b/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts index 29260ed57..aa9adee76 100644 --- a/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts +++ b/govtool/frontend/src/utils/tests/removeDuplicatedProposals.test.ts @@ -30,8 +30,6 @@ const uniqueProposals: ProposalData[] = [ ccAbstainVotes: 4324, title: "Proposal 1322 Title", abstract: "This is about Proposal 1322", - metadataStatus: null, - metadataValid: false, motivation: "Motivation behind Proposal 1322", rationale: "Rationale for Proposal 1322", protocolParams: null, @@ -65,8 +63,6 @@ const uniqueProposals: ProposalData[] = [ ccAbstainVotes: 4324, title: "Proposal 1338 Title", abstract: "This is about Proposal 1338", - metadataStatus: null, - metadataValid: false, motivation: "Motivation behind Proposal 1338", rationale: "Rationale for Proposal 1338", protocolParams: null, @@ -100,8 +96,6 @@ const uniqueProposals: ProposalData[] = [ ccAbstainVotes: 4324, title: "Proposal 1335 Title", abstract: "This is about Proposal 1335", - metadataStatus: null, - metadataValid: false, motivation: "Motivation behind Proposal 1335", rationale: "Rationale for Proposal 1335", protocolParams: null, diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock index f22212f21..dc1af3026 100644 --- a/govtool/frontend/yarn.lock +++ b/govtool/frontend/yarn.lock @@ -1377,10 +1377,10 @@ resolved "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-14.1.1.tgz" integrity sha512-Q2HVpPRt417Quxv3qagGWbkJQU8SiQCl1K/344ZtQMwsLoqTfRlCNzmSWMBN7jyBxbtKoh+vdbSiLqwG1NAjYg== -"@esbuild/linux-x64@0.25.0": +"@esbuild/darwin-arm64@0.25.0": version "0.25.0" - resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz" - integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw== + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz" + integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.1" @@ -1492,10 +1492,10 @@ resolved "https://registry.npmjs.org/@inquirer/type/-/type-3.0.4.tgz" integrity sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA== -"@intersect.mbo/govtool-outcomes-pillar-ui@1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.3.0.tgz" - integrity sha512-6+H+QG8kyM2UUEycNsjrF1K5+UGUw6+wy7gRxlyOtFjIrZ9CUdTfwwyD1hrh+g55awZ4t+EmQgYB00An0iXOag== +"@intersect.mbo/govtool-outcomes-pillar-ui@1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.4.0.tgz" + integrity sha512-iSg6FzeUW4F3KSQDcHCytggCG68Q4OKUzQa8yNMSPVAuM6w5I6LhaqEYRO8x02G8WPyE+8W+Q9m3SCFea59osg== dependencies: "@fontsource/poppins" "^5.0.14" "@intersect.mbo/intersectmbo.org-icons-set" "^1.0.8" @@ -1512,10 +1512,10 @@ resolved "https://registry.npmjs.org/@intersect.mbo/intersectmbo.org-icons-set/-/intersectmbo.org-icons-set-1.1.0.tgz" integrity sha512-sjKEtnK9eLYH/8kCD0YRQCms3byFA/tnSsei9NHTZbBYX9sBpeX6ErfR0sKYjOSxQOxl4FumX9D0X+vHIqxo8g== -"@intersect.mbo/pdf-ui@0.7.0-beta-16": - version "0.7.0-beta-16" - resolved "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-16.tgz" - integrity sha512-2I7BSg+5FDGhlvwLdZk0teVhtPMRQ1uDWKQiwobIF41cM0b+iT2CJMDL5MJk6tsM2/2JBbOEev9VrsHSanrCVA== +"@intersect.mbo/pdf-ui@0.7.0-beta-19": + version "0.7.0-beta-19" + resolved "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-19.tgz" + integrity sha512-7VxTZos1biINze2eijXX1qJ5zSlAEIbtbCbH12+9NPSfkRFHty4pZphgCka0/res/mI/wU6TNcweHnomjyJ2vQ== dependencies: "@emurgo/cardano-serialization-lib-asmjs" "^12.0.0-beta.2" "@fontsource/poppins" "^5.0.14" @@ -2173,15 +2173,10 @@ resolved "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz" integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== -"@parcel/watcher-linux-x64-glibc@2.5.0": - version "2.5.0" - resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz" - integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw== - -"@parcel/watcher-linux-x64-musl@2.5.0": +"@parcel/watcher-darwin-arm64@2.5.0": version "2.5.0" - resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz" - integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA== + resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz" + integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw== "@parcel/watcher@^2.4.1": version "2.5.0" @@ -2294,15 +2289,10 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-linux-x64-gnu@4.34.9": +"@rollup/rollup-darwin-arm64@4.34.9": version "4.34.9" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz" - integrity sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A== - -"@rollup/rollup-linux-x64-musl@4.34.9": - version "4.34.9" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz" - integrity sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA== + resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz" + integrity sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -2905,15 +2895,10 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" -"@swc/core-linux-x64-gnu@1.9.3": - version "1.9.3" - resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz" - integrity sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w== - -"@swc/core-linux-x64-musl@1.9.3": +"@swc/core-darwin-arm64@1.9.3": version "1.9.3" - resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz" - integrity sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg== + resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz" + integrity sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w== "@swc/core@*", "@swc/core@^1.5.22", "@swc/core@^1.7.26": version "1.9.3" @@ -6401,6 +6386,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escodegen@^1.8.1: version "1.14.3" resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz" @@ -7216,6 +7206,16 @@ fs@^0.0.1-security: resolved "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz" integrity sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w== +fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -9924,6 +9924,16 @@ mdast-util-definitions@^5.0.0: "@types/unist" "^2.0.0" unist-util-visit "^4.0.0" +mdast-util-find-and-replace@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + mdast-util-from-markdown@^1.0.0: version "1.3.1" resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz" @@ -10015,6 +10025,14 @@ mdast-util-mdxjs-esm@^2.0.0: mdast-util-from-markdown "^2.0.0" mdast-util-to-markdown "^2.0.0" +mdast-util-newline-to-break@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz" + integrity sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-find-and-replace "^3.0.0" + mdast-util-phrasing@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz" @@ -12619,6 +12637,15 @@ release-zalgo@^1.0.0: dependencies: es6-error "^4.0.1" +remark-breaks@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz" + integrity sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-newline-to-break "^2.0.0" + unified "^11.0.0" + remark-math@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz" diff --git a/govtool/metadata-validation/package-lock.json b/govtool/metadata-validation/package-lock.json index 83e662ff1..f35ffe507 100644 --- a/govtool/metadata-validation/package-lock.json +++ b/govtool/metadata-validation/package-lock.json @@ -1,12 +1,12 @@ { "name": "@govtool/metadata-validation", - "version": "2.0.19", + "version": "2.0.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@govtool/metadata-validation", - "version": "2.0.19", + "version": "2.0.20", "license": "UNLICENSED", "dependencies": { "@nestjs/axios": "^3.0.2", diff --git a/govtool/metadata-validation/package.json b/govtool/metadata-validation/package.json index c3b69cd67..6601acc07 100644 --- a/govtool/metadata-validation/package.json +++ b/govtool/metadata-validation/package.json @@ -1,6 +1,6 @@ { "name": "@govtool/metadata-validation", - "version": "2.0.19", + "version": "2.0.20", "description": "", "author": "", "private": true, diff --git a/govtool/metadata-validation/src/app.service.ts b/govtool/metadata-validation/src/app.service.ts index a44669a89..82f0bb1af 100644 --- a/govtool/metadata-validation/src/app.service.ts +++ b/govtool/metadata-validation/src/app.service.ts @@ -7,7 +7,7 @@ import * as jsonld from 'jsonld'; import { ValidateMetadataDTO } from '@dto'; import { LoggerMessage, MetadataValidationStatus } from '@enums'; import { validateMetadataStandard, parseMetadata, getStandard } from '@utils'; -import { ValidateMetadataResult } from '@types'; +import { MetadataStandard, ValidateMetadataResult } from '@types'; @Injectable() export class AppService { @@ -57,6 +57,13 @@ export class AppService { throw MetadataValidationStatus.INCORRECT_FORMAT; } + if ( + standard === MetadataStandard.CIP108 && + !Array.isArray(parsedData.authors) + ) { + throw MetadataValidationStatus.INCORRECT_FORMAT; + } + if (!parsedData?.body) { throw MetadataValidationStatus.INCORRECT_FORMAT; } diff --git a/govtool/metadata-validation/src/main.ts b/govtool/metadata-validation/src/main.ts index 627d3af87..f1c7a0dd2 100644 --- a/govtool/metadata-validation/src/main.ts +++ b/govtool/metadata-validation/src/main.ts @@ -13,7 +13,7 @@ async function bootstrap() { const config = new DocumentBuilder() .setTitle('Metadata Validation Tool') .setDescription('The Metadata Validation Tool API description') - .setVersion("2.0.19") + .setVersion("2.0.20") .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts index 9ac2efb25..44a50c317 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts @@ -144,9 +144,6 @@ export default class BudgetDiscussionDetailsPage { await this.readMoreBtn.click(); // proposal ownership validation - await expect(this.publicProposalChampionContent).toHaveText( - budgetProposal.proposalOwnership.publicChampion - ); await expect(this.socialHandlesContent).toHaveText( budgetProposal.proposalOwnership.contactDetails ); diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts index 9d2ff5197..3376c2b7c 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts @@ -1,5 +1,5 @@ import environments from "@constants/environments"; -import { fa, faker } from "@faker-js/faker"; +import { faker } from "@faker-js/faker"; import { extractProposalIdFromUrl } from "@helpers/string"; import { Page, expect } from "@playwright/test"; import { @@ -11,11 +11,11 @@ import { BudgetProposalOwnershipProps, BudgetProposalProblemStatementAndBenefitProps, BudgetProposalProps, + BudgetProposalStageEnum, CommitteeAlignmentEnum, CompanyEnum, LocationEnum, PreferredCurrencyEnum, - ProposalChampionEnum, ProposalContractingEnum, ProposalLink, RoadmapNameEnum, @@ -163,7 +163,7 @@ export default class BudgetDiscussionSubmissionPage { this.page.getByTestId("preferred-currency"); readonly intersectNamedAdministratorSelect = this.page.getByTestId( - "itersect-named-administrator" + "intersect-named-administrator" ); // content @@ -310,11 +310,6 @@ export default class BudgetDiscussionSubmissionPage { .getByRole("option", { name: proposalOwnership.companyType }) .click(); //BUG missing testId - await this.publicChampionSelect.click(); - await this.page - .getByRole("option", { name: proposalOwnership.publicChampion }) - .click(); //BUG missing testId - await this.contactDetailsInput.fill(proposalOwnership.contactDetails); if (proposalOwnership.companyType === "Group") { @@ -446,29 +441,28 @@ export default class BudgetDiscussionSubmissionPage { await this.continueBtn.click(); } - async fillupForm(budgetProposal: BudgetProposalProps, stage = 8) { - await this.fillupContactInformationForm(budgetProposal.contactInformation); - - if (stage > 2) { - await this.fillupProposalOwnershipForm(budgetProposal.proposalOwnership); - } + async fillupForm( + budgetProposal: BudgetProposalProps, + stage: BudgetProposalStageEnum = BudgetProposalStageEnum.AdministrationAndAuditing + ) { + await this.fillupProposalOwnershipForm(budgetProposal.proposalOwnership); - if (stage > 3) { + if (stage > BudgetProposalStageEnum.ProposalOwnership) { await this.fillupProblemStatementAndBenefitsForm( budgetProposal.problemStatementAndBenefits ); } - if (stage > 4) { + if (stage > BudgetProposalStageEnum.ProblemStatementAndBenefits) { await this.fillupProposalDetailsForm(budgetProposal.proposalDetails); } - if (stage > 5) { + if (stage > BudgetProposalStageEnum.ProposalDetails) { await this.fillupCostingForm(budgetProposal.costing); } - if (stage > 6) { + if (stage > BudgetProposalStageEnum.Costing) { await this.fillupFurtherInformation(budgetProposal.furtherInformation); } - if (stage > 7) { + if (stage > BudgetProposalStageEnum.FurtherInformation) { await this.intersectNamedAdministratorSelect.click(); await this.page @@ -558,9 +552,6 @@ export default class BudgetDiscussionSubmissionPage { generateValidProposalOwnerShip(): BudgetProposalOwnershipProps { return { companyType: faker.helpers.arrayElement(Object.values(CompanyEnum)), - publicChampion: faker.helpers.arrayElement( - Object.values(ProposalChampionEnum) - ), contactDetails: faker.internet.email(), groupName: faker.company.name(), groupType: faker.company.buzzVerb(), @@ -623,7 +614,6 @@ export default class BudgetDiscussionSubmissionPage { generateValidBudgetProposalInformation(): BudgetProposalProps { return { - contactInformation: this.generateValidBudgetProposalContactInformation(), proposalOwnership: this.generateValidProposalOwnerShip(), problemStatementAndBenefits: this.generateValidBudgetProposalProblemStatementAndBenefits(), @@ -640,9 +630,7 @@ export default class BudgetDiscussionSubmissionPage { if (fillAllDetails) { await this.fillupForm(budgetProposal); } else { - await this.fillupContactInformationForm( - budgetProposal.contactInformation - ); + await this.fillupProposalOwnershipForm(budgetProposal.proposalOwnership); } await this.saveDraftBtn.click(); @@ -650,7 +638,7 @@ export default class BudgetDiscussionSubmissionPage { await this.cancelBtn.click(); await this.createBudgetProposalBtn.click(); - return fillAllDetails ? budgetProposal : budgetProposal.contactInformation; + return fillAllDetails ? budgetProposal : budgetProposal.proposalOwnership; } async createBudgetProposal(): Promise<{ @@ -678,42 +666,10 @@ export default class BudgetDiscussionSubmissionPage { async validateReviewBudgetProposal( proposalInformations: BudgetProposalProps ) { - // contact information - await expect(this.beneficiaryFullNameContent).toHaveText( - proposalInformations.contactInformation.beneficiaryFullName - ); - await expect(this.beneficiaryCountryOfResidenceContent).toHaveText( - proposalInformations.contactInformation.beneficiaryCountry - ); - //BUG missing testId - await expect( - this.currentPage.getByText( - proposalInformations.contactInformation.beneficiaryNationality, - { exact: true } - ) - ).toBeVisible(); - //BUG missing testId - await expect( - this.currentPage.getByText( - proposalInformations.contactInformation.submissionLeadEmail, - { exact: true } - ) - ).toBeVisible(); - //BUG missing testId - await expect( - this.currentPage.getByText( - proposalInformations.contactInformation.submissionLeadFullName, - { exact: true } - ) - ).toBeVisible(); - // proposal ownership await expect(this.companyTypeContent).toHaveText( proposalInformations.proposalOwnership.companyType ); - await expect(this.providePreferredContent).toHaveText( - proposalInformations.proposalOwnership.publicChampion - ); await expect(this.socialHandlesContent).toHaveText( proposalInformations.proposalOwnership.contactDetails ); diff --git a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts index 21dc76fe5..13c95ecab 100644 --- a/tests/govtool-frontend/playwright/lib/pages/loginPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/loginPage.ts @@ -47,7 +47,9 @@ export default class LoginPage { // Handle multiple stake keys if (stakeKeys.length > 1) { await this.page - .getByTestId(`${rewardAddresses[0]}-radio`).locator('div').first() + .getByTestId(`${rewardAddresses[0]}-radio`) + .locator("div") + .first() .click({ force: true }); await this.page.getByTestId("select-button").click(); } diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts index 4efb623d6..eafc59ca8 100644 --- a/tests/govtool-frontend/playwright/lib/types.ts +++ b/tests/govtool-frontend/playwright/lib/types.ts @@ -352,17 +352,8 @@ export enum CompanyEnum { Group = "Group", } -export type ProposalChampionType = - | "Beneficiary listed above" - | "Submission lead listed above"; -export enum ProposalChampionEnum { - BeneficiaryListedAbove = "Beneficiary listed above", - SubmissionLeadListedAbove = "Submission lead listed above", -} - export interface BudgetProposalOwnershipProps { companyType: CompanyType; - publicChampion: ProposalChampionType; contactDetails: string; groupName?: string; groupType?: string; @@ -510,7 +501,6 @@ export interface AdministrationAndAuditingProps { } export interface BudgetProposalProps { - contactInformation: BudgetProposalContactInformationProps; proposalOwnership: BudgetProposalOwnershipProps; problemStatementAndBenefits: BudgetProposalProblemStatementAndBenefitProps; proposalDetails: BudgetProposalDetailsProps; @@ -518,3 +508,13 @@ export interface BudgetProposalProps { furtherInformation: Array; administrationAndAuditing: AdministrationAndAuditingProps; } + +export enum BudgetProposalStageEnum { + ProposalOwnership = 1, + ProblemStatementAndBenefits = 2, + ProposalDetails = 3, + Costing = 4, + FurtherInformation = 5, + AdministrationAndAuditing = 6, + Review = 7, +} diff --git a/tests/govtool-frontend/playwright/package-lock.json b/tests/govtool-frontend/playwright/package-lock.json index 74d2b854d..a186f77ce 100644 --- a/tests/govtool-frontend/playwright/package-lock.json +++ b/tests/govtool-frontend/playwright/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@cardanoapi/cardano-test-wallet": "^3.0.0", + "@cardanoapi/cardano-test-wallet": "^3.2.0", "@faker-js/faker": "^8.4.1", "@noble/curves": "^1.3.0", "@noble/ed25519": "^2.0.0", @@ -43,9 +43,9 @@ } }, "node_modules/@cardanoapi/cardano-test-wallet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@cardanoapi/cardano-test-wallet/-/cardano-test-wallet-3.0.0.tgz", - "integrity": "sha512-5cQPQPrsco8eeDBIQI74jiAVl48hI0E3u2JSmya9PvRL/D18LUPqanmhpDScD8fpWRzS7MKSAhqoqsxTlgWYOw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@cardanoapi/cardano-test-wallet/-/cardano-test-wallet-3.2.0.tgz", + "integrity": "sha512-tstRrWlfxatJ12Ra4Y3u4sMLwTyGWl7AYw8ix8H8pdxWToU2of8PWNQrRQ2fE40j2RJ0Ul5XLlMFOSLFrkybxA==", "license": "MIT" }, "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { @@ -1008,9 +1008,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "funding": [ { @@ -1028,10 +1028,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -1082,9 +1082,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001658", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz", - "integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==", + "version": "1.0.30001713", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", + "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", "dev": true, "funding": [ { @@ -1377,9 +1377,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.16", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.16.tgz", - "integrity": "sha512-2gQpi2WYobXmz2q23FrOBYTLcI1O/P4heW3eqX+ldmPVDQELRqhiebV380EhlGG12NtnX1qbK/FHpN0ba+7bLA==", + "version": "1.5.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", "dev": true, "license": "ISC" }, @@ -2477,9 +2477,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, @@ -2637,9 +2637,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -3516,9 +3516,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -3536,8 +3536,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" diff --git a/tests/govtool-frontend/playwright/package.json b/tests/govtool-frontend/playwright/package.json index a57cd1b79..03997d2e4 100644 --- a/tests/govtool-frontend/playwright/package.json +++ b/tests/govtool-frontend/playwright/package.json @@ -29,7 +29,7 @@ "generate-wallets": "ts-node ./generate_wallets.ts 24" }, "dependencies": { - "@cardanoapi/cardano-test-wallet": "^3.0.0", + "@cardanoapi/cardano-test-wallet": "^3.2.0", "@faker-js/faker": "^8.4.1", "@noble/curves": "^1.3.0", "@noble/ed25519": "^2.0.0", diff --git a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.spec.ts index 181b37138..698106567 100644 --- a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.spec.ts @@ -11,8 +11,9 @@ import BudgetDiscussionDetailsPage from "@pages/budgetDiscussionDetailsPage"; import BudgetDiscussionSubmissionPage from "@pages/budgetDiscussionSubmissionPage"; import { expect } from "@playwright/test"; import { - BudgetProposalContactInformationProps, + BudgetProposalOwnershipProps, BudgetProposalProps, + BudgetProposalStageEnum, CompanyEnum, } from "@types"; import { allure } from "allure-playwright"; @@ -47,38 +48,12 @@ test.describe("Budget proposal 01 wallet", () => { }); test.describe("Budget proposal field verification", () => { - test("12D_1. Should verify all field of “contact information” section", async () => { - await expect( - budgetProposalSubmissionPage.beneficiaryFullNameInput - ).toBeVisible(); - await expect( - budgetProposalSubmissionPage.beneficiaryEmailInput - ).toBeVisible(); - await expect( - budgetProposalSubmissionPage.beneficiaryCountrySelect - ).toBeVisible(); - await expect( - budgetProposalSubmissionPage.beneficiaryNationalitySelect - ).toBeVisible(); - await expect( - budgetProposalSubmissionPage.submissionLeadFullNameInput - ).toBeVisible(); - }); - - test("12D_2. Should verify all field of “proposal ownership” section", async () => { - const proposalContactInformationContent = - budgetProposalSubmissionPage.generateValidBudgetProposalContactInformation(); - await budgetProposalSubmissionPage.fillupContactInformationForm( - proposalContactInformationContent - ); - + test("12D_1. Should verify all field of “proposal ownership” section", async () => { // default field await expect( budgetProposalSubmissionPage.companyTypeSelect ).toBeVisible(); - await expect( - budgetProposalSubmissionPage.publicChampionSelect - ).toBeVisible(); + await expect( budgetProposalSubmissionPage.contactDetailsInput ).toBeVisible(); @@ -111,10 +86,12 @@ test.describe("Budget proposal 01 wallet", () => { ).toBeVisible(); }); - test("12D_3. Should verify all field of “problem statements and proposal benefits” section", async () => { - const proposalInformation = - budgetProposalSubmissionPage.generateValidBudgetProposalInformation(); - await budgetProposalSubmissionPage.fillupForm(proposalInformation, 3); + test("12D_2. Should verify all field of “problem statements and proposal benefits” section", async () => { + const proposalOwnership = + budgetProposalSubmissionPage.generateValidProposalOwnerShip(); + await budgetProposalSubmissionPage.fillupProposalOwnershipForm( + proposalOwnership + ); await expect( budgetProposalSubmissionPage.problemStatementInput @@ -136,10 +113,13 @@ test.describe("Budget proposal 01 wallet", () => { ).toBeVisible(); }); - test("12D_4. Should verify all field of “proposal details” section", async () => { + test("12D_3. Should verify all field of “proposal details” section", async () => { const proposalInformation = budgetProposalSubmissionPage.generateValidBudgetProposalInformation(); - await budgetProposalSubmissionPage.fillupForm(proposalInformation, 4); + await budgetProposalSubmissionPage.fillupForm( + proposalInformation, + BudgetProposalStageEnum.ProblemStatementAndBenefits + ); await expect( budgetProposalSubmissionPage.proposalNameInput @@ -164,10 +144,13 @@ test.describe("Budget proposal 01 wallet", () => { ).toBeVisible(); }); - test("12D_5. Should verify all field of “costing” section", async () => { + test("12D_4. Should verify all field of “costing” section", async () => { const proposalInformation = budgetProposalSubmissionPage.generateValidBudgetProposalInformation(); - await budgetProposalSubmissionPage.fillupForm(proposalInformation, 5); + await budgetProposalSubmissionPage.fillupForm( + proposalInformation, + BudgetProposalStageEnum.ProposalDetails + ); await expect(budgetProposalSubmissionPage.adaAmountInput).toBeVisible(); await expect( @@ -184,20 +167,26 @@ test.describe("Budget proposal 01 wallet", () => { ).toBeVisible(); }); - test("12D_6. Should verify all field of “further information” section", async () => { + test("12D_5. Should verify all field of “further information” section", async () => { const proposalInformation = budgetProposalSubmissionPage.generateValidBudgetProposalInformation(); - await budgetProposalSubmissionPage.fillupForm(proposalInformation, 6); + await budgetProposalSubmissionPage.fillupForm( + proposalInformation, + BudgetProposalStageEnum.Costing + ); await expect(budgetProposalSubmissionPage.linkTextInput).toBeVisible(); await expect(budgetProposalSubmissionPage.linkUrlInput).toBeVisible(); await expect(budgetProposalSubmissionPage.addLinkBtn).toBeVisible(); }); - test("12D_7. Should verify all field of “administration and auditing” section", async () => { + test("12D_6. Should verify all field of “administration and auditing” section", async () => { const proposalInformation = budgetProposalSubmissionPage.generateValidBudgetProposalInformation(); - await budgetProposalSubmissionPage.fillupForm(proposalInformation, 7); + await budgetProposalSubmissionPage.fillupForm( + proposalInformation, + BudgetProposalStageEnum.FurtherInformation + ); await expect( budgetProposalSubmissionPage.intersectNamedAdministratorSelect @@ -247,32 +236,44 @@ test("12C. Should save and view draft proposal", async ({ browser }) => { const budgetSubmissionPage = new BudgetDiscussionSubmissionPage(page); await budgetSubmissionPage.goto(); - const draftContactInformationContent = - (await budgetSubmissionPage.createDraftBudgetProposal()) as BudgetProposalContactInformationProps; + const draftProposalOwnership = + (await budgetSubmissionPage.createDraftBudgetProposal()) as BudgetProposalOwnershipProps; const getAddDrafts = await budgetSubmissionPage.getAllDrafts(); expect(getAddDrafts.length).toBeGreaterThan(0); await budgetSubmissionPage.viewLastDraft(); - await expect(budgetSubmissionPage.beneficiaryFullNameInput).toHaveValue( - draftContactInformationContent.beneficiaryFullName + await expect(budgetSubmissionPage.companyTypeSelect).toHaveText( + draftProposalOwnership.companyType ); - await expect(budgetSubmissionPage.beneficiaryEmailInput).toHaveValue( - draftContactInformationContent.beneficiaryEmail - ); - await expect(budgetSubmissionPage.beneficiaryCountrySelect).toHaveText( - draftContactInformationContent.beneficiaryCountry - ); - await expect(budgetSubmissionPage.beneficiaryNationalitySelect).toHaveText( - draftContactInformationContent.beneficiaryNationality - ); - await expect(budgetSubmissionPage.submissionLeadFullNameInput).toHaveValue( - draftContactInformationContent.submissionLeadFullName - ); - await expect(budgetSubmissionPage.submissionLeadEmailInput).toHaveValue( - draftContactInformationContent.submissionLeadEmail + + await expect(budgetSubmissionPage.contactDetailsInput).toHaveValue( + draftProposalOwnership.contactDetails ); + + if (draftProposalOwnership.companyType === "Group") { + await expect(budgetSubmissionPage.groupNameInput).toHaveValue( + draftProposalOwnership.groupName + ); + await expect(budgetSubmissionPage.groupTypeInput).toHaveValue( + draftProposalOwnership.groupType + ); + await expect(budgetSubmissionPage.keyInformationOfGroupInput).toHaveValue( + draftProposalOwnership.groupKeyIdentity + ); + } + if (draftProposalOwnership.companyType === "Company") { + await expect(budgetSubmissionPage.companyNameInput).toHaveValue( + draftProposalOwnership.companyName + ); + await expect(budgetSubmissionPage.companyDomainNameInput).toHaveValue( + draftProposalOwnership.companyDomainName + ); + await expect(budgetSubmissionPage.countryOfIncorporationBtn).toHaveText( + draftProposalOwnership.countryOfIncorportation + ); + } }); test("12H. Should submit a valid budget proposal", async ({ browser }) => { @@ -310,7 +311,7 @@ test("12I. Should submit a valid draft budget proposal", async ({ await budgetSubmissionPage.viewLastDraft(); - for (let i = 0; i < 8; i++) { + for (let i = 0; i < 7; i++) { await budgetSubmissionPage.continueBtn.click(); } await budgetSubmissionPage.submitBtn.click();