From e3ae5e3c76efc87024faed88e522995167ad58b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Thu, 4 Apr 2024 15:28:05 +0200 Subject: [PATCH 001/143] fix resolutions --- .../src/components/organisms/Hero.tsx | 28 +++++++++++++------ .../src/components/organisms/HomeCards.tsx | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/govtool/frontend/src/components/organisms/Hero.tsx b/govtool/frontend/src/components/organisms/Hero.tsx index 6ef7aa1b9..739c80b3f 100644 --- a/govtool/frontend/src/components/organisms/Hero.tsx +++ b/govtool/frontend/src/components/organisms/Hero.tsx @@ -32,7 +32,19 @@ export const Hero = () => { flexDirection="row" overflow="visible" position="relative" - px={screenWidth < 640 ? 3 : screenWidth < 1512 ? 10 : 14} + px={ + screenWidth < 640 + ? 3 + : screenWidth < 1512 + ? 9.375 + : screenWidth < 1728 + ? 14 + : screenWidth < 1920 + ? 27.375 + : screenWidth < 2560 + ? 39.375 + : 49.25 + } > { flex={1} position="absolute" right={ - screenWidth >= 1728 - ? IMAGE_SIZE / 8 - : screenWidth >= 1512 - ? -(IMAGE_SIZE / 12) - : screenWidth >= 860 - ? -(IMAGE_SIZE / 8) - : -(IMAGE_SIZE / 4) + screenWidth <= 860 + ? -(IMAGE_SIZE / 4) + : screenWidth <= 1440 + ? -(IMAGE_SIZE / 15) + : screenWidth <= 1728 + ? screenWidth / 20 + : screenWidth / 11 } top={-80} zIndex={-1} diff --git a/govtool/frontend/src/components/organisms/HomeCards.tsx b/govtool/frontend/src/components/organisms/HomeCards.tsx index 8ea58947c..d3316679c 100644 --- a/govtool/frontend/src/components/organisms/HomeCards.tsx +++ b/govtool/frontend/src/components/organisms/HomeCards.tsx @@ -60,7 +60,7 @@ export const HomeCards = () => { ? "repeat(1, minmax(300px, 866px))" : "repeat(2, minmax(300px, 866px))" } - justifyItems="center" + justifyContent="center" mb={screenWidth < 640 ? 10 : 17} mt={screenWidth < 640 ? 10 : 14.5} px={ From 0d5449187fc844212435ec7c10ecc10ff590ad25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Fri, 5 Apr 2024 10:20:01 +0200 Subject: [PATCH 002/143] replace ternary operator with if statement --- .../src/components/organisms/Hero.tsx | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/govtool/frontend/src/components/organisms/Hero.tsx b/govtool/frontend/src/components/organisms/Hero.tsx index 739c80b3f..e870a5ae9 100644 --- a/govtool/frontend/src/components/organisms/Hero.tsx +++ b/govtool/frontend/src/components/organisms/Hero.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; import { Box, Link } from "@mui/material"; @@ -17,11 +17,24 @@ export const Hero = () => { const { t } = useTranslation(); const IMAGE_SIZE = screenWidth < 640 ? 300 : screenWidth < 860 ? 400 : 600; - const onClickVotingPower = useCallback( - () => - openInNewTab("https://docs.sanchogov.tools/faqs/what-is-voting-power"), - [], - ); + const paddingHorizontal = useMemo(() => { + if (screenWidth < 640) return 3; + if (screenWidth < 1512) return 9.375; + if (screenWidth < 1728) return 14; + if (screenWidth < 1920) return 27.375; + if (screenWidth < 2560) return 39.375; + return 49.25; + }, [screenWidth]); + + const imageRightMargin = useMemo(() => { + if (screenWidth <= 860) return -(IMAGE_SIZE / 4); + if (screenWidth <= 1440) return -(IMAGE_SIZE / 15); + if (screenWidth <= 1728) return screenWidth / 20; + return screenWidth / 11; + }, [screenWidth]); + + const onClickVotingPower = () => + openInNewTab("https://docs.sanchogov.tools/faqs/what-is-voting-power"); return ( { flexDirection="row" overflow="visible" position="relative" - px={ - screenWidth < 640 - ? 3 - : screenWidth < 1512 - ? 9.375 - : screenWidth < 1728 - ? 14 - : screenWidth < 1920 - ? 27.375 - : screenWidth < 2560 - ? 39.375 - : 49.25 - } + px={paddingHorizontal} > { From 8e9ab20d96a5388bd1aad2fd5efaadaf5d2dd474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Tue, 27 Feb 2024 11:10:14 +0100 Subject: [PATCH 003/143] [#216] add Automated Voting Options component --- CHANGELOG.md | 4 + .../molecules/AutomatedVotingCard.tsx | 100 ++++++++++++++++++ .../src/components/molecules/index.ts | 1 + .../src/components/molecules/types.ts | 8 ++ .../organisms/AutomatedVotingOptions.tsx | 70 ++++++++++++ .../src/components/organisms/index.ts | 1 + govtool/frontend/src/i18n/locales/en.ts | 12 ++- 7 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx create mode 100644 govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index c555e2791..cb3c92587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ changes. - Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151) - Abandoning GA creation [Issue 359](https://github.com/IntersectMBO/govtool/issues/359) +- Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358) +- Create Automated Voting Options component [Issue 216](https://github.com/IntersectMBO/govtool/issues/216) +- Change step 3 components [Issue 152](https://github.com/intersectMBO/govtool/issues/152) +- Add possibility to vote on behalf of myself - Sole Voter [Issue 119](https://github.com/IntersectMBO/govtool/issues/119) - Create DRep registration page about roles [Issue 205](https://github.com/IntersectMBO/govtool/issues/205) - Create Checkbox component. Improve Field and ControlledField [Issue 177](https://github.com/IntersectMBO/govtool/pull/177) - Vitest unit tests added for utils functions [Issue 81](https://github.com/IntersectMBO/govtool/issues/81) diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx new file mode 100644 index 000000000..7deef9a44 --- /dev/null +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -0,0 +1,100 @@ +import { Box, Divider } from "@mui/material"; + +import { AutomatedVotingCardProps } from "./types"; +import { Button, Spacer, Typography } from "@atoms"; +import { useScreenDimension, useTranslation } from "@hooks"; + +export const AutomatedVotingCard = ({ + description, + onClickDelegate, + onClickInfo, + title, + votingPower, +}: AutomatedVotingCardProps) => { + const { isMobile, screenWidth } = useScreenDimension(); + const { t } = useTranslation(); + + return ( + + + {title} + + {description} + + + + + + {t("dRepDirectory.votingPower")} + + + + {votingPower} + + + + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/index.ts b/govtool/frontend/src/components/molecules/index.ts index dad2697b0..c99a89db7 100644 --- a/govtool/frontend/src/components/molecules/index.ts +++ b/govtool/frontend/src/components/molecules/index.ts @@ -1,4 +1,5 @@ export * from "./ActionCard"; +export * from "./AutomatedVotingCard"; export * from "./Breadcrumbs"; export * from "./Card"; export * from "./CenteredBoxBottomButtons"; diff --git a/govtool/frontend/src/components/molecules/types.ts b/govtool/frontend/src/components/molecules/types.ts index f67f05ba1..68a0bc73c 100644 --- a/govtool/frontend/src/components/molecules/types.ts +++ b/govtool/frontend/src/components/molecules/types.ts @@ -26,3 +26,11 @@ export type EmptyStateGovernanceActionsCategoryProps = { category?: string; isSearch?: boolean; }; + +export type AutomatedVotingCardProps = { + description: string; + onClickDelegate: () => void; + onClickInfo: () => void; + title: string; + votingPower: string | number; +}; diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx new file mode 100644 index 000000000..4848be2de --- /dev/null +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { Box } from "@mui/material"; + +import { Spacer, Typography } from "@atoms"; +import { ICONS } from "@consts"; +import { useTranslation } from "@hooks"; +import { AutomatedVotingCard } from "@molecules"; + +export const AutomatedVotingOptions = () => { + const [isOpen, setIsOpen] = useState(false); + const { t } = useTranslation(); + + const handleOpen = () => { + setIsOpen((prev) => !prev); + }; + + return ( + + + {t("dRepDirectory.automatedVotingOptions")} + arrow + + {isOpen ? ( + <> + + {}} + onClickInfo={() => {}} + title={t("dRepDirectory.abstainCardTitle")} + votingPower={"99,111,111"} + /> + + {}} + onClickInfo={() => {}} + title={t("dRepDirectory.noConfidenceTitle")} + votingPower={"99,111,111"} + /> + + ) : null} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index f4931d142..bcb646f55 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -1,3 +1,4 @@ +export * from "./AutomatedVotingOptions"; export * from "./BgCard"; export * from "./ChooseStakeKeyPanel"; export * from "./ChooseWalletModal"; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 1188ff912..c4257f302 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -249,6 +249,15 @@ export const en = { title: "Delegate to myself", }, }, + dRepDirectory: { + abstainCardDescription: "Select this to vote ABSTAIN to every vote.", + abstainCardTitle: "Abstain from Every Vote", + automatedVotingOptions: "Automated Voting Options", + noConfidenceDescription: + "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", + noConfidenceTitle: "Signal No Confidence on Every Vote", + votingPower: "Voting Power", + }, errorPage: { backToDashboard: "Back to dashboard", backToHomepage: "Back to homepage", @@ -711,7 +720,8 @@ export const en = { filter: "Filter", goBack: "Go back", here: "here", - inProgress: "In Progress", + info: "Info", + inProgress: "In progress", learnMore: "Learn more", loading: "Loading...", myDRepId: "My dRep ID:", From 1c4012debfe0e935d5cd47ca5534cf3dd2e8308c Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Wed, 6 Mar 2024 14:10:17 +0100 Subject: [PATCH 004/143] [#217] add dRep directory page and nav items --- .../frontend/public/icons/DRepDirectory.svg | 13 ++++++++ .../public/icons/DRepDirectoryActive.svg | 13 ++++++++ govtool/frontend/src/App.tsx | 6 ++++ .../src/components/atoms/ContentBox.tsx | 8 +++++ .../src/components/atoms/PagePaddingBox.tsx | 8 +++++ .../frontend/src/components/atoms/index.ts | 2 ++ .../molecules/AutomatedVotingCard.tsx | 2 +- .../src/components/molecules/PageTitle.tsx | 24 ++++++++++++++ .../src/components/molecules/index.ts | 1 + .../organisms/AutomatedVotingOptions.tsx | 4 +-- .../organisms/DRepDirectoryContent.tsx | 17 ++++++++++ .../src/components/organisms/index.ts | 1 + govtool/frontend/src/consts/icons.ts | 2 ++ govtool/frontend/src/consts/navItems.ts | 13 ++++++++ govtool/frontend/src/consts/paths.ts | 2 ++ govtool/frontend/src/i18n/locales/en.ts | 1 + govtool/frontend/src/pages/DRepDirectory.tsx | 33 +++++++++++++++++++ govtool/frontend/src/pages/index.ts | 2 ++ 18 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 govtool/frontend/public/icons/DRepDirectory.svg create mode 100644 govtool/frontend/public/icons/DRepDirectoryActive.svg create mode 100644 govtool/frontend/src/components/atoms/ContentBox.tsx create mode 100644 govtool/frontend/src/components/atoms/PagePaddingBox.tsx create mode 100644 govtool/frontend/src/components/molecules/PageTitle.tsx create mode 100644 govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx create mode 100644 govtool/frontend/src/pages/DRepDirectory.tsx diff --git a/govtool/frontend/public/icons/DRepDirectory.svg b/govtool/frontend/public/icons/DRepDirectory.svg new file mode 100644 index 000000000..b6cf0553d --- /dev/null +++ b/govtool/frontend/public/icons/DRepDirectory.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/govtool/frontend/public/icons/DRepDirectoryActive.svg b/govtool/frontend/public/icons/DRepDirectoryActive.svg new file mode 100644 index 000000000..7e012314e --- /dev/null +++ b/govtool/frontend/public/icons/DRepDirectoryActive.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index c73ca3bfb..fc20a4611 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -16,6 +16,7 @@ import { Dashboard, DashboardGovernanceActionsCategory, DelegateTodRep, + DRepDirectory, ErrorPage, GovernanceActionDetails, GovernanceActions, @@ -104,7 +105,12 @@ export default () => { path={PATHS.dashboardGovernanceActionsCategory} element={} /> + } + /> + } /> } diff --git a/govtool/frontend/src/components/atoms/ContentBox.tsx b/govtool/frontend/src/components/atoms/ContentBox.tsx new file mode 100644 index 000000000..c2f058766 --- /dev/null +++ b/govtool/frontend/src/components/atoms/ContentBox.tsx @@ -0,0 +1,8 @@ +import { Box, BoxProps } from "@mui/material"; +import { FC } from "react"; + +export const ContentBox: FC = ({ children, ...props }) => ( + + {children} + +); diff --git a/govtool/frontend/src/components/atoms/PagePaddingBox.tsx b/govtool/frontend/src/components/atoms/PagePaddingBox.tsx new file mode 100644 index 000000000..72f0abc4c --- /dev/null +++ b/govtool/frontend/src/components/atoms/PagePaddingBox.tsx @@ -0,0 +1,8 @@ +import { Box, BoxProps } from "@mui/material"; +import { FC } from "react"; + +export const PagePaddingBox: FC = ({ children, ...props }) => ( + + {children} + +); diff --git a/govtool/frontend/src/components/atoms/index.ts b/govtool/frontend/src/components/atoms/index.ts index 186752f80..b91dec294 100644 --- a/govtool/frontend/src/components/atoms/index.ts +++ b/govtool/frontend/src/components/atoms/index.ts @@ -2,6 +2,7 @@ export * from "./ActionRadio"; export * from "./Background"; export * from "./Button"; export * from "./Checkbox"; +export * from "./ContentBox"; export * from "./CopyButton"; export * from "./DrawerLink"; export * from "./ExternalModalButton"; @@ -16,6 +17,7 @@ export * from "./modal/Modal"; export * from "./modal/ModalContents"; export * from "./modal/ModalHeader"; export * from "./modal/ModalWrapper"; +export * from "./PagePaddingBox"; export * from "./Radio"; export * from "./ScrollToManage"; export * from "./ScrollToTop"; diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx index 7deef9a44..0ac7d78ec 100644 --- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -1,8 +1,8 @@ import { Box, Divider } from "@mui/material"; -import { AutomatedVotingCardProps } from "./types"; import { Button, Spacer, Typography } from "@atoms"; import { useScreenDimension, useTranslation } from "@hooks"; +import { AutomatedVotingCardProps } from "./types"; export const AutomatedVotingCard = ({ description, diff --git a/govtool/frontend/src/components/molecules/PageTitle.tsx b/govtool/frontend/src/components/molecules/PageTitle.tsx new file mode 100644 index 000000000..6105f2f61 --- /dev/null +++ b/govtool/frontend/src/components/molecules/PageTitle.tsx @@ -0,0 +1,24 @@ +import { useScreenDimension } from "@hooks"; +import { FC } from "react"; +import { Typography, PagePaddingBox, ContentBox } from "@/components/atoms"; + +interface PageTitleProps { + title: string; +} + +export const PageTitle: FC = ({ title }) => { + const { isMobile } = useScreenDimension(); + + return ( + `1px solid ${theme.palette.neutralWhite}`} + py={3} + > + + + {title} + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/index.ts b/govtool/frontend/src/components/molecules/index.ts index c99a89db7..ad45ca5b0 100644 --- a/govtool/frontend/src/components/molecules/index.ts +++ b/govtool/frontend/src/components/molecules/index.ts @@ -32,6 +32,7 @@ export * from "./SliderArrow"; export * from "./SliderArrows"; export * from "./SoleVoterAction"; export * from "./Step"; +export * from "./PageTitle"; export * from "./VoteActionForm"; export * from "./VotesSubmitted"; export * from "./WalletInfoCard"; diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index 4848be2de..2f5d8abcf 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -53,7 +53,7 @@ export const AutomatedVotingOptions = () => { onClickDelegate={() => {}} onClickInfo={() => {}} title={t("dRepDirectory.abstainCardTitle")} - votingPower={"99,111,111"} + votingPower="99,111,111" /> { onClickDelegate={() => {}} onClickInfo={() => {}} title={t("dRepDirectory.noConfidenceTitle")} - votingPower={"99,111,111"} + votingPower="99,111,111" /> ) : null} diff --git a/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx b/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx new file mode 100644 index 000000000..cfdbde3d1 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx @@ -0,0 +1,17 @@ +import { FC } from "react"; + +interface DRepDirectoryContentProps { + isConnected?: boolean; +} + +export const DRepDirectoryContent: FC = ({ + isConnected, +}) => ( + <> +

DRepDirectory

+

+ connected: + {String(!!isConnected)} +

+ +); diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index bcb646f55..e6cf14e92 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -15,6 +15,7 @@ export * from "./DelegateTodRepStepOne"; export * from "./DelegateTodRepStepTwo"; export * from "./Drawer"; export * from "./DrawerMobile"; +export * from "./DRepDirectoryContent"; export * from "./EditDRepInfoSteps"; export * from "./ExternalLinkModal"; export * from "./Footer"; diff --git a/govtool/frontend/src/consts/icons.ts b/govtool/frontend/src/consts/icons.ts index dfabf260d..02c6d5740 100644 --- a/govtool/frontend/src/consts/icons.ts +++ b/govtool/frontend/src/consts/icons.ts @@ -15,6 +15,8 @@ export const ICONS = { dashboardIcon: "/icons/Dashboard.svg", download: "/icons/Download.svg", drawerIcon: "/icons/DrawerIcon.svg", + dRepDirectoryActiveIcon: "/icons/DRepDirectoryActive.svg", + dRepDirectoryIcon: "/icons/DRepDirectory.svg", externalLinkIcon: "/icons/ExternalLink.svg", faqsActiveIcon: "/icons/FaqsActive.svg", faqsIcon: "/icons/Faqs.svg", diff --git a/govtool/frontend/src/consts/navItems.ts b/govtool/frontend/src/consts/navItems.ts index 1c3d0ec37..643f7a025 100644 --- a/govtool/frontend/src/consts/navItems.ts +++ b/govtool/frontend/src/consts/navItems.ts @@ -1,3 +1,4 @@ +import i18n from "@/i18n"; import { ICONS } from "./icons"; import { PATHS } from "./paths"; @@ -8,6 +9,11 @@ export const NAV_ITEMS = [ label: "Dashboard", newTabLink: null, }, + { + dataTestId: "drep-directory-link", + navTo: PATHS.dRepDirectory, + label: i18n.t("dRepDirectory.title"), + }, { dataTestId: "governance-actions-link", navTo: PATHS.governanceActions, @@ -37,6 +43,13 @@ export const CONNECTED_NAV_ITEMS = [ icon: ICONS.dashboardIcon, newTabLink: null, }, + { + dataTestId: "drep-directory-link", + label: i18n.t("dRepDirectory.title"), + navTo: PATHS.dashboardDRepDirectory, + activeIcon: ICONS.dRepDirectoryActiveIcon, + icon: ICONS.dRepDirectoryIcon, + }, { dataTestId: "governance-actions-link", label: "Governance Actions", diff --git a/govtool/frontend/src/consts/paths.ts b/govtool/frontend/src/consts/paths.ts index 3361f31a3..b58c4b545 100644 --- a/govtool/frontend/src/consts/paths.ts +++ b/govtool/frontend/src/consts/paths.ts @@ -5,7 +5,9 @@ export const PATHS = { dashboardGovernanceActionsAction: "/connected/governance_actions/:proposalId", dashboardGovernanceActionsCategory: "/connected/governance_actions/category/:category", + dashboardDRepDirectory: "/connected/drep_directory", delegateTodRep: "/delegate", + dRepDirectory: "/drep_directory", editDrepMetadata: "/edit_drep", error: "/error", faqs: "/faqs", diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index c4257f302..852d86a41 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -256,6 +256,7 @@ export const en = { noConfidenceDescription: "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", noConfidenceTitle: "Signal No Confidence on Every Vote", + title: "DRep Directory", votingPower: "Voting Power", }, errorPage: { diff --git a/govtool/frontend/src/pages/DRepDirectory.tsx b/govtool/frontend/src/pages/DRepDirectory.tsx new file mode 100644 index 000000000..71e4d38ec --- /dev/null +++ b/govtool/frontend/src/pages/DRepDirectory.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from "@hooks"; +import { checkIsWalletConnected } from "@/utils"; +import { Background, PagePaddingBox, ContentBox } from "@/components/atoms"; +import { DRepDirectoryContent, TopNav } from "@/components/organisms"; +import { PageTitle } from "@/components/molecules"; + +export const DRepDirectory = () => { + const { t } = useTranslation(); + + const isConnected = !checkIsWalletConnected(); + + if (isConnected) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + ); +}; diff --git a/govtool/frontend/src/pages/index.ts b/govtool/frontend/src/pages/index.ts index d1878f196..3bd34f2bb 100644 --- a/govtool/frontend/src/pages/index.ts +++ b/govtool/frontend/src/pages/index.ts @@ -3,12 +3,14 @@ export * from "./CreateGovernanceAction"; export * from "./Dashboard"; export * from "./DashboardGovernanceActionsCategory"; export * from "./DelegateTodRep"; +export * from "./DRepDirectory"; export * from "./EditDRepMetadata"; export * from "./ErrorPage"; export * from "./GovernanceActionDetails"; export * from "./GovernanceActions"; export * from "./GovernanceActionsCategory"; export * from "./Home"; +export * from "./RegisterAsSoleVoter"; export * from "./RegisterAsdRep"; export * from "./RegisterAsSoleVoter"; export * from "./RetireAsDrep"; From dea8c297af0a19cf9e47296d6e146deccffb7a57 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Fri, 8 Mar 2024 11:14:50 +0100 Subject: [PATCH 005/143] [#217] use translations in menu items --- govtool/frontend/src/consts/navItems.ts | 16 ++++++++-------- govtool/frontend/src/i18n/locales/en.ts | 2 -- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/govtool/frontend/src/consts/navItems.ts b/govtool/frontend/src/consts/navItems.ts index 643f7a025..4c6922b32 100644 --- a/govtool/frontend/src/consts/navItems.ts +++ b/govtool/frontend/src/consts/navItems.ts @@ -6,7 +6,7 @@ export const NAV_ITEMS = [ { dataTestId: "dashboard-link", navTo: PATHS.home, - label: "Dashboard", + label: i18n.t("dashboard.title"), newTabLink: null, }, { @@ -17,19 +17,19 @@ export const NAV_ITEMS = [ { dataTestId: "governance-actions-link", navTo: PATHS.governanceActions, - label: "Governance Actions", + label: i18n.t("govActions.title"), newTabLink: null, }, { dataTestId: "guides-link", navTo: "", - label: "Guides", + label: i18n.t("menu.guides"), newTabLink: "https://docs.sanchogov.tools/about/what-is-sanchonet-govtool", }, { dataTestId: "faqs-link", navTo: "", - label: "FAQs", + label: i18n.t("menu.faqs"), newTabLink: "https://docs.sanchogov.tools/faqs", }, ]; @@ -37,7 +37,7 @@ export const NAV_ITEMS = [ export const CONNECTED_NAV_ITEMS = [ { dataTestId: "dashboard-link", - label: "Dashboard", + label: i18n.t("dashboard.title"), navTo: PATHS.dashboard, activeIcon: ICONS.dashboardActiveIcon, icon: ICONS.dashboardIcon, @@ -52,7 +52,7 @@ export const CONNECTED_NAV_ITEMS = [ }, { dataTestId: "governance-actions-link", - label: "Governance Actions", + label: i18n.t("govActions.title"), navTo: PATHS.dashboardGovernanceActions, activeIcon: ICONS.governanceActionsActiveIcon, icon: ICONS.governanceActionsIcon, @@ -60,7 +60,7 @@ export const CONNECTED_NAV_ITEMS = [ }, { dataTestId: "guides-link", - label: "Guides", + label: i18n.t("menu.guides"), navTo: "", activeIcon: ICONS.guidesActiveIcon, icon: ICONS.guidesIcon, @@ -68,7 +68,7 @@ export const CONNECTED_NAV_ITEMS = [ }, { dataTestId: "faqs-link", - label: "FAQs", + label: i18n.t("menu.faqs"), navTo: "", activeIcon: ICONS.faqsActiveIcon, icon: ICONS.faqsIcon, diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 852d86a41..729a54e7a 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -460,8 +460,6 @@ export const en = { faqs: "FAQs", guides: "Guides", help: "Help", - dashboard: "Dashboard", - viewGovActions: "View Governance Actions", }, metadataUpdate: { description: From 1c44199913ed3d5cb14c9110521780b3be273aa6 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Fri, 8 Mar 2024 13:53:59 +0100 Subject: [PATCH 006/143] [#216] use Accordion from MUI to improve user experience --- .../organisms/AutomatedVotingOptions.tsx | 68 ++++++++----------- .../organisms/DRepDirectoryContent.tsx | 11 +-- govtool/frontend/src/theme.ts | 7 ++ 3 files changed, 36 insertions(+), 50 deletions(-) diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index 2f5d8abcf..ff7503e28 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -1,53 +1,40 @@ -import { useState } from "react"; -import { Box } from "@mui/material"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, +} from "@mui/material"; -import { Spacer, Typography } from "@atoms"; +import { Typography } from "@atoms"; import { ICONS } from "@consts"; import { useTranslation } from "@hooks"; import { AutomatedVotingCard } from "@molecules"; export const AutomatedVotingOptions = () => { - const [isOpen, setIsOpen] = useState(false); const { t } = useTranslation(); - const handleOpen = () => { - setIsOpen((prev) => !prev); - }; - return ( - ({ + bgcolor: `${theme.palette.lightBlue}80`, + border: `1px solid ${theme.palette.neutralWhite}`, + })} > - } + sx={{ borderRadius: 3, px: { xxs: 2, md: 3 } }} > {t("dRepDirectory.automatedVotingOptions")} - arrow + + - - {isOpen ? ( - <> - + > {}} @@ -55,7 +42,6 @@ export const AutomatedVotingOptions = () => { title={t("dRepDirectory.abstainCardTitle")} votingPower="99,111,111" /> - {}} @@ -63,8 +49,8 @@ export const AutomatedVotingOptions = () => { title={t("dRepDirectory.noConfidenceTitle")} votingPower="99,111,111" /> - - ) : null} - + + + ); }; diff --git a/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx b/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx index cfdbde3d1..d69be045c 100644 --- a/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx @@ -1,4 +1,5 @@ import { FC } from "react"; +import { AutomatedVotingOptions } from "."; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -6,12 +7,4 @@ interface DRepDirectoryContentProps { export const DRepDirectoryContent: FC = ({ isConnected, -}) => ( - <> -

DRepDirectory

-

- connected: - {String(!!isConnected)} -

- -); +}) => <>{isConnected && }; diff --git a/govtool/frontend/src/theme.ts b/govtool/frontend/src/theme.ts index 7b9bd2331..0bdccf311 100644 --- a/govtool/frontend/src/theme.ts +++ b/govtool/frontend/src/theme.ts @@ -23,6 +23,13 @@ export const theme = createTheme({ }, }, components: { + MuiAccordion: { + styleOverrides: { + root: { + borderRadius: `12px !important`, + } + } + }, MuiInputBase: { styleOverrides: { root: { From 7a40c804e4d5034a2e975b23c78f62c74082f14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Wed, 28 Feb 2024 11:53:45 +0100 Subject: [PATCH 007/143] [#219] create details page for drep in drep directory --- govtool/frontend/src/App.tsx | 23 +- .../src/components/atoms/StakeRadio.tsx | 1 + .../molecules/AutomatedVotingCard.tsx | 2 +- .../src/components/molecules/Share.tsx | 48 ++-- .../src/components/molecules/index.ts | 1 + govtool/frontend/src/consts/paths.ts | 2 + govtool/frontend/src/i18n/locales/en.ts | 12 +- govtool/frontend/src/pages/DRepDetails.tsx | 206 ++++++++++++++++++ govtool/frontend/src/pages/DRepDirectory.tsx | 7 +- .../DRepDirectoryContent.tsx | 2 +- govtool/frontend/src/pages/Dashboard.tsx | 14 +- govtool/frontend/src/pages/index.ts | 3 + govtool/frontend/src/theme.ts | 5 + 13 files changed, 289 insertions(+), 37 deletions(-) create mode 100644 govtool/frontend/src/pages/DRepDetails.tsx rename govtool/frontend/src/{components/organisms => pages}/DRepDirectoryContent.tsx (81%) diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index fc20a4611..ceb2bda75 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -16,7 +16,9 @@ import { Dashboard, DashboardGovernanceActionsCategory, DelegateTodRep, + DRepDetails, DRepDirectory, + DRepDirectoryContent, ErrorPage, GovernanceActionDetails, GovernanceActions, @@ -105,12 +107,27 @@ export default () => { path={PATHS.dashboardGovernanceActionsCategory} element={} /> + }> + } + /> + } + /> + + + }> + } + /> } + path={PATHS.dRepDirectoryDRep} + element={} /> - } /> } diff --git a/govtool/frontend/src/components/atoms/StakeRadio.tsx b/govtool/frontend/src/components/atoms/StakeRadio.tsx index 61dd45b39..ab054286a 100644 --- a/govtool/frontend/src/components/atoms/StakeRadio.tsx +++ b/govtool/frontend/src/components/atoms/StakeRadio.tsx @@ -71,6 +71,7 @@ export const StakeRadio: FC = ({ ...props }) => { {t("votingPower")} + : {powerIsLoading ? ( diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx index 0ac7d78ec..c566011a5 100644 --- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -60,7 +60,7 @@ export const AutomatedVotingCard = ({ {t("dRepDirectory.votingPower")} - + {'₳ '} {votingPower} diff --git a/govtool/frontend/src/components/molecules/Share.tsx b/govtool/frontend/src/components/molecules/Share.tsx index a57e7763d..39a712842 100644 --- a/govtool/frontend/src/components/molecules/Share.tsx +++ b/govtool/frontend/src/components/molecules/Share.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { Box, Popover } from "@mui/material"; +import { MouseEvent, useState } from "react"; +import { Box, ButtonBase, Popover } from "@mui/material"; import { Typography } from "@atoms"; import { ICONS } from "@consts"; @@ -9,20 +9,22 @@ import { useTranslation } from "@hooks"; export const Share = ({ link }: { link: string }) => { const { addSuccessAlert } = useSnackbar(); const { t } = useTranslation(); - const [anchorEl, setAnchorEl] = useState(null); + const [anchorEl, setAnchorEl] = useState(null); + const [isActive, setIsActive] = useState(true); - const handleClick = (e: React.MouseEvent) => { - setAnchorEl(e.currentTarget); + const handleClick = (event: MouseEvent) => { + setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; - const onCopy = (e: React.MouseEvent) => { + const onCopy = (event: MouseEvent) => { navigator.clipboard.writeText(link); addSuccessAlert(t("alerts.copiedToClipboard")); - e.stopPropagation(); + setIsActive(false); + event.stopPropagation(); }; const open = Boolean(anchorEl); @@ -30,29 +32,33 @@ export const Share = ({ link }: { link: string }) => { return ( <> - ({ alignItems: "center", - bgcolor: "#F7F9FB", + bgcolor: open ? "#F7F9FB" : "transparent", borderRadius: 50, - boxShadow: "2px 2px 15px 0px #2F62DC47", + boxShadow: open ? theme.shadows[1] : "none", cursor: "pointer", display: "flex", justifyContent: "center", padding: 1.5, - }} + transition: 'all 0.3s', + '&:hover': { + boxShadow: theme.shadows[1], + bgcolor: "#F7F9FB", + } + })} > - share icon - + + { {t("share")} - theme.shadows[1], cursor: "pointer", display: "flex", height: 48, @@ -88,8 +98,8 @@ export const Share = ({ link }: { link: string }) => { }} > link - - {t("clickToCopyLink")} + + {isActive ? t("clickToCopyLink") : t("linkCopied")} diff --git a/govtool/frontend/src/components/molecules/index.ts b/govtool/frontend/src/components/molecules/index.ts index ad45ca5b0..2bfb3f8f9 100644 --- a/govtool/frontend/src/components/molecules/index.ts +++ b/govtool/frontend/src/components/molecules/index.ts @@ -33,6 +33,7 @@ export * from "./SliderArrows"; export * from "./SoleVoterAction"; export * from "./Step"; export * from "./PageTitle"; +export * from "./Share"; export * from "./VoteActionForm"; export * from "./VotesSubmitted"; export * from "./WalletInfoCard"; diff --git a/govtool/frontend/src/consts/paths.ts b/govtool/frontend/src/consts/paths.ts index b58c4b545..7fb2d92f4 100644 --- a/govtool/frontend/src/consts/paths.ts +++ b/govtool/frontend/src/consts/paths.ts @@ -6,8 +6,10 @@ export const PATHS = { dashboardGovernanceActionsCategory: "/connected/governance_actions/category/:category", dashboardDRepDirectory: "/connected/drep_directory", + dashboardDRepDirectoryDRep: "/connected/drep_directory/:dRepId", delegateTodRep: "/delegate", dRepDirectory: "/drep_directory", + dRepDirectoryDRep: "/drep_directory/:dRepId", editDrepMetadata: "/edit_drep", error: "/error", faqs: "/faqs", diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 729a54e7a..c4dcf7c80 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -704,6 +704,7 @@ export const en = { usingUnregisteredStakeKeys: "Warning, no registered stake keys, using unregistered stake keys", }, + about: "About", abstain: "Abstain", addLink: "+ Add link", back: "Back", @@ -714,16 +715,22 @@ export const en = { clickToCopyLink: "Click to copy link", close: "Close", confirm: "Confirm", + connectToDelegate: "Connect to delegate", continue: "Continue", + copiedLink: "Copied link", delegate: "Delegate", + drepId: "DRep ID", + email: "Email", filter: "Filter", goBack: "Go back", here: "here", info: "Info", inProgress: "In progress", learnMore: "Learn more", + linkCopied: "Link copied", loading: "Loading...", - myDRepId: "My dRep ID:", + moreInformation: "More information", + myDRepId: "My DRep ID:", nextStep: "Next step", no: "No", ok: "Ok", @@ -739,8 +746,9 @@ export const en = { sort: "Sort", sortBy: "Sort by", submit: "Submit", + status: "Status", thisLink: "this link", - votingPower: "Voting power:", + votingPower: "Voting power", yes: "Yes", }, }; diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx new file mode 100644 index 000000000..2cbe98184 --- /dev/null +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -0,0 +1,206 @@ +import { Box, ButtonBase } from "@mui/material"; + +import { Button, Typography } from "@atoms"; +import { Card, Share } from "@molecules"; +import { ICONS } from "@consts"; +import { useScreenDimension, useTranslation } from "@hooks"; +import { openInNewTab } from "@utils"; +import { PropsWithChildren } from "react"; +import { useModal } from "@/context"; + +const LINKS = [ + "darlenelonglink1.DRepwebsiteorwhatever.com", + "darlenelonglink2.DRepwebsiteorwhatever.com", + "darlenelonglink3.DRepwebsiteorwhatever.com", + "darlenelonglink4.DRepwebsiteorwhatever.com", + "darlenelonglink5.DRepwebsiteorwhatever.com", +]; + +type DRepDetailsProps = { + isConnected?: boolean; +}; + +export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { + const { t } = useTranslation(); + const { openModal } = useModal(); + const { screenWidth } = useScreenDimension(); + + return ( + + + + ExampleDRepName + + {/* TODO: connect link */} + + + + + + + drep_1njkdnkjwenfk12321ndcnsjdcsndc + + + + {/* TODO: add status pill */} + {/* */} + + + + {'₳ '} + 50,000,000 + + + + + + + + {LINKS.map((link) => ( + + ))} + + + + + + {isConnected ? ( + + ) : ( + + )} + + + {t("about")} + + I am the Cardano crusader carving his path in the blockchain + battleground. With a mind sharper than a Ledger Nano X, this fearless + crypto connoisseur fearlessly navigates the volatile seas of Cardano, + turning code into currency. Armed with a keyboard and a heart pumping + with blockchain beats, Mister Big Bad fearlessly champions + decentralization, smart contracts, and the Cardano community. His + Twitter feed is a mix of market analysis that rivals CNBC and memes that + could break the internet. + + + ); +}; + +const ellipsisStyles = { + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", +}; + +type DrepDetailsInfoItemProps = PropsWithChildren & { + label: string; +}; + +const DRepDetailsInfoItem = ({ children, label }: DrepDetailsInfoItemProps) => ( + <> + + + {label} + + + + {children} + + +); + +const DRepId = ({ children }: PropsWithChildren) => ( + + + {children} + + + +); + +type LinkWithIconProps = { + label: string; + navTo: string; +}; + +const LinkWithIcon = ({ label, navTo }: LinkWithIconProps) => { + const openLink = () => openInNewTab(navTo); + + return ( + + link + + {label} + + + ); +}; diff --git a/govtool/frontend/src/pages/DRepDirectory.tsx b/govtool/frontend/src/pages/DRepDirectory.tsx index 71e4d38ec..d3569f848 100644 --- a/govtool/frontend/src/pages/DRepDirectory.tsx +++ b/govtool/frontend/src/pages/DRepDirectory.tsx @@ -1,7 +1,8 @@ import { useTranslation } from "@hooks"; +import { Outlet } from "react-router-dom"; import { checkIsWalletConnected } from "@/utils"; import { Background, PagePaddingBox, ContentBox } from "@/components/atoms"; -import { DRepDirectoryContent, TopNav } from "@/components/organisms"; +import { TopNav } from "@/components/organisms"; import { PageTitle } from "@/components/molecules"; export const DRepDirectory = () => { @@ -12,7 +13,7 @@ export const DRepDirectory = () => { if (isConnected) { return ( - + ); } @@ -25,7 +26,7 @@ export const DRepDirectory = () => { - + diff --git a/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx similarity index 81% rename from govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx rename to govtool/frontend/src/pages/DRepDirectoryContent.tsx index d69be045c..cb87558cb 100644 --- a/govtool/frontend/src/components/organisms/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { AutomatedVotingOptions } from "."; +import { AutomatedVotingOptions } from "@organisms"; interface DRepDirectoryContentProps { isConnected?: boolean; diff --git a/govtool/frontend/src/pages/Dashboard.tsx b/govtool/frontend/src/pages/Dashboard.tsx index 80c9797b1..818165757 100644 --- a/govtool/frontend/src/pages/Dashboard.tsx +++ b/govtool/frontend/src/pages/Dashboard.tsx @@ -3,7 +3,7 @@ import { useLocation, Outlet, useNavigate } from "react-router-dom"; import { Box } from "@mui/material"; import { Background, ScrollToManage } from "@atoms"; -import { PATHS } from "@consts"; +import { CONNECTED_NAV_ITEMS, PATHS } from "@consts"; import { useCardano } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; import { DashboardTopNav, Drawer, Footer } from "@organisms"; @@ -18,13 +18,11 @@ export const Dashboard = () => { const { t } = useTranslation(); const getPageTitle = (path: string) => { - if (path === PATHS.dashboard) { - return t("dashboard.title"); - } - if (path.includes(PATHS.dashboardGovernanceActions)) { - return t("govActions.title"); - } - return ""; + if (path === PATHS.dashboard) return t("dashboard.title"); + return ( + Object.values(CONNECTED_NAV_ITEMS).find(({ navTo }) => pathname.startsWith(navTo)) + ?.label ?? "" + ); }; useEffect(() => { diff --git a/govtool/frontend/src/pages/index.ts b/govtool/frontend/src/pages/index.ts index 3bd34f2bb..2507933ef 100644 --- a/govtool/frontend/src/pages/index.ts +++ b/govtool/frontend/src/pages/index.ts @@ -1,5 +1,8 @@ export * from "./ChooseStakeKey"; export * from "./CreateGovernanceAction"; +export * from "./DRepDetails"; +export * from "./DRepDirectory"; +export * from "./DRepDirectoryContent"; export * from "./Dashboard"; export * from "./DashboardGovernanceActionsCategory"; export * from "./DelegateTodRep"; diff --git a/govtool/frontend/src/theme.ts b/govtool/frontend/src/theme.ts index 0bdccf311..a9ba6272e 100644 --- a/govtool/frontend/src/theme.ts +++ b/govtool/frontend/src/theme.ts @@ -113,6 +113,11 @@ export const theme = createTheme({ }, }, }, + MuiPopover: { + defaultProps: { + elevation: 2, + } + }, }, typography: { fontFamily: "Poppins, Arial", From 5cf3940286dd3537ebbd0f353e639af9b3bba5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Fri, 1 Mar 2024 12:22:33 +0100 Subject: [PATCH 008/143] [#219] reorder translations --- govtool/frontend/src/i18n/locales/en.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index c4dcf7c80..d132daa29 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -724,8 +724,8 @@ export const en = { filter: "Filter", goBack: "Go back", here: "here", - info: "Info", inProgress: "In progress", + info: "Info", learnMore: "Learn more", linkCopied: "Link copied", loading: "Loading...", @@ -745,8 +745,8 @@ export const en = { skip: "Skip", sort: "Sort", sortBy: "Sort by", - submit: "Submit", status: "Status", + submit: "Submit", thisLink: "this link", votingPower: "Voting power", yes: "Yes", From cd93e5167534881b98ff67d277994815a2ec11cf Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Thu, 21 Mar 2024 11:00:51 +0100 Subject: [PATCH 009/143] [#219] fetch data for DRep details page --- .../src/hooks/queries/useGetDRepListQuery.ts | 4 +- govtool/frontend/src/i18n/locales/en.ts | 3 + govtool/frontend/src/models/api.ts | 4 + govtool/frontend/src/pages/DRepDetails.tsx | 135 +++++++++++++++--- .../src/services/requests/getDRepList.ts | 4 +- govtool/frontend/src/theme.ts | 10 +- 6 files changed, 127 insertions(+), 33 deletions(-) diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index 806d55072..59021a24c 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -4,7 +4,7 @@ import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { getDRepList } from "@services"; -export const useGetDRepListQuery = () => { +export const useGetDRepListQuery = (dRepView?: string) => { const { pendingTransaction } = useCardano(); const { data, isLoading } = useQuery({ @@ -15,7 +15,7 @@ export const useGetDRepListQuery = () => { pendingTransaction.retireAsSoleVoter || pendingTransaction.retireAsDrep)?.transactionHash, ], - queryFn: getDRepList, + queryFn: () => getDRepList(dRepView), }); return { data, isLoading }; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index d132daa29..e46168178 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -253,6 +253,9 @@ export const en = { abstainCardDescription: "Select this to vote ABSTAIN to every vote.", abstainCardTitle: "Abstain from Every Vote", automatedVotingOptions: "Automated Voting Options", + editBtn: "Edit DRep data", + meAsDRep: "This DRep ID is connected to your wallet", + myDRep: "This is your DRep", noConfidenceDescription: "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", noConfidenceTitle: "Signal No Confidence on Every Vote", diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 7f9b24e9a..fdfcbb86d 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -10,9 +10,13 @@ export interface VoterInfo { export interface DRepData { drepId: string; + view: string; url: string; metadataHash: string; deposit: number; + votingPower: number; + status: 'Active' | 'Inactive' | 'Retired'; + type: 'DRep' | 'SoleVoter'; } export type Vote = "yes" | "no" | "abstain"; diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 2cbe98184..f3c084327 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -1,12 +1,20 @@ -import { Box, ButtonBase } from "@mui/material"; +import { PropsWithChildren, useState } from "react"; +import { Navigate, useNavigate, useParams } from "react-router-dom"; +import { + Box, ButtonBase, Chip, CircularProgress +} from "@mui/material"; -import { Button, Typography } from "@atoms"; +import { Button, LoadingButton, Typography } from "@atoms"; import { Card, Share } from "@molecules"; -import { ICONS } from "@consts"; -import { useScreenDimension, useTranslation } from "@hooks"; -import { openInNewTab } from "@utils"; -import { PropsWithChildren } from "react"; -import { useModal } from "@/context"; +import { ICONS, PATHS } from "@consts"; +import { + useGetAdaHolderCurrentDelegationQuery, + useGetDRepListQuery, + useScreenDimension, + useTranslation +} from "@hooks"; +import { correctAdaFormat, openInNewTab } from "@utils"; +import { useCardano, useModal, useSnackbar } from "@/context"; const LINKS = [ "darlenelonglink1.DRepwebsiteorwhatever.com", @@ -21,48 +29,129 @@ type DRepDetailsProps = { }; export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { + const { + buildSignSubmitConwayCertTx, + buildVoteDelegationCert, + dRepID: myDRepId, + pendingTransaction, + stakeKey + } = useCardano(); const { t } = useTranslation(); + const navigate = useNavigate(); const { openModal } = useModal(); + const { addSuccessAlert, addErrorAlert } = useSnackbar(); const { screenWidth } = useScreenDimension(); + const { dRepId: dRepParam } = useParams(); + + const [isDelegating, setIsDelegating] = useState(false); + + const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); + const { data, isLoading } = useGetDRepListQuery(dRepParam); + const dRep = data?.[0]; + + if (!dRep && isLoading) return ; + + if (!dRep) return ; + + const { + drepId, view, status, votingPower, type + } = dRep; + + const isMe = drepId === myDRepId || view === myDRepId; + const isMyDrep = drepId === currentDelegation || view === currentDelegation; + const inProgressDelegation = pendingTransaction.delegate?.resourceId; + const isMyDrepInProgress = drepId === inProgressDelegation || view === inProgressDelegation; + + const delegate = async () => { + setIsDelegating(true); + try { + const certBuilder = await buildVoteDelegationCert(drepId); + const result = await buildSignSubmitConwayCertTx({ + certBuilder, + type: "delegate", + resourceId: drepId, + }); + if (result) { + addSuccessAlert(t("alerts.delegate.success")); + } + } catch (error) { + addErrorAlert(t("alerts.delegate.failed")); + } finally { + setIsDelegating(false); + } + }; return ( - + + {(isMe || isMyDrep) && ( + theme.shadows[2], + color: (theme) => theme.palette.text.primary, + mb: 1.5, + px: 2, + py: 0.5, + width: '100%', + }} + /> + )} - ExampleDRepName + {type} - {/* TODO: connect link */} - + {isMe && ( + + )} + - - drep_1njkdnkjwenfk12321ndcnsjdcsndc - + {view} {/* TODO: add status pill */} {/* */} + {status} {'₳ '} - 50,000,000 + {correctAdaFormat(votingPower)} + {/* TODO: fetch metadata, add views for metadata errors */} { }} > {isConnected ? ( - + ) : ( + {status === "active" && isConnected && ( + + )} + {status === "active" && !isConnected && ( + + )} + +
+ + ); +}; + +const ellipsisStyles = { + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", +} as const; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index e6cf14e92..3b775bf2e 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -15,6 +15,7 @@ export * from "./DelegateTodRepStepOne"; export * from "./DelegateTodRepStepTwo"; export * from "./Drawer"; export * from "./DrawerMobile"; +export * from "./DRepCard"; export * from "./DRepDirectoryContent"; export * from "./EditDRepInfoSteps"; export * from "./ExternalLinkModal"; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index e46168178..f47ff1c9f 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -254,8 +254,11 @@ export const en = { abstainCardTitle: "Abstain from Every Vote", automatedVotingOptions: "Automated Voting Options", editBtn: "Edit DRep data", + delegationOptions: "Delegation Options", meAsDRep: "This DRep ID is connected to your wallet", + myDelegation: "You have delegated ₳ {{ada}} to:", myDRep: "This is your DRep", + listTitle: "Find a DRep", noConfidenceDescription: "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", noConfidenceTitle: "Signal No Confidence on Every Vote", @@ -751,6 +754,7 @@ export const en = { status: "Status", submit: "Submit", thisLink: "this link", + viewDetails: "View details", votingPower: "Voting power", yes: "Yes", }, diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index cb87558cb..b9db91afa 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,5 +1,8 @@ +import { Box } from "@mui/material"; import { FC } from "react"; -import { AutomatedVotingOptions } from "@organisms"; +import { AutomatedVotingOptions, DRepCard } from "@organisms"; +import { Typography } from "@atoms"; +import { Trans, useTranslation } from "react-i18next"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -7,4 +10,96 @@ interface DRepDirectoryContentProps { export const DRepDirectoryContent: FC = ({ isConnected, -}) => <>{isConnected && }; +}) => { + const { t } = useTranslation(); + + const ada = 1234567; + + return ( + + {isConnected && ( +
+ + + + +
+ )} + + {isConnected && ( +
+ {t("dRepDirectory.delegationOptions")} + +
+ )} + +
+ {t("dRepDirectory.listTitle")} + + {data.map((dRep) => ( + + + + ))} + +
+
+ ); +}; + +const data = [ + { + name: "DRep 1", + id: "1kejngelrngekngeriogj3io4j3gnd3", + votingPower: 3000000, + status: "active", + }, + { + name: "DRep 2", + id: "1kejrngelrngekngeriogfrej3io4fj3gn3", + votingPower: 10000000, + status: "active", + }, + { + name: "DRep 1", + id: "1kejngelrngekngeriogj3io4j3gnd3", + votingPower: 1000000, + status: "active", + }, + { + name: "DRep 2", + id: "1kejrngelrngekngeriogfrej3io4fj3gn3", + votingPower: 9900000000000000, + status: "active", + }, + { + name: "DRep 1", + id: "1kejngelrngekngeriogj3io4j3gn3", + votingPower: 12345678, + status: "active", + }, + { + name: "DRep 2", + id: "1kejrngelrngekngeriogfrej3io4j3gn3", + votingPower: 1234567800, + status: "active", + }, + { + name: "DRep 4", + id: "1kejrngelkngeriogj3io4j3gn3", + votingPower: 12345678000000, + status: "retired", + }, + { + name: "DRep 3", + id: "1kejrngelrngekngeriogj3io4j3gn2", + votingPower: 123456, + status: "active", + }, + { + name: "Some dreps can have a very long name and it will be displayed correctly", + id: "1kejrngelrngekngeriogj3io4j3gn3", + votingPower: 123456, + status: "inactive", + }, +]; diff --git a/govtool/frontend/src/stories/StatusPill.stories.ts b/govtool/frontend/src/stories/StatusPill.stories.ts new file mode 100644 index 000000000..a26e09dc7 --- /dev/null +++ b/govtool/frontend/src/stories/StatusPill.stories.ts @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { StatusPill } from "@atoms"; + +const meta = { + title: "Example/StatusPill", + component: StatusPill, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const StatusPillActive: Story = { + args: { + status: "active", + }, +}; + +export const StatusPillInactive: Story = { + args: { + status: "inactive", + }, +}; + +export const StatusPillRetired: Story = { + args: { + status: "retired", + }, +}; From 3ff60d5aea75bbe38e56eff2668d4440ba859681 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Thu, 21 Mar 2024 17:04:17 +0100 Subject: [PATCH 012/143] [#220] fetch data for drep list --- .../src/components/atoms/StatusPill.tsx | 22 +- .../src/components/organisms/DRepCard.tsx | 56 +++- govtool/frontend/src/hooks/index.ts | 1 + .../src/hooks/queries/useGetDRepListQuery.ts | 9 +- .../frontend/src/hooks/useDelegateToDrep.ts | 45 +++ govtool/frontend/src/pages/DRepDetails.tsx | 301 +++++++++--------- .../src/pages/DRepDirectoryContent.tsx | 117 +++---- .../src/stories/StatusPill.stories.ts | 6 +- govtool/frontend/src/utils/dRep.ts | 11 + govtool/frontend/src/utils/index.ts | 1 + 10 files changed, 316 insertions(+), 253 deletions(-) create mode 100644 govtool/frontend/src/hooks/useDelegateToDrep.ts create mode 100644 govtool/frontend/src/utils/dRep.ts diff --git a/govtool/frontend/src/components/atoms/StatusPill.tsx b/govtool/frontend/src/components/atoms/StatusPill.tsx index 60b70d049..ff08844d2 100644 --- a/govtool/frontend/src/components/atoms/StatusPill.tsx +++ b/govtool/frontend/src/components/atoms/StatusPill.tsx @@ -1,7 +1,7 @@ import { Chip, ChipProps, styled } from "@mui/material"; import { cyan, errorRed, successGreen } from "@/consts"; -type Status = 'active' | 'retired' | 'inactive'; +type Status = 'Active' | 'Inactive' | 'Retired'; interface StatusPillProps { status: Status; @@ -26,25 +26,25 @@ export const StatusPill = ({ const getBgColor = (status: Status): string => { switch (status) { - case 'active': + case 'Active': return successGreen.c200; - case 'retired': - return errorRed.c100; - case 'inactive': + case 'Inactive': return cyan.c100; - // no default + case 'Retired': + return errorRed.c100; + // no default } }; const getTextColor = (status: Status): string => { switch (status) { - case 'active': + case 'Active': return successGreen.c700; - case 'retired': - return errorRed.c500; - case 'inactive': + case 'Inactive': return cyan.c500; - // no default + case 'Retired': + return errorRed.c500; + // no default } }; diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index 43ce7a599..3b583e567 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -5,20 +5,44 @@ import { useTranslation } from "@hooks"; import { Button, StatusPill, Typography } from "@atoms"; import { Card } from "@molecules"; import { correctAdaFormat } from "@/utils"; -import { ICONS } from "@/consts"; +import { ICONS, PATHS } from "@/consts"; +import { DRepData } from "@/models"; + +type DRepCardProps = { + dRep: DRepData; + isConnected: boolean; + isInProgress?: boolean; + isMe?: boolean; + onDelegate?: () => void; +} export const DRepCard = ({ + dRep: { + status, + type, + view, + votingPower, + }, isConnected, - name, - id, - votingPower, - status, -}: any) => { + isInProgress, + isMe, + onDelegate, +}: DRepCardProps) => { const navigate = useNavigate(); const { t } = useTranslation(); return ( - + - {name} + {type} - {id} + {view} @@ -108,13 +132,19 @@ export const DRepCard = ({ }, }} > - - {status === "active" && isConnected && ( - + {status === "Active" && isConnected && onDelegate && ( + )} - {status === "active" && !isConnected && ( + {status === "Active" && !isConnected && ( )} diff --git a/govtool/frontend/src/hooks/index.ts b/govtool/frontend/src/hooks/index.ts index da6b8743b..8fdb31e31 100644 --- a/govtool/frontend/src/hooks/index.ts +++ b/govtool/frontend/src/hooks/index.ts @@ -2,6 +2,7 @@ export { useTranslation } from "react-i18next"; export * from "./useDataActionsBar"; export * from "./useDebounce"; +export * from "./useDelegateToDrep"; export * from "./useFetchNextPageDetector"; export * from "./useOutsideClick"; export * from "./useSaveScrollPosition"; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index 59021a24c..b00932f1d 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -1,21 +1,24 @@ -import { useQuery } from "react-query"; +import { UseQueryOptions, useQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { getDRepList } from "@services"; +import { DRepData } from "@/models"; -export const useGetDRepListQuery = (dRepView?: string) => { +export const useGetDRepListQuery = (dRepView?: string, options?: UseQueryOptions) => { const { pendingTransaction } = useCardano(); - const { data, isLoading } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: [ QUERY_KEYS.useGetDRepListKey, (pendingTransaction.registerAsSoleVoter || pendingTransaction.registerAsDrep || pendingTransaction.retireAsSoleVoter || pendingTransaction.retireAsDrep)?.transactionHash, + dRepView ], queryFn: () => getDRepList(dRepView), + ...options }); return { data, isLoading }; diff --git a/govtool/frontend/src/hooks/useDelegateToDrep.ts b/govtool/frontend/src/hooks/useDelegateToDrep.ts new file mode 100644 index 000000000..0183e82de --- /dev/null +++ b/govtool/frontend/src/hooks/useDelegateToDrep.ts @@ -0,0 +1,45 @@ +import { useCallback, useState } from "react"; +import { useTranslation } from "@hooks"; +import { useCardano, useSnackbar } from "@/context"; + +export const useDelegateTodRep = () => { + const { + buildSignSubmitConwayCertTx, + buildVoteDelegationCert, + } = useCardano(); + const { t } = useTranslation(); + const { addSuccessAlert, addErrorAlert } = useSnackbar(); + + const [isDelegating, setIsDelegating] = useState(false); + + const delegate = useCallback(async (dRepId: string | undefined) => { + if (!dRepId) return; + setIsDelegating(true); + try { + const certBuilder = await buildVoteDelegationCert(dRepId); + const result = await buildSignSubmitConwayCertTx({ + certBuilder, + type: "delegate", + resourceId: dRepId, + }); + if (result) { + addSuccessAlert(t("alerts.delegate.success")); + } + } catch (error) { + addErrorAlert(t("alerts.delegate.failed")); + } finally { + setIsDelegating(false); + } + }, [ + addErrorAlert, + addSuccessAlert, + buildSignSubmitConwayCertTx, + buildVoteDelegationCert, + t, + ]); + + return { + delegate, + isDelegating, + }; +}; diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index ea9685c9d..1d0783358 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -1,20 +1,24 @@ -import { PropsWithChildren, useState } from "react"; +import { PropsWithChildren } from "react"; import { Navigate, useNavigate, useParams } from "react-router-dom"; import { Box, ButtonBase, Chip, CircularProgress } from "@mui/material"; -import { Button, LoadingButton, Typography } from "@atoms"; -import { Card, Share } from "@molecules"; +import { + Button, LoadingButton, StatusPill, Typography +} from "@atoms"; +import { Card, LinkWithIcon, Share } from "@molecules"; import { ICONS, PATHS } from "@consts"; import { + useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, useGetDRepListQuery, useScreenDimension, useTranslation } from "@hooks"; import { correctAdaFormat, openInNewTab } from "@utils"; -import { useCardano, useModal, useSnackbar } from "@/context"; +import { useCardano, useModal } from "@/context"; +import { isSameDRep } from "@/utils"; const LINKS = [ "darlenelonglink1.DRepwebsiteorwhatever.com", @@ -30,8 +34,6 @@ type DRepDetailsProps = { export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { - buildSignSubmitConwayCertTx, - buildVoteDelegationCert, dRepID: myDRepId, pendingTransaction, stakeKey, @@ -39,11 +41,10 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { t } = useTranslation(); const navigate = useNavigate(); const { openModal } = useModal(); - const { addSuccessAlert, addErrorAlert } = useSnackbar(); const { screenWidth } = useScreenDimension(); const { dRepId: dRepParam } = useParams(); - const [isDelegating, setIsDelegating] = useState(false); + const { delegate, isDelegating } = useDelegateTodRep(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const { data, isLoading } = useGetDRepListQuery(dRepParam); @@ -54,166 +55,152 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { if (!dRep) return ; const { - drepId, view, status, votingPower, type + view, status, votingPower, type } = dRep; - const isMe = drepId === myDRepId || view === myDRepId; - const isMyDrep = drepId === currentDelegation || view === currentDelegation; - const inProgressDelegation = pendingTransaction.delegate?.resourceId; - const isMyDrepInProgress = drepId === inProgressDelegation || view === inProgressDelegation; - - const delegate = async () => { - setIsDelegating(true); - try { - const certBuilder = await buildVoteDelegationCert(drepId); - const result = await buildSignSubmitConwayCertTx({ - certBuilder, - type: "delegate", - resourceId: drepId, - }); - if (result) { - addSuccessAlert(t("alerts.delegate.success")); - } - } catch (error) { - addErrorAlert(t("alerts.delegate.failed")); - } finally { - setIsDelegating(false); - } - }; + const isMe = isSameDRep(dRep, myDRepId); + const isMyDrep = isSameDRep(dRep, currentDelegation); + const isMyDrepInProgress = isSameDRep(dRep, pendingTransaction.delegate?.resourceId); return ( - - {(isMe || isMyDrep) && ( - theme.shadows[2], - color: (theme) => theme.palette.text.primary, - mb: 1.5, - px: 2, - py: 0.5, - width: '100%', - }} - /> - )} - + navigate(isConnected ? PATHS.dashboardDRepDirectory : PATHS.dRepDirectory)} + sx={{ mb: 2 }} + /> + - theme.shadows[2], + color: (theme) => theme.palette.text.primary, + mb: 1.5, + px: 2, + py: 0.5, + width: '100%', + }} + /> + )} + - {type} - - {isMe && ( - - )} - - - - - - {view} - - - {/* TODO: add status pill */} - {/* */} - {status} - - - - {'₳ '} - {correctAdaFormat(votingPower)} + {type} - - {/* TODO: fetch metadata, add views for metadata errors */} - - - - - - {LINKS.map((link) => ( - - ))} - - - + {isMe && ( + + )} + + - - {isConnected ? ( - - {t("delegate")} - - ) : ( - - )} - + + + {view} + + + + + + + {'₳ '} + {correctAdaFormat(votingPower)} + + + {/* TODO: fetch metadata, add views for metadata errors */} + + + + + + {LINKS.map((link) => ( + + ))} + + + - {t("about")} - - {/* TODO replace with actual data */} - I am the Cardano crusader carving his path in the blockchain - battleground. With a mind sharper than a Ledger Nano X, this fearless - crypto connoisseur fearlessly navigates the volatile seas of Cardano, - turning code into currency. Armed with a keyboard and a heart pumping - with blockchain beats, Mister Big Bad fearlessly champions - decentralization, smart contracts, and the Cardano community. His - Twitter feed is a mix of market analysis that rivals CNBC and memes that - could break the internet. - - + + {(isConnected && status === 'Active' && !isMyDrep) && ( + delegate(dRep.view)} + size="extraLarge" + sx={{ width: "100%" }} + variant="contained" + > + {t("delegate")} + + )} + {!isConnected && ( + + )} + + + {t("about")} + + {/* TODO replace with actual data */} + I am the Cardano crusader carving his path in the blockchain + battleground. With a mind sharper than a Ledger Nano X, this fearless + crypto connoisseur fearlessly navigates the volatile seas of Cardano, + turning code into currency. Armed with a keyboard and a heart pumping + with blockchain beats, Mister Big Bad fearlessly champions + decentralization, smart contracts, and the Cardano community. His + Twitter feed is a mix of market analysis that rivals CNBC and memes that + could break the internet. + + + ); }; @@ -267,7 +254,7 @@ type LinkWithIconProps = { navTo: string; }; -const LinkWithIcon = ({ label, navTo }: LinkWithIconProps) => { +const MoreInfoLink = ({ label, navTo }: LinkWithIconProps) => { const openLink = () => openInNewTab(navTo); return ( diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index b9db91afa..d1cc38b4c 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,8 +1,16 @@ -import { Box } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { FC } from "react"; import { AutomatedVotingOptions, DRepCard } from "@organisms"; import { Typography } from "@atoms"; import { Trans, useTranslation } from "react-i18next"; +import { useCardano } from "@/context"; +import { + useDelegateTodRep, + useGetAdaHolderCurrentDelegationQuery, + useGetAdaHolderVotingPowerQuery, + useGetDRepListQuery +} from "@/hooks"; +import { correctAdaFormat, formHexToBech32, isSameDRep } from "@/utils"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -11,18 +19,44 @@ interface DRepDirectoryContentProps { export const DRepDirectoryContent: FC = ({ isConnected, }) => { + const { + dRepID: myDRepId, + isEnableLoading, + pendingTransaction, + stakeKey, + } = useCardano(); const { t } = useTranslation(); - const ada = 1234567; + const { delegate } = useDelegateTodRep(); + + const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); + const inProgressDelegation = pendingTransaction.delegate?.resourceId; + + const { data: myDRepList, isLoading: isMyDRepLoading } = useGetDRepListQuery( + currentDelegation?.startsWith('drep') ? currentDelegation : formHexToBech32(currentDelegation), + { enabled: !!inProgressDelegation || !!currentDelegation } + ); + const myDrep = myDRepList?.[0]; + const { data: dRepList, isLoading: isDRepListLoading } = useGetDRepListQuery(); + + const { votingPower } = useGetAdaHolderVotingPowerQuery(); + + if (isEnableLoading || isMyDRepLoading || isDRepListLoading) return ; + + const ada = correctAdaFormat(votingPower); return ( - {isConnected && ( + {myDrep && (
- +
)} @@ -36,70 +70,21 @@ export const DRepDirectoryContent: FC = ({
{t("dRepDirectory.listTitle")} - {data.map((dRep) => ( - - - - ))} + {dRepList?.map((dRep) => (isSameDRep(dRep, myDrep?.view) + ? null + : ( + + delegate(dRep.drepId)} + /> + + )))}
); }; - -const data = [ - { - name: "DRep 1", - id: "1kejngelrngekngeriogj3io4j3gnd3", - votingPower: 3000000, - status: "active", - }, - { - name: "DRep 2", - id: "1kejrngelrngekngeriogfrej3io4fj3gn3", - votingPower: 10000000, - status: "active", - }, - { - name: "DRep 1", - id: "1kejngelrngekngeriogj3io4j3gnd3", - votingPower: 1000000, - status: "active", - }, - { - name: "DRep 2", - id: "1kejrngelrngekngeriogfrej3io4fj3gn3", - votingPower: 9900000000000000, - status: "active", - }, - { - name: "DRep 1", - id: "1kejngelrngekngeriogj3io4j3gn3", - votingPower: 12345678, - status: "active", - }, - { - name: "DRep 2", - id: "1kejrngelrngekngeriogfrej3io4j3gn3", - votingPower: 1234567800, - status: "active", - }, - { - name: "DRep 4", - id: "1kejrngelkngeriogj3io4j3gn3", - votingPower: 12345678000000, - status: "retired", - }, - { - name: "DRep 3", - id: "1kejrngelrngekngeriogj3io4j3gn2", - votingPower: 123456, - status: "active", - }, - { - name: "Some dreps can have a very long name and it will be displayed correctly", - id: "1kejrngelrngekngeriogj3io4j3gn3", - votingPower: 123456, - status: "inactive", - }, -]; diff --git a/govtool/frontend/src/stories/StatusPill.stories.ts b/govtool/frontend/src/stories/StatusPill.stories.ts index a26e09dc7..0ca27c631 100644 --- a/govtool/frontend/src/stories/StatusPill.stories.ts +++ b/govtool/frontend/src/stories/StatusPill.stories.ts @@ -16,18 +16,18 @@ type Story = StoryObj; export const StatusPillActive: Story = { args: { - status: "active", + status: "Active", }, }; export const StatusPillInactive: Story = { args: { - status: "inactive", + status: "Inactive", }, }; export const StatusPillRetired: Story = { args: { - status: "retired", + status: "Retired", }, }; diff --git a/govtool/frontend/src/utils/dRep.ts b/govtool/frontend/src/utils/dRep.ts new file mode 100644 index 000000000..2135677a1 --- /dev/null +++ b/govtool/frontend/src/utils/dRep.ts @@ -0,0 +1,11 @@ +import { DRepData } from '@/models'; + +export const isSameDRep = ( + { drepId, view }: DRepData, + dRepIdOrView: string | undefined, +) => { + if (!dRepIdOrView) { + return false; + } + return drepId === dRepIdOrView || view === dRepIdOrView; +}; diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts index 71d82b13f..22dc4a84f 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -6,6 +6,7 @@ export * from "./callAll"; export * from "./canonizeJSON"; export * from "./checkIsMaintenanceOn"; export * from "./checkIsWalletConnected"; +export * from "./dRep"; export * from "./formatDate"; export * from "./generateAnchor"; export * from "./generateJsonld"; From 742c886e73e047d87dda2e04c4bdefcb199df1b7 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Fri, 22 Mar 2024 12:14:09 +0100 Subject: [PATCH 013/143] [#216] add logic for automated voting options --- .../molecules/AutomatedVotingCard.tsx | 58 ++++++++++++++----- .../src/components/molecules/types.ts | 3 + .../organisms/AutomatedVotingOptions.tsx | 53 ++++++++++++++--- .../src/components/organisms/DRepCard.tsx | 4 +- govtool/frontend/src/i18n/locales/en.ts | 1 + .../src/pages/DRepDirectoryContent.tsx | 47 ++++++++++----- 6 files changed, 128 insertions(+), 38 deletions(-) diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx index c566011a5..dd17fa3cf 100644 --- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -1,31 +1,46 @@ import { Box, Divider } from "@mui/material"; -import { Button, Spacer, Typography } from "@atoms"; +import { Button, Typography } from "@atoms"; import { useScreenDimension, useTranslation } from "@hooks"; import { AutomatedVotingCardProps } from "./types"; +import { Card } from "./Card"; +import { primaryBlue } from "@/consts"; +import { useModal } from "@/context"; export const AutomatedVotingCard = ({ description, + inProgress, + isConnected, + isSelected, onClickDelegate, onClickInfo, title, votingPower, }: AutomatedVotingCardProps) => { const { isMobile, screenWidth } = useScreenDimension(); + const { openModal } = useModal(); const { t } = useTranslation(); return ( - `${theme.palette.neutralWhite}40`, + boxShadow: `0px 4px 15px 0px ${primaryBlue.c100}`, display: "flex", flex: 1, flexDirection: screenWidth < 1440 ? "column" : "row", justifyContent: "space-between", - padding: "18px 24px", + mt: inProgress || isSelected ? 2 : 0, + py: 2.25, }} > - - + {!isConnected + ? ( + + ) + : !isSelected && ( + + )} - + ); }; diff --git a/govtool/frontend/src/components/molecules/types.ts b/govtool/frontend/src/components/molecules/types.ts index 68a0bc73c..624129859 100644 --- a/govtool/frontend/src/components/molecules/types.ts +++ b/govtool/frontend/src/components/molecules/types.ts @@ -29,6 +29,9 @@ export type EmptyStateGovernanceActionsCategoryProps = { export type AutomatedVotingCardProps = { description: string; + inProgress?: boolean; + isConnected?: boolean; + isSelected?: boolean; onClickDelegate: () => void; onClickInfo: () => void; title: string; diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index ff7503e28..f0482abab 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -3,19 +3,39 @@ import { AccordionDetails, AccordionSummary, Box, + Chip, } from "@mui/material"; import { Typography } from "@atoms"; import { ICONS } from "@consts"; import { useTranslation } from "@hooks"; import { AutomatedVotingCard } from "@molecules"; +import { useState } from "react"; -export const AutomatedVotingOptions = () => { +type AutomatedVotingOptionsProps = { + currentDelegation: string | undefined; + delegate: (delegateTo: string) => void; + delegationInProgress?: string; + isConnected?: boolean; + votingPower: string; +}; + +export const AutomatedVotingOptions = ({ + currentDelegation, + delegate, + delegationInProgress, + isConnected, + votingPower, +}: AutomatedVotingOptionsProps) => { const { t } = useTranslation(); + const [isOpen, setIsOpen] = useState(false); + return ( setIsOpen(isExpanded)} sx={(theme) => ({ bgcolor: `${theme.palette.lightBlue}80`, border: `1px solid ${theme.palette.neutralWhite}`, @@ -26,6 +46,19 @@ export const AutomatedVotingOptions = () => { sx={{ borderRadius: 3, px: { xxs: 2, md: 3 } }} > {t("dRepDirectory.automatedVotingOptions")} + {currentDelegation && !isOpen && ( + // TODO this Chip is temporary, since there were no design for this case + theme.palette.neutralWhite, + fontWeight: 400, + ml: 2, + textTransform: 'uppercase', + }} + /> + )} { > {}} - onClickInfo={() => {}} + inProgress={delegationInProgress === "abstain"} + isConnected={isConnected} + isSelected={currentDelegation === "drep_always_abstain"} + onClickDelegate={() => delegate("abstain")} + onClickInfo={() => { }} title={t("dRepDirectory.abstainCardTitle")} - votingPower="99,111,111" + votingPower={votingPower} /> {}} - onClickInfo={() => {}} + inProgress={delegationInProgress === "no confidence"} + isConnected={isConnected} + isSelected={currentDelegation === "drep_always_no_confidence"} + onClickDelegate={() => delegate("no confidence")} + onClickInfo={() => { }} title={t("dRepDirectory.noConfidenceTitle")} - votingPower="99,111,111" + votingPower={votingPower} /> diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index 3b583e567..25faa77c1 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -35,11 +35,11 @@ export const DRepCard = ({ diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index f47ff1c9f..58192e02a 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -757,5 +757,6 @@ export const en = { viewDetails: "View details", votingPower: "Voting power", yes: "Yes", + yourself: "Yourself", }, }; diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index d1cc38b4c..58b8b3daf 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -21,7 +21,7 @@ export const DRepDirectoryContent: FC = ({ }) => { const { dRepID: myDRepId, - isEnableLoading, + isEnabled, pendingTransaction, stakeKey, } = useCardano(); @@ -29,19 +29,20 @@ export const DRepDirectoryContent: FC = ({ const { delegate } = useDelegateTodRep(); + const { votingPower } = useGetAdaHolderVotingPowerQuery(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const inProgressDelegation = pendingTransaction.delegate?.resourceId; - const { data: myDRepList, isLoading: isMyDRepLoading } = useGetDRepListQuery( + const { data: myDRepList } = useGetDRepListQuery( currentDelegation?.startsWith('drep') ? currentDelegation : formHexToBech32(currentDelegation), { enabled: !!inProgressDelegation || !!currentDelegation } ); const myDrep = myDRepList?.[0]; - const { data: dRepList, isLoading: isDRepListLoading } = useGetDRepListQuery(); - - const { votingPower } = useGetAdaHolderVotingPowerQuery(); + const { data: dRepList } = useGetDRepListQuery(); - if (isEnableLoading || isMyDRepLoading || isDRepListLoading) return ; + if (!isEnabled || votingPower === undefined || !dRepList) { + return ; + } const ada = correctAdaFormat(votingPower); @@ -62,18 +63,35 @@ export const DRepDirectoryContent: FC = ({ {isConnected && (
- {t("dRepDirectory.delegationOptions")} - + + {t("dRepDirectory.delegationOptions")} + +
)}
- {t("dRepDirectory.listTitle")} + + {t('dRepDirectory.listTitle')} + - {dRepList?.map((dRep) => (isSameDRep(dRep, myDrep?.view) - ? null - : ( - + {dRepList?.map((dRep) => + (isSameDRep(dRep, myDrep?.view) ? null : ( + = ({ onDelegate={() => delegate(dRep.drepId)} /> - )))} + )), + )}
From d0650b9325c54730a2a893a91a016b105ca1ed12 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Wed, 27 Mar 2024 01:19:34 +0100 Subject: [PATCH 014/143] [#220] changes after CR - refactor determining StatusPill colors - add Sentry.captureException on delegate error --- .../src/components/atoms/StatusPill.tsx | 32 ++++++------------- .../frontend/src/hooks/useDelegateToDrep.ts | 2 ++ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/govtool/frontend/src/components/atoms/StatusPill.tsx b/govtool/frontend/src/components/atoms/StatusPill.tsx index ff08844d2..b09fcebf9 100644 --- a/govtool/frontend/src/components/atoms/StatusPill.tsx +++ b/govtool/frontend/src/components/atoms/StatusPill.tsx @@ -24,33 +24,21 @@ export const StatusPill = ({ /> ); -const getBgColor = (status: Status): string => { - switch (status) { - case 'Active': - return successGreen.c200; - case 'Inactive': - return cyan.c100; - case 'Retired': - return errorRed.c100; - // no default - } +const bgColor = { + Active: successGreen.c200, + Inactive: cyan.c100, + Retired: errorRed.c100, }; -const getTextColor = (status: Status): string => { - switch (status) { - case 'Active': - return successGreen.c700; - case 'Inactive': - return cyan.c500; - case 'Retired': - return errorRed.c500; - // no default - } +const textColor = { + Active: successGreen.c700, + Inactive: cyan.c500, + Retired: errorRed.c500, }; const StyledChip = styled(Chip)<{ status: Status }>(({ theme, status }) => ({ - backgroundColor: getBgColor(status), - color: getTextColor(status), + backgroundColor: bgColor[status], + color: textColor[status], border: `2px solid ${theme.palette.neutralWhite}`, fontSize: '0.75rem', textTransform: 'capitalize', diff --git a/govtool/frontend/src/hooks/useDelegateToDrep.ts b/govtool/frontend/src/hooks/useDelegateToDrep.ts index 0183e82de..db43104a5 100644 --- a/govtool/frontend/src/hooks/useDelegateToDrep.ts +++ b/govtool/frontend/src/hooks/useDelegateToDrep.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from "react"; +import * as Sentry from "@sentry/react"; import { useTranslation } from "@hooks"; import { useCardano, useSnackbar } from "@/context"; @@ -26,6 +27,7 @@ export const useDelegateTodRep = () => { addSuccessAlert(t("alerts.delegate.success")); } } catch (error) { + Sentry.captureException(error); addErrorAlert(t("alerts.delegate.failed")); } finally { setIsDelegating(false); From 7fc84a02fb1678ac58be28479ce5b086418d4c26 Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Wed, 27 Mar 2024 18:10:24 +0100 Subject: [PATCH 015/143] [#220] fix drep list infinite loader for unnconnected users --- govtool/frontend/src/pages/DRepDetails.tsx | 2 +- govtool/frontend/src/pages/DRepDirectoryContent.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 1d0783358..1ab2d2d1a 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -50,7 +50,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { data, isLoading } = useGetDRepListQuery(dRepParam); const dRep = data?.[0]; - if (!dRep && isLoading) return ; + if (!dRep && isLoading) return ; if (!dRep) return ; diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 58b8b3daf..250457df3 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -21,7 +21,6 @@ export const DRepDirectoryContent: FC = ({ }) => { const { dRepID: myDRepId, - isEnabled, pendingTransaction, stakeKey, } = useCardano(); @@ -40,7 +39,7 @@ export const DRepDirectoryContent: FC = ({ const myDrep = myDRepList?.[0]; const { data: dRepList } = useGetDRepListQuery(); - if (!isEnabled || votingPower === undefined || !dRepList) { + if ((stakeKey && votingPower === undefined) || !dRepList) { return ; } From 61b0d08e91c8951b08b353eb55769714d518a68a Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Wed, 27 Mar 2024 00:53:59 +0100 Subject: [PATCH 016/143] [#221] add search, filter and sort to drep list --- .../src/components/atoms/StatusPill.tsx | 19 +++---- .../src/components/organisms/DRepCard.tsx | 7 +++ .../src/components/organisms/index.ts | 1 - .../src/consts/dRepDirectory/filters.ts | 6 ++ .../src/consts/dRepDirectory/index.ts | 2 + .../src/consts/dRepDirectory/sorting.ts | 14 +++++ govtool/frontend/src/consts/index.ts | 1 + .../src/hooks/queries/useGetDRepListQuery.ts | 22 ++++++-- govtool/frontend/src/i18n/locales/en.ts | 3 + govtool/frontend/src/models/api.ts | 8 ++- govtool/frontend/src/pages/DRepDetails.tsx | 2 +- .../src/pages/DRepDirectoryContent.tsx | 56 +++++++++++++++++-- .../src/services/requests/getDRepList.ts | 10 +++- .../src/stories/StatusPill.stories.ts | 7 ++- 14 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 govtool/frontend/src/consts/dRepDirectory/filters.ts create mode 100644 govtool/frontend/src/consts/dRepDirectory/index.ts create mode 100644 govtool/frontend/src/consts/dRepDirectory/sorting.ts diff --git a/govtool/frontend/src/components/atoms/StatusPill.tsx b/govtool/frontend/src/components/atoms/StatusPill.tsx index b09fcebf9..e8fbf0377 100644 --- a/govtool/frontend/src/components/atoms/StatusPill.tsx +++ b/govtool/frontend/src/components/atoms/StatusPill.tsx @@ -1,10 +1,9 @@ import { Chip, ChipProps, styled } from "@mui/material"; import { cyan, errorRed, successGreen } from "@/consts"; - -type Status = 'Active' | 'Inactive' | 'Retired'; +import { DRepStatus } from "@/models"; interface StatusPillProps { - status: Status; + status: DRepStatus; label?: string; size?: 'small' | 'medium'; sx?: ChipProps['sx']; @@ -25,18 +24,18 @@ export const StatusPill = ({ ); const bgColor = { - Active: successGreen.c200, - Inactive: cyan.c100, - Retired: errorRed.c100, + [DRepStatus.Active]: successGreen.c200, + [DRepStatus.Inactive]: cyan.c100, + [DRepStatus.Retired]: errorRed.c100, }; const textColor = { - Active: successGreen.c700, - Inactive: cyan.c500, - Retired: errorRed.c500, + [DRepStatus.Active]: successGreen.c700, + [DRepStatus.Inactive]: cyan.c500, + [DRepStatus.Retired]: errorRed.c500, }; -const StyledChip = styled(Chip)<{ status: Status }>(({ theme, status }) => ({ +const StyledChip = styled(Chip)<{ status: DRepStatus }>(({ theme, status }) => ({ backgroundColor: bgColor[status], color: textColor[status], border: `2px solid ${theme.palette.neutralWhite}`, diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index 25faa77c1..786969754 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -7,6 +7,7 @@ import { Card } from "@molecules"; import { correctAdaFormat } from "@/utils"; import { ICONS, PATHS } from "@/consts"; import { DRepData } from "@/models"; +import { useSnackbar } from "@/context"; type DRepCardProps = { dRep: DRepData; @@ -30,6 +31,7 @@ export const DRepCard = ({ }: DRepCardProps) => { const navigate = useNavigate(); const { t } = useTranslation(); + const { addSuccessAlert } = useSnackbar(); return ( {type} { + navigator.clipboard.writeText(view); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }} sx={{ gap: 1, maxWidth: "100%", diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index 3b775bf2e..6f12d1e6f 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -16,7 +16,6 @@ export * from "./DelegateTodRepStepTwo"; export * from "./Drawer"; export * from "./DrawerMobile"; export * from "./DRepCard"; -export * from "./DRepDirectoryContent"; export * from "./EditDRepInfoSteps"; export * from "./ExternalLinkModal"; export * from "./Footer"; diff --git a/govtool/frontend/src/consts/dRepDirectory/filters.ts b/govtool/frontend/src/consts/dRepDirectory/filters.ts new file mode 100644 index 000000000..f6a089f8e --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/filters.ts @@ -0,0 +1,6 @@ +import { DRepStatus } from "@/models"; + +export const DREP_DIRECTORY_FILTERS = Object.values(DRepStatus).map((status) => ({ + key: status, + label: status, +})); diff --git a/govtool/frontend/src/consts/dRepDirectory/index.ts b/govtool/frontend/src/consts/dRepDirectory/index.ts new file mode 100644 index 000000000..93e21800d --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/index.ts @@ -0,0 +1,2 @@ +export * from "./filters"; +export * from "./sorting"; diff --git a/govtool/frontend/src/consts/dRepDirectory/sorting.ts b/govtool/frontend/src/consts/dRepDirectory/sorting.ts new file mode 100644 index 000000000..ab5b531bb --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/sorting.ts @@ -0,0 +1,14 @@ +export const DREP_DIRECTORY_SORTING = [ + { + key: "NewestRegistered", + label: "Newest registered", + }, + { + key: "VotingPower", + label: "Voting power", + }, + { + key: "Status", + label: "Status", + }, +]; diff --git a/govtool/frontend/src/consts/index.ts b/govtool/frontend/src/consts/index.ts index 22a9c3bbc..87b876b2e 100644 --- a/govtool/frontend/src/consts/index.ts +++ b/govtool/frontend/src/consts/index.ts @@ -1,6 +1,7 @@ export * from "./externalDataModalConfig"; export * from "./colors"; export * from "./dRepActions"; +export * from "./dRepDirectory"; export * from "./governanceAction"; export * from "./icons"; export * from "./images"; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index b00932f1d..2d5882fe8 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -2,24 +2,34 @@ import { UseQueryOptions, useQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; -import { getDRepList } from "@services"; +import { GetDRepListParams, getDRepList } from "@services"; import { DRepData } from "@/models"; -export const useGetDRepListQuery = (dRepView?: string, options?: UseQueryOptions) => { +export const useGetDRepListQuery = ( + params?: GetDRepListParams, + options?: UseQueryOptions +) => { + const { drepView, sort, status } = params || {}; const { pendingTransaction } = useCardano(); - const { data, isLoading } = useQuery({ + const { data, isLoading, isPreviousData } = useQuery({ queryKey: [ QUERY_KEYS.useGetDRepListKey, (pendingTransaction.registerAsSoleVoter || pendingTransaction.registerAsDrep || pendingTransaction.retireAsSoleVoter || pendingTransaction.retireAsDrep)?.transactionHash, - dRepView + drepView, + sort, + status, ], - queryFn: () => getDRepList(dRepView), + queryFn: () => getDRepList({ + ...(drepView && { drepView }), + ...(sort && { sort }), + ...(status && { status }), + }), ...options }); - return { data, isLoading }; + return { data, isLoading, isPreviousData }; }; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 58192e02a..8debf7007 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -255,6 +255,7 @@ export const en = { automatedVotingOptions: "Automated Voting Options", editBtn: "Edit DRep data", delegationOptions: "Delegation Options", + filterTitle: "DRep Status", meAsDRep: "This DRep ID is connected to your wallet", myDelegation: "You have delegated ₳ {{ada}} to:", myDRep: "This is your DRep", @@ -262,6 +263,8 @@ export const en = { noConfidenceDescription: "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", noConfidenceTitle: "Signal No Confidence on Every Vote", + noResultsForTheSearchTitle: "No DReps found", + noResultsForTheSearchDescription: "Please try a different search", title: "DRep Directory", votingPower: "Voting Power", }, diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index fdfcbb86d..0686601b7 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -8,6 +8,12 @@ export interface VoterInfo { deposit: number; } +export enum DRepStatus { + Active = "Active", + Inactive = "Inactive", + Retired = "Retired", +} + export interface DRepData { drepId: string; view: string; @@ -15,7 +21,7 @@ export interface DRepData { metadataHash: string; deposit: number; votingPower: number; - status: 'Active' | 'Inactive' | 'Retired'; + status: DRepStatus; type: 'DRep' | 'SoleVoter'; } diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 1ab2d2d1a..9b8284cdc 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -47,7 +47,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { delegate, isDelegating } = useDelegateTodRep(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); - const { data, isLoading } = useGetDRepListQuery(dRepParam); + const { data, isLoading } = useGetDRepListQuery({ drepView: dRepParam }); const dRep = data?.[0]; if (!dRep && isLoading) return ; diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 250457df3..08c14283d 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -3,14 +3,17 @@ import { FC } from "react"; import { AutomatedVotingOptions, DRepCard } from "@organisms"; import { Typography } from "@atoms"; import { Trans, useTranslation } from "react-i18next"; +import { Card, DataActionsBar } from "@molecules"; import { useCardano } from "@/context"; import { + useDataActionsBar, useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, useGetAdaHolderVotingPowerQuery, useGetDRepListQuery } from "@/hooks"; import { correctAdaFormat, formHexToBech32, isSameDRep } from "@/utils"; +import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@/consts"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -25,6 +28,8 @@ export const DRepDirectoryContent: FC = ({ stakeKey, } = useCardano(); const { t } = useTranslation(); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenFilters, chosenSorting } = dataActionsBarProps; const { delegate } = useDelegateTodRep(); @@ -33,13 +38,21 @@ export const DRepDirectoryContent: FC = ({ const inProgressDelegation = pendingTransaction.delegate?.resourceId; const { data: myDRepList } = useGetDRepListQuery( - currentDelegation?.startsWith('drep') ? currentDelegation : formHexToBech32(currentDelegation), + { drepView: currentDelegation?.startsWith('drep') + ? currentDelegation + : formHexToBech32(currentDelegation) }, { enabled: !!inProgressDelegation || !!currentDelegation } ); const myDrep = myDRepList?.[0]; - const { data: dRepList } = useGetDRepListQuery(); + const { data: dRepList, isPreviousData } = useGetDRepListQuery({ + drepView: debouncedSearchText, + sort: chosenSorting, + status: chosenFilters, + }, { + keepPreviousData: true, + }); - if ((stakeKey && votingPower === undefined) || !dRepList) { + if (stakeKey && votingPower === undefined) { return ; } @@ -47,6 +60,7 @@ export const DRepDirectoryContent: FC = ({ return ( + {/* My delegation */} {myDrep && (
@@ -60,6 +74,7 @@ export const DRepDirectoryContent: FC = ({
)} + {/* Automated voting options */} {isConnected && (
@@ -83,11 +98,42 @@ export const DRepDirectoryContent: FC = ({
)} + {/* DRep list */}
- + {t('dRepDirectory.listTitle')} - + + + {dRepList?.length === 0 && ( + + {t('dRepDirectory.noResultsForTheSearchTitle')} + {t('dRepDirectory.noResultsForTheSearchDescription')} + + )} {dRepList?.map((dRep) => (isSameDRep(dRep, myDrep?.view) ? null : ( diff --git a/govtool/frontend/src/services/requests/getDRepList.ts b/govtool/frontend/src/services/requests/getDRepList.ts index 9452638af..c4c0af028 100644 --- a/govtool/frontend/src/services/requests/getDRepList.ts +++ b/govtool/frontend/src/services/requests/getDRepList.ts @@ -1,7 +1,13 @@ import type { DRepData } from "@models"; import { API } from "../API"; -export const getDRepList = async (drepView?: string) => { - const response = await API.get("/drep/list", drepView ? { params: { drepView } } : undefined); +export type GetDRepListParams = { + drepView?: string; + sort?: string; + status?: string[]; +}; + +export const getDRepList = async (params: GetDRepListParams) => { + const response = await API.get("/drep/list", { params }); return response.data; }; diff --git a/govtool/frontend/src/stories/StatusPill.stories.ts b/govtool/frontend/src/stories/StatusPill.stories.ts index 0ca27c631..1a8467cad 100644 --- a/govtool/frontend/src/stories/StatusPill.stories.ts +++ b/govtool/frontend/src/stories/StatusPill.stories.ts @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { StatusPill } from "@atoms"; +import { DRepStatus } from "@/models"; const meta = { title: "Example/StatusPill", @@ -16,18 +17,18 @@ type Story = StoryObj; export const StatusPillActive: Story = { args: { - status: "Active", + status: DRepStatus.Active, }, }; export const StatusPillInactive: Story = { args: { - status: "Inactive", + status: DRepStatus.Inactive, }, }; export const StatusPillRetired: Story = { args: { - status: "Retired", + status: DRepStatus.Retired, }, }; From 82b154402a564b8ec331618afed3d42a4724fd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Fri, 5 Apr 2024 14:30:43 +0200 Subject: [PATCH 017/143] fix: change edit drep navigation path --- govtool/frontend/src/pages/DRepDetails.tsx | 95 +++++++++++----------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 9b8284cdc..690634bcc 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -1,12 +1,8 @@ import { PropsWithChildren } from "react"; import { Navigate, useNavigate, useParams } from "react-router-dom"; -import { - Box, ButtonBase, Chip, CircularProgress -} from "@mui/material"; +import { Box, ButtonBase, Chip, CircularProgress } from "@mui/material"; -import { - Button, LoadingButton, StatusPill, Typography -} from "@atoms"; +import { Button, LoadingButton, StatusPill, Typography } from "@atoms"; import { Card, LinkWithIcon, Share } from "@molecules"; import { ICONS, PATHS } from "@consts"; import { @@ -14,7 +10,7 @@ import { useGetAdaHolderCurrentDelegationQuery, useGetDRepListQuery, useScreenDimension, - useTranslation + useTranslation, } from "@hooks"; import { correctAdaFormat, openInNewTab } from "@utils"; import { useCardano, useModal } from "@/context"; @@ -33,11 +29,7 @@ type DRepDetailsProps = { }; export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { - const { - dRepID: myDRepId, - pendingTransaction, - stakeKey, - } = useCardano(); + const { dRepID: myDRepId, pendingTransaction, stakeKey } = useCardano(); const { t } = useTranslation(); const navigate = useNavigate(); const { openModal } = useModal(); @@ -50,23 +42,29 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { data, isLoading } = useGetDRepListQuery({ drepView: dRepParam }); const dRep = data?.[0]; - if (!dRep && isLoading) return ; + if (!dRep && isLoading) + return ; if (!dRep) return ; - const { - view, status, votingPower, type - } = dRep; + const { view, status, votingPower, type } = dRep; const isMe = isSameDRep(dRep, myDRepId); const isMyDrep = isSameDRep(dRep, currentDelegation); - const isMyDrepInProgress = isSameDRep(dRep, pendingTransaction.delegate?.resourceId); + const isMyDrepInProgress = isSameDRep( + dRep, + pendingTransaction.delegate?.resourceId, + ); return ( <> navigate(isConnected ? PATHS.dashboardDRepDirectory : PATHS.dRepDirectory)} + onClick={() => + navigate( + isConnected ? PATHS.dashboardDRepDirectory : PATHS.dRepDirectory, + ) + } sx={{ mb: 2 }} /> { label: t("inProgress"), })} sx={{ - borderRadius: 5, mt: isMe || isMyDrep ? 1 : 0, pb: 4, pt: 2.25 + borderRadius: 5, + mt: isMe || isMyDrep ? 1 : 0, + pb: 4, + pt: 2.25, }} > {(isMe || isMyDrep) && ( theme.shadows[2], color: (theme) => theme.palette.text.primary, mb: 1.5, px: 2, py: 0.5, - width: '100%', + width: "100%", }} /> )} @@ -102,7 +105,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { display: "flex", flexDirection: "row", gap: 1, - mb: 3 + mb: 3, }} > { {isMe && ( + ); diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 1188ff912..99131c32e 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -299,8 +299,9 @@ export const en = { }, }, footer: { - copyright: "© 2023 Voltaire Gov Tool", + copyright: "© 2024 Intersect MBO", privacyPolicy: "Privacy policy", + termOfService: "Term of service", }, forms: { hashPlaceholder: "The hash of metadata at URL", @@ -708,6 +709,7 @@ export const en = { confirm: "Confirm", continue: "Continue", delegate: "Delegate", + feedback: "Feedback", filter: "Filter", goBack: "Go back", here: "here", diff --git a/govtool/frontend/src/pages/ChooseStakeKey.tsx b/govtool/frontend/src/pages/ChooseStakeKey.tsx index 8f680535c..f8f4ae739 100644 --- a/govtool/frontend/src/pages/ChooseStakeKey.tsx +++ b/govtool/frontend/src/pages/ChooseStakeKey.tsx @@ -3,20 +3,15 @@ import { Box } from "@mui/material"; import { Background } from "@atoms"; import { TopNav, ChooseStakeKeyPanel, Footer } from "@organisms"; -import { useScreenDimension } from "@/hooks"; -export const ChooseStakeKey = () => { - const { isMobile } = useScreenDimension(); - - return ( - - - - - - - {isMobile &&
} +export const ChooseStakeKey = () => ( + + + + + - - ); -}; +
+ + +); diff --git a/govtool/frontend/src/pages/CreateGovernanceAction.tsx b/govtool/frontend/src/pages/CreateGovernanceAction.tsx index 1977dd5af..658494a16 100644 --- a/govtool/frontend/src/pages/CreateGovernanceAction.tsx +++ b/govtool/frontend/src/pages/CreateGovernanceAction.tsx @@ -88,7 +88,7 @@ export const CreateGovernanceAction = () => { {step === 5 && } {step === 6 && } - {isMobile &&
} +
); diff --git a/govtool/frontend/src/pages/Dashboard.tsx b/govtool/frontend/src/pages/Dashboard.tsx index 80c9797b1..7fc32b7b5 100644 --- a/govtool/frontend/src/pages/Dashboard.tsx +++ b/govtool/frontend/src/pages/Dashboard.tsx @@ -62,7 +62,7 @@ export const Dashboard = () => { - {isMobile ?
: null} +
diff --git a/govtool/frontend/src/pages/DelegateTodRep.tsx b/govtool/frontend/src/pages/DelegateTodRep.tsx index 56b0c8ff3..343b1d9b2 100644 --- a/govtool/frontend/src/pages/DelegateTodRep.tsx +++ b/govtool/frontend/src/pages/DelegateTodRep.tsx @@ -41,7 +41,7 @@ export const DelegateTodRep = () => { {step === 1 && } {step === 2 && } - {isMobile &&
} +
); diff --git a/govtool/frontend/src/pages/EditDRepMetadata.tsx b/govtool/frontend/src/pages/EditDRepMetadata.tsx index e8296ee45..41fd70d2d 100644 --- a/govtool/frontend/src/pages/EditDRepMetadata.tsx +++ b/govtool/frontend/src/pages/EditDRepMetadata.tsx @@ -82,7 +82,7 @@ export const EditDRepMetadata = () => { {step === 2 && } {step === 3 && } - {isMobile &&
} +
); diff --git a/govtool/frontend/src/pages/GovernanceActions.tsx b/govtool/frontend/src/pages/GovernanceActions.tsx index bd4a807ed..6fb8676d2 100644 --- a/govtool/frontend/src/pages/GovernanceActions.tsx +++ b/govtool/frontend/src/pages/GovernanceActions.tsx @@ -3,7 +3,11 @@ import { useNavigate } from "react-router-dom"; import { Box, CircularProgress, Divider } from "@mui/material"; import { Background, ScrollToManage, Typography } from "@atoms"; -import { GOVERNANCE_ACTIONS_FILTERS, GOVERNANCE_ACTIONS_SORTING, PATHS } from "@consts"; +import { + GOVERNANCE_ACTIONS_FILTERS, + GOVERNANCE_ACTIONS_SORTING, + PATHS, +} from "@consts"; import { useCardano } from "@context"; import { useDataActionsBar, @@ -16,7 +20,7 @@ import { Footer, TopNav, GovernanceActionsToVote } from "@organisms"; import { WALLET_LS_KEY, getItemFromLocalStorage } from "@utils"; const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( - (category) => category.key + (category) => category.key, ); export const GovernanceActions = () => { diff --git a/govtool/frontend/src/pages/RegisterAsdRep.tsx b/govtool/frontend/src/pages/RegisterAsdRep.tsx index fc2d1430f..88f2b1d9e 100644 --- a/govtool/frontend/src/pages/RegisterAsdRep.tsx +++ b/govtool/frontend/src/pages/RegisterAsdRep.tsx @@ -115,7 +115,7 @@ export const RegisterAsdRep = () => { )} - {isMobile &&
} +
); diff --git a/govtool/frontend/src/pages/RetireAsDrep.tsx b/govtool/frontend/src/pages/RetireAsDrep.tsx index 6d03aebea..920dd5895 100644 --- a/govtool/frontend/src/pages/RetireAsDrep.tsx +++ b/govtool/frontend/src/pages/RetireAsDrep.tsx @@ -4,7 +4,7 @@ import { Box } from "@mui/material"; import { Background } from "@atoms"; import { PATHS } from "@consts"; -import { DashboardTopNav, WhatRetirementMeans } from "@organisms"; +import { DashboardTopNav, Footer, WhatRetirementMeans } from "@organisms"; import { useScreenDimension, useTranslation } from "@hooks"; import { LinkWithIcon } from "@molecules"; import { checkIsWalletConnected } from "@utils"; @@ -41,6 +41,7 @@ export const RetireAsDrep = () => { }} /> +
); From 8ddd3025d37063dbca63c1e003b75f4f631e0a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Fri, 5 Apr 2024 16:58:16 +0200 Subject: [PATCH 019/143] [#545] add footer to drep directory --- govtool/frontend/src/pages/DRepDirectory.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/govtool/frontend/src/pages/DRepDirectory.tsx b/govtool/frontend/src/pages/DRepDirectory.tsx index d3569f848..cf3daac90 100644 --- a/govtool/frontend/src/pages/DRepDirectory.tsx +++ b/govtool/frontend/src/pages/DRepDirectory.tsx @@ -2,7 +2,7 @@ import { useTranslation } from "@hooks"; import { Outlet } from "react-router-dom"; import { checkIsWalletConnected } from "@/utils"; import { Background, PagePaddingBox, ContentBox } from "@/components/atoms"; -import { TopNav } from "@/components/organisms"; +import { Footer, TopNav } from "@/components/organisms"; import { PageTitle } from "@/components/molecules"; export const DRepDirectory = () => { @@ -29,6 +29,7 @@ export const DRepDirectory = () => { +
); }; From bbaee21b411d34d29d8fc0fae8c41ee49df69f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Fri, 5 Apr 2024 17:09:48 +0200 Subject: [PATCH 020/143] bugfix: change dispatch condition on deployment workflows --- .github/workflows/build-and-deploy-staging.yml | 4 +--- .github/workflows/build-and-deploy-test.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-deploy-staging.yml b/.github/workflows/build-and-deploy-staging.yml index 54fb51634..f53352b35 100644 --- a/.github/workflows/build-and-deploy-staging.yml +++ b/.github/workflows/build-and-deploy-staging.yml @@ -2,11 +2,9 @@ name: Build and deploy GovTool to STAGING server run-name: Deploy by @${{ github.actor }} on: - pull_request: + push: branches: - staging - types: - - closed env: ENVIRONMENT: "staging" diff --git a/.github/workflows/build-and-deploy-test.yml b/.github/workflows/build-and-deploy-test.yml index a254023df..8c4a3424c 100644 --- a/.github/workflows/build-and-deploy-test.yml +++ b/.github/workflows/build-and-deploy-test.yml @@ -2,11 +2,9 @@ name: Build and deploy GovTool to TEST server run-name: Deploy by @${{ github.actor }} on: - pull_request: + push: branches: - test - types: - - closed env: ENVIRONMENT: "test" From 82c80b6b45bfd7b7e7e7e7e4d28597e0972b8d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Wed, 3 Apr 2024 18:56:11 +0200 Subject: [PATCH 021/143] [#617] feat: integrate frontend with metadata validation service --- .github/workflows/lighthouse.yml | 22 ++++---- CHANGELOG.md | 2 + govtool/frontend/Makefile | 2 +- .../src/consts/externalDataModalConfig.ts | 10 ++-- govtool/frontend/src/consts/queryKeys.ts | 4 ++ .../forms/useCreateGovernanceActionForm.ts | 32 ++++++----- .../src/hooks/forms/useEditDRepInfoForm.ts | 25 ++++----- .../src/hooks/forms/useRegisterAsdRepForm.tsx | 28 +++++----- .../src/hooks/forms/useVoteContextForm.tsx | 34 ++++++------ govtool/frontend/src/hooks/mutations/index.ts | 1 + .../mutations/metadataValidation/index.ts | 1 + .../metadataValidation/useValidateMutation.ts | 18 +++++++ govtool/frontend/src/models/index.ts | 1 + .../frontend/src/models/metadataValidation.ts | 16 ++++++ govtool/frontend/src/services/API.ts | 9 +++- .../frontend/src/services/requests/index.ts | 1 + .../requests/metadataValidation/index.ts | 1 + .../metadataValidation/postValidate.ts | 11 ++++ .../utils/tests/validateMetadataHash.test.ts | 24 --------- .../src/utils/validateMetadataHash.ts | 54 +------------------ .../metadata-validation/src/app.controller.ts | 4 +- .../config/templates/docker-compose.yml.tpl | 2 +- 22 files changed, 146 insertions(+), 156 deletions(-) create mode 100644 govtool/frontend/src/hooks/mutations/index.ts create mode 100644 govtool/frontend/src/hooks/mutations/metadataValidation/index.ts create mode 100644 govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts create mode 100644 govtool/frontend/src/models/metadataValidation.ts create mode 100644 govtool/frontend/src/services/requests/metadataValidation/index.ts create mode 100644 govtool/frontend/src/services/requests/metadataValidation/postValidate.ts delete mode 100644 govtool/frontend/src/utils/tests/validateMetadataHash.test.ts diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index bf1d30c45..2bf75ada6 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -36,23 +36,23 @@ jobs: - name: Run build and lighthouse task working-directory: ./govtool/frontend run: | - npm install - VITE_BASE_URL=https://staging.govtool.byron.network/api npm run build - lhci collect + npm install + VITE_BASE_URL=https://staging.govtool.byron.network/ npm run build + lhci collect - name: Evaluate reports if: github.repository_owner != 'IntersectMBO' run: | - lhci assert --preset "lighthouse:recommended" + lhci assert --preset "lighthouse:recommended" - name: Publish reports working-directory: ./govtool/frontend if: github.repository_owner == 'IntersectMBO' run: | - lhci assert --preset lighthouse:recommended || echo "LightHouse Assertion error ignored ..." - lhci upload --githubAppToken="${{ secrets.LHCI_GITHUB_APP_TOKEN }}" --token="${{ secrets.LHCI_SERVER_TOKEN }}" --serverBaseUrl=https://lighthouse.cardanoapi.io --ignoreDuplicateBuildFailure - curl -X POST https://ligththouse.cardanoapi.io/api/metrics/build-reports \ - -d "@./lighthouseci/$(ls ./.lighthouseci |grep 'lhr.*\.json' | head -n 1)" \ - -H "commit-hash: $(git rev-parse HEAD)" \ - -H "secret-token: ${{ secrets.METRICS_SERVER_SECRET_TOKEN }}" \ - -H 'Content-Type: application/json' || echo "Metric Upload error ignored ..." + lhci assert --preset lighthouse:recommended || echo "LightHouse Assertion error ignored ..." + lhci upload --githubAppToken="${{ secrets.LHCI_GITHUB_APP_TOKEN }}" --token="${{ secrets.LHCI_SERVER_TOKEN }}" --serverBaseUrl=https://lighthouse.cardanoapi.io --ignoreDuplicateBuildFailure + curl -X POST https://ligththouse.cardanoapi.io/api/metrics/build-reports \ + -d "@./lighthouseci/$(ls ./.lighthouseci |grep 'lhr.*\.json' | head -n 1)" \ + -H "commit-hash: $(git rev-parse HEAD)" \ + -H "secret-token: ${{ secrets.METRICS_SERVER_SECRET_TOKEN }}" \ + -H 'Content-Type: application/json' || echo "Metric Upload error ignored ..." diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3c92587..961928ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ changes. ## [Unreleased] +- Integrate frontend with metadata validation service [Issue 617](https://github.com/IntersectMBO/govtool/issues/617) + ### Added - added `epochNo` and `date` to `drep/getVotes` and `proposal/get` diff --git a/govtool/frontend/Makefile b/govtool/frontend/Makefile index 24f0702dc..f8dd5c348 100644 --- a/govtool/frontend/Makefile +++ b/govtool/frontend/Makefile @@ -15,7 +15,7 @@ build-frontend: docker-login if [[ "$(cardano_network)" = "mainnet" ]]; then NETWORK_FLAG=1; else NETWORK_FLAG=0; fi; \ $(call check_image_on_ecr,frontend,$(frontend_image_tag)) || \ $(docker) build --tag "$(repo_url)/frontend:$(frontend_image_tag)" \ - --build-arg VITE_BASE_URL="https://$(domain)/api" \ + --build-arg VITE_BASE_URL="https://$(domain)" \ --build-arg VITE_GTM_ID="$${GTM_ID}" \ --build-arg VITE_NETWORK_FLAG="$$NETWORK_FLAG" \ --build-arg VITE_SENTRY_DSN="$${SENTRY_DSN}" \ diff --git a/govtool/frontend/src/consts/externalDataModalConfig.ts b/govtool/frontend/src/consts/externalDataModalConfig.ts index 1508004f4..132b20103 100644 --- a/govtool/frontend/src/consts/externalDataModalConfig.ts +++ b/govtool/frontend/src/consts/externalDataModalConfig.ts @@ -1,5 +1,6 @@ import { ModalState } from "@/context"; import I18n from "@/i18n"; +import { MetadataValidationStatus } from "@/models"; export enum MetadataHashValidationErrors { INVALID_URL = "Invalid URL", @@ -29,13 +30,12 @@ const urlCannotBeFound = { }; export const storageInformationErrorModals: Record< - MetadataHashValidationErrors, + MetadataValidationStatus, ModalState< typeof externalDataDoesntMatchModal | typeof urlCannotBeFound >["state"] > = { - [MetadataHashValidationErrors.INVALID_URL]: urlCannotBeFound, - [MetadataHashValidationErrors.FETCH_ERROR]: urlCannotBeFound, - [MetadataHashValidationErrors.INVALID_JSON]: externalDataDoesntMatchModal, - [MetadataHashValidationErrors.INVALID_HASH]: externalDataDoesntMatchModal, + [MetadataValidationStatus.URL_NOT_FOUND]: urlCannotBeFound, + [MetadataValidationStatus.INVALID_JSONLD]: externalDataDoesntMatchModal, + [MetadataValidationStatus.INVALID_HASH]: externalDataDoesntMatchModal, }; diff --git a/govtool/frontend/src/consts/queryKeys.ts b/govtool/frontend/src/consts/queryKeys.ts index 66a12b0b1..9a1e3c887 100644 --- a/govtool/frontend/src/consts/queryKeys.ts +++ b/govtool/frontend/src/consts/queryKeys.ts @@ -10,3 +10,7 @@ export const QUERY_KEYS = { useGetDRepInfoKey: "useGetDRepInfoKey", useGetVoteContextFromFile: "useGetVoteContextFromFile", }; + +export const MUTATION_KEYS = { + postValidateKey: "postValidateKey", +}; diff --git a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts index e8ff4f660..24ed8a26b 100644 --- a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts +++ b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts @@ -10,22 +10,19 @@ import { CIP_100, CIP_108, GOVERNANCE_ACTION_CONTEXT, - MetadataHashValidationErrors, PATHS, storageInformationErrorModals, } from "@consts"; import { useCardano, useModal } from "@context"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - validateMetadataHash, -} from "@utils"; +import { canonizeJSON, downloadJson, generateJsonld } from "@utils"; +import { MetadataValidationStatus } from "@models"; import { GovernanceActionFieldSchemas, GovernanceActionType, } from "@/types/governanceAction"; +import { useValidateMutation } from "../mutations"; + export type CreateGovernanceActionValues = { links?: { link: string }[]; storeData?: boolean; @@ -48,6 +45,7 @@ export const useCreateGovernanceActionForm = ( buildTreasuryGovernanceAction, buildSignSubmitConwayCertTx, } = useCardano(); + const { validateMetadata } = useValidateMutation(); const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [hash, setHash] = useState(null); @@ -120,22 +118,27 @@ export const useCreateGovernanceActionForm = ( }, [govActionType, json]); const validateHash = useCallback( - async (storingUrl: string, localHash: string | null) => { + async (url: string, localHash: string | null) => { try { if (!localHash) { - throw new Error(MetadataHashValidationErrors.INVALID_HASH); + throw new Error(MetadataValidationStatus.INVALID_HASH); } - await validateMetadataHash(storingUrl, localHash); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { + const result = await validateMetadata({ url, hash: localHash }); + + if (result.status) { + throw result.status; + } + } catch (error) { if ( - Object.values(MetadataHashValidationErrors).includes(error.message) + Object.values(MetadataValidationStatus).includes( + error as MetadataValidationStatus, + ) ) { openModal({ type: "statusModal", state: { ...storageInformationErrorModals[ - error.message as MetadataHashValidationErrors + error as MetadataValidationStatus ], onSubmit: backToForm, onCancel: backToDashboard, @@ -214,6 +217,7 @@ export const useCreateGovernanceActionForm = ( setIsLoading(true); await validateHash(data.storingURL, hash); + const govActionBuilder = await buildTransaction(data); await buildSignSubmitConwayCertTx({ govActionBuilder, diff --git a/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts b/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts index 89795269c..60a3e6bd0 100644 --- a/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts +++ b/govtool/frontend/src/hooks/forms/useEditDRepInfoForm.ts @@ -15,12 +15,10 @@ import { storageInformationErrorModals, } from "@consts"; import { useCardano, useModal } from "@context"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - validateMetadataHash, -} from "@utils"; +import { canonizeJSON, downloadJson, generateJsonld } from "@utils"; +import { MetadataValidationStatus } from "@models"; + +import { useValidateMutation } from "../mutations"; export type EditDRepInfoValues = { bio?: string; @@ -43,6 +41,7 @@ export const defaultEditDRepInfoValues: EditDRepInfoValues = { export const useEditDRepInfoForm = ( setStep?: Dispatch>, ) => { + const { validateMetadata } = useValidateMutation(); const { t } = useTranslation(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(false); @@ -113,21 +112,23 @@ export const useEditDRepInfoForm = ( }; const validateHash = useCallback( - async (storingUrl: string) => { + async (url: string) => { try { if (!hash) throw new Error(MetadataHashValidationErrors.INVALID_HASH); - await validateMetadataHash(storingUrl, hash); + const result = await validateMetadata({ url, hash }); + + if (result.status) { + throw result.status; + } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { - if ( - Object.values(MetadataHashValidationErrors).includes(error.message) - ) { + if (Object.values(MetadataValidationStatus).includes(error)) { openModal({ type: "statusModal", state: { ...storageInformationErrorModals[ - error.message as MetadataHashValidationErrors + error as MetadataValidationStatus ], onSubmit: backToForm, onCancel: backToDashboard, diff --git a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx index 22c2aa4cd..d7f502f43 100644 --- a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx @@ -10,18 +10,15 @@ import { CIP_100, CIP_QQQ, DREP_CONTEXT, - MetadataHashValidationErrors, PATHS, storageInformationErrorModals, } from "@consts"; import { useCardano, useModal } from "@context"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - validateMetadataHash, -} from "@utils"; +import { MetadataValidationStatus } from "@models"; +import { canonizeJSON, downloadJson, generateJsonld } from "@utils"; + import { useGetVoterInfo } from ".."; +import { useValidateMutation } from "../mutations"; export type RegisterAsDRepValues = { bio?: string; @@ -44,6 +41,7 @@ export const defaultRegisterAsDRepValues: RegisterAsDRepValues = { export const useRegisterAsdRepForm = ( setStep?: Dispatch>, ) => { + const { validateMetadata } = useValidateMutation(); const { t } = useTranslation(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(false); @@ -116,21 +114,27 @@ export const useRegisterAsdRepForm = ( }; const validateHash = useCallback( - async (storingUrl: string) => { + async (url: string) => { try { - if (!hash) throw new Error(MetadataHashValidationErrors.INVALID_HASH); + if (!hash) throw new Error(MetadataValidationStatus.INVALID_HASH); - await validateMetadataHash(storingUrl, hash); + const result = await validateMetadata({ url, hash }); + + if (result.status) { + throw result.status; + } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { if ( - Object.values(MetadataHashValidationErrors).includes(error.message) + Object.values(MetadataValidationStatus).includes( + error as MetadataValidationStatus, + ) ) { openModal({ type: "statusModal", state: { ...storageInformationErrorModals[ - error.message as MetadataHashValidationErrors + error as MetadataValidationStatus ], onSubmit: backToForm, onCancel: backToDashboard, diff --git a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx index 27cfccede..b14f1722f 100644 --- a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx @@ -2,20 +2,14 @@ import { Dispatch, SetStateAction, useCallback, useState } from "react"; import { NodeObject } from "jsonld"; import { useFormContext } from "react-hook-form"; import { blake2bHex } from "blakejs"; - -import { - CIP_108, - MetadataHashValidationErrors, - VOTE_TEST_CONTEXT, -} from "@consts"; -import { - canonizeJSON, - downloadJson, - generateJsonld, - validateMetadataHash, -} from "@utils"; import { captureException } from "@sentry/react"; +import { CIP_108, VOTE_TEST_CONTEXT } from "@consts"; +import { canonizeJSON, downloadJson, generateJsonld } from "@utils"; +import { MetadataValidationStatus } from "@models"; + +import { useValidateMutation } from "../mutations"; + export type VoteContextFormValues = { voteContextText: string; terms?: boolean; @@ -27,6 +21,7 @@ export const useVoteContextForm = ( setStep?: Dispatch>, setErrorMessage?: Dispatch>, ) => { + const { validateMetadata } = useValidateMutation(); const [hash, setHash] = useState(null); const [json, setJson] = useState(null); @@ -71,18 +66,19 @@ export const useVoteContextForm = ( }, [json]); const validateHash = useCallback( - async (storingUrl: string, localHash: string | null) => { + async (url: string, localHash: string | null) => { try { if (!localHash) { - throw new Error(MetadataHashValidationErrors.INVALID_HASH); + throw new Error(MetadataValidationStatus.INVALID_HASH); + } + const result = await validateMetadata({ url, hash: localHash }); + if (result.status) { + throw result.status; } - await validateMetadataHash(storingUrl, localHash); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { - if ( - Object.values(MetadataHashValidationErrors).includes(error.message) - ) { - if (setErrorMessage) setErrorMessage(error.message); + if (Object.values(MetadataValidationStatus).includes(error)) { + if (setErrorMessage) setErrorMessage(error); if (setStep) setStep(4); } throw error; diff --git a/govtool/frontend/src/hooks/mutations/index.ts b/govtool/frontend/src/hooks/mutations/index.ts new file mode 100644 index 000000000..c70ff462e --- /dev/null +++ b/govtool/frontend/src/hooks/mutations/index.ts @@ -0,0 +1 @@ +export * from "./metadataValidation"; diff --git a/govtool/frontend/src/hooks/mutations/metadataValidation/index.ts b/govtool/frontend/src/hooks/mutations/metadataValidation/index.ts new file mode 100644 index 000000000..61abbe804 --- /dev/null +++ b/govtool/frontend/src/hooks/mutations/metadataValidation/index.ts @@ -0,0 +1 @@ +export * from "./useValidateMutation"; diff --git a/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts b/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts new file mode 100644 index 000000000..9363ca7f1 --- /dev/null +++ b/govtool/frontend/src/hooks/mutations/metadataValidation/useValidateMutation.ts @@ -0,0 +1,18 @@ +import { useMutation } 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), + mutationKey: [MUTATION_KEYS.postValidateKey], + }); + + return { + validateMetadata: mutateAsync, + validationStatus: data, + isValidating: isLoading, + }; +}; diff --git a/govtool/frontend/src/models/index.ts b/govtool/frontend/src/models/index.ts index 331bf6b43..46b6a8e5d 100644 --- a/govtool/frontend/src/models/index.ts +++ b/govtool/frontend/src/models/index.ts @@ -1,3 +1,4 @@ export * from "./api"; export * from "./snackbar"; export * from "./wallet"; +export * from "./metadataValidation"; diff --git a/govtool/frontend/src/models/metadataValidation.ts b/govtool/frontend/src/models/metadataValidation.ts new file mode 100644 index 000000000..284a8821c --- /dev/null +++ b/govtool/frontend/src/models/metadataValidation.ts @@ -0,0 +1,16 @@ +// TODO: Should be taken from @govtool/metadata-validation +export enum MetadataValidationStatus { + URL_NOT_FOUND = "URL_NOT_FOUND", + INVALID_JSONLD = "INVALID_JSONLD", + INVALID_HASH = "INVALID_HASH", +} + +export type ValidateMetadataResult = { + status?: MetadataValidationStatus; + valid: boolean; +}; + +export type MetadataValidationDTO = { + url: string; + hash: string; +}; diff --git a/govtool/frontend/src/services/API.ts b/govtool/frontend/src/services/API.ts index 8ac9341f2..1815ecbc2 100644 --- a/govtool/frontend/src/services/API.ts +++ b/govtool/frontend/src/services/API.ts @@ -1,13 +1,18 @@ import axios from "axios"; +import { NavigateFunction } from "react-router-dom"; import { PATHS } from "@consts"; -import { NavigateFunction } from "react-router-dom"; const TIMEOUT_IN_SECONDS = 30 * 1000; // 1000 ms is 1 s then its 10 s const BASE_URL = import.meta.env.VITE_BASE_URL; export const API = axios.create({ - baseURL: BASE_URL, + baseURL: `${BASE_URL}api`, + timeout: TIMEOUT_IN_SECONDS, +}); + +export const METADATA_VALIDATION_API = axios.create({ + baseURL: `${BASE_URL}metadata-validation`, timeout: TIMEOUT_IN_SECONDS, }); diff --git a/govtool/frontend/src/services/requests/index.ts b/govtool/frontend/src/services/requests/index.ts index 747289515..3121e4c5c 100644 --- a/govtool/frontend/src/services/requests/index.ts +++ b/govtool/frontend/src/services/requests/index.ts @@ -17,3 +17,4 @@ export * from "./postDRepRegister"; export * from "./postDRepRemoveVote"; export * from "./postDRepRetire"; export * from "./postDRepVote"; +export * from "./metadataValidation"; diff --git a/govtool/frontend/src/services/requests/metadataValidation/index.ts b/govtool/frontend/src/services/requests/metadataValidation/index.ts new file mode 100644 index 000000000..3eb5cea95 --- /dev/null +++ b/govtool/frontend/src/services/requests/metadataValidation/index.ts @@ -0,0 +1 @@ +export * from "./postValidate"; diff --git a/govtool/frontend/src/services/requests/metadataValidation/postValidate.ts b/govtool/frontend/src/services/requests/metadataValidation/postValidate.ts new file mode 100644 index 000000000..4dd95281b --- /dev/null +++ b/govtool/frontend/src/services/requests/metadataValidation/postValidate.ts @@ -0,0 +1,11 @@ +import type { MetadataValidationDTO, ValidateMetadataResult } from "@models"; +import { METADATA_VALIDATION_API } from "../../API"; + +export const postValidate = async (body: MetadataValidationDTO) => { + const response = await METADATA_VALIDATION_API.post( + `/validate`, + body, + ); + + return response.data; +}; diff --git a/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts b/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts deleted file mode 100644 index b6bd163c7..000000000 --- a/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { vi } from "vitest"; -import { API } from "@/services"; -import { validateMetadataHash } from ".."; - -describe("validateMetadataHash", () => { - const mockHash = "abcdef1234567890"; - - it("should throw an error for invalid URL", async () => { - const invalidURL = "invalid-url"; - await expect(validateMetadataHash(invalidURL, mockHash)).rejects.toThrow( - "Invalid URL", - ); - }); - - it("should throw an error for invalid JSON", async () => { - const invalidJSONURL = "https://example.com/invalid-json"; - vi.spyOn(API, "get").mockRejectedValueOnce(new Error("Invalid JSON")); - await expect( - validateMetadataHash(invalidJSONURL, mockHash), - ).rejects.toThrow("Invalid JSON"); - }); - - // TODO: Provide tests for invalid hash -}); diff --git a/govtool/frontend/src/utils/validateMetadataHash.ts b/govtool/frontend/src/utils/validateMetadataHash.ts index 4c9f640eb..af8ece41d 100644 --- a/govtool/frontend/src/utils/validateMetadataHash.ts +++ b/govtool/frontend/src/utils/validateMetadataHash.ts @@ -1,11 +1,7 @@ import * as blake from "blakejs"; -import { isAxiosError } from "axios"; import { API } from "@services"; -import { - sharedGovernanceActionFields, - MetadataHashValidationErrors, -} from "@consts"; +import { sharedGovernanceActionFields } from "@consts"; import { URL_REGEX, areObjectsTheSame, canonizeJSON } from "."; @@ -15,54 +11,6 @@ export enum GAMetedataErrors { INCORRECT_FORMAT = "Data Formatted Incorrectly", } -/** - * Validates the metadata hash by fetching the metadata from the given URL, - * canonizing it, and comparing the hash with the provided hash. - * - * @param storingURL - The URL where the metadata is stored. - * @param hash - The hash to compare with the calculated hash of the metadata. - * @returns A promise that resolves to `true` if the metadata hash is valid, - * or rejects with an error message if validation fails. - */ -export const validateMetadataHash = async ( - storingURL: string, - hash: string, -) => { - try { - if (!storingURL.match(URL_REGEX)) { - throw new Error(MetadataHashValidationErrors.INVALID_URL); - } - - const { data: userMetadataJSON } = await API.get(storingURL); - - let canonizedUserMetadata; - try { - canonizedUserMetadata = await canonizeJSON(userMetadataJSON); - } catch (error) { - throw new Error(MetadataHashValidationErrors.INVALID_JSON); - } - if (!canonizedUserMetadata) { - throw new Error(MetadataHashValidationErrors.INVALID_JSON); - } - - const hashedUserMetadata = blake.blake2bHex( - canonizedUserMetadata, - undefined, - 32, - ); - - if (hashedUserMetadata !== hash) { - throw new Error(MetadataHashValidationErrors.INVALID_HASH); - } - return true; - } catch (error) { - if (isAxiosError(error)) { - throw new Error(MetadataHashValidationErrors.FETCH_ERROR); - } - throw error; - } -}; - export const checkIsMissingGAMetadata = async ({ url, hash, diff --git a/govtool/metadata-validation/src/app.controller.ts b/govtool/metadata-validation/src/app.controller.ts index 0bcf57fe9..6b248a789 100644 --- a/govtool/metadata-validation/src/app.controller.ts +++ b/govtool/metadata-validation/src/app.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Body } from '@nestjs/common'; +import { Controller, Body, Post } from '@nestjs/common'; import { AppService } from './app.service'; import { ValidateMetadataDTO } from './dto/validateMetadata.dto'; import { ValidateMetadataResult } from './types/validateMetadata'; @@ -7,7 +7,7 @@ import { ValidateMetadataResult } from './types/validateMetadata'; export class AppController { constructor(private readonly appService: AppService) {} - @Get('validate') + @Post('validate') validateMetadata( @Body() validateMetadataDto: ValidateMetadataDTO, ): Promise { diff --git a/scripts/govtool/config/templates/docker-compose.yml.tpl b/scripts/govtool/config/templates/docker-compose.yml.tpl index 4cf11a525..cc05931c3 100644 --- a/scripts/govtool/config/templates/docker-compose.yml.tpl +++ b/scripts/govtool/config/templates/docker-compose.yml.tpl @@ -246,7 +246,7 @@ services: - "traefik.http.routers.frontend.rule=Host(``)" - "traefik.http.routers.frontend.entrypoints=websecure" - "traefik.http.routers.frontend.tls.certresolver=myresolver" - - "traefik.http.middlewares.frontend-csp.headers.contentSecurityPolicy=default-src 'self'; img-src *.usersnap.com 'self' data:; script-src *.usersnap.com 'self' 'unsafe-inline' https://www.googletagmanager.com https://browser.sentry-cdn.com; style-src *.usersnap.com *.googleapis.com 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src *.usersnap.com https://s3.eu-central-1.amazonaws.com/upload.usersnap.com 'self' o4506155985141760.ingest.sentry.io *.google-analytics.com *.githubusercontent.com *.ipfs.io; font-src *.usersnap.com *.gstatic.com 'self' 'unsafe-inline' https://fonts.gstatic.com; worker-src blob:" + - "traefik.http.middlewares.frontend-csp.headers.contentSecurityPolicy=default-src 'self'; img-src *.usersnap.com 'self' data:; script-src *.usersnap.com 'self' 'unsafe-inline' https://www.googletagmanager.com https://browser.sentry-cdn.com; style-src *.usersnap.com *.googleapis.com 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src *.usersnap.com https://s3.eu-central-1.amazonaws.com/upload.usersnap.com 'self' o4506155985141760.ingest.sentry.io *.google-analytics.com; font-src *.usersnap.com *.gstatic.com 'self' 'unsafe-inline' https://fonts.gstatic.com; worker-src blob:" - "traefik.http.routers.frontend.middlewares=frontend-csp@docker" - "traefik.http.services.frontend.loadbalancer.server.port=80" From fe2146f45989edceaf5e0d3a5ef3d7247a0d9efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 8 Apr 2024 10:09:54 +0200 Subject: [PATCH 022/143] [#641] fix sole voter card --- .../components/organisms/DashboardCards.tsx | 1 - .../DashboardCards/SoleVoterDashboardCard.tsx | 26 +++---------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx index 300e7502c..dcb2d48cc 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx @@ -72,7 +72,6 @@ export const DashboardCards = () => { /> navigate("/"); - // learn more button const learnMoreButton: LoadingButtonProps = { children: t("learnMore"), @@ -140,15 +131,6 @@ export const SoleVoterDashboardCard = ({ pendingTransaction?.registerAsSoleVoter?.transactionHash || pendingTransaction?.retireAsSoleVoter?.transactionHash } - > - {(pendingTransaction?.registerAsSoleVoter || - voter.isRegisteredAsSoleVoter) && ( - - )} - + > ); }; From 8426b3d90da33c23fd0587f4cfc801af237a632e Mon Sep 17 00:00:00 2001 From: jankun4 Date: Sun, 7 Apr 2024 19:29:47 +0200 Subject: [PATCH 023/143] [#627] add latestTxHash to drep/list and drep/info endpoints --- CHANGELOG.md | 2 +- govtool/backend/sql/get-drep-info.sql | 5 ++++- govtool/backend/sql/list-dreps.sql | 9 ++++++--- govtool/backend/src/VVA/API.hs | 4 +++- govtool/backend/src/VVA/API/Types.hs | 22 +++++++++++++--------- govtool/backend/src/VVA/DRep.hs | 8 +++++--- govtool/backend/src/VVA/Types.hs | 18 ++++++++++-------- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 961928ae3..88c975b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ changes. - Integrate frontend with metadata validation service [Issue 617](https://github.com/IntersectMBO/govtool/issues/617) ### Added - +- addded latestTxHash to the `drep/info` and `drep/list` endpoints [Issue 627](https://github.com/IntersectMBO/govtool/issues/627) - added `epochNo` and `date` to `drep/getVotes` and `proposal/get` - Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151) diff --git a/govtool/backend/sql/get-drep-info.sql b/govtool/backend/sql/get-drep-info.sql index cb50e0642..a7b4536a5 100644 --- a/govtool/backend/sql/get-drep-info.sql +++ b/govtool/backend/sql/get-drep-info.sql @@ -6,10 +6,12 @@ LatestRegistrationEntry AS ( SELECT drep_registration.voting_anchor_id AS voting_anchor_id, deposit AS deposit + tx.hash as tx_hash FROM drep_registration CROSS JOIN DrepId JOIN drep_hash ON drep_hash.id = drep_registration.drep_hash_id + JOIN tx ON tx.id = drep_registration.tx_id WHERE drep_hash.raw = DRepId.raw ORDER BY @@ -95,7 +97,8 @@ SELECT CurrentDeposit.value, CurrentMetadata.url, CurrentMetadata.data_hash, - CurrentVotingPower.amount + CurrentVotingPower.amount, + LatestRegistrationEntry.tx_hash FROM IsRegisteredAsDRep CROSS JOIN IsRegisteredAsSoleVoter diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 931ae6430..bb2f8fd08 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -23,7 +23,8 @@ SELECT dr_deposit.deposit, DRepDistr.amount, (DRepActivity.epoch_no - Max(coalesce(block.epoch_no,block_first_register.epoch_no))) <= DRepActivity.drep_activity as active, - second_to_newest_drep_registration.voting_anchor_id is not null as has_voting_anchor + second_to_newest_drep_registration.voting_anchor_id is not null as has_voting_anchor, + encode(dr_voting_anchor.tx_hash, 'hex') as tx_hash FROM drep_hash dh JOIN ( SELECT dr.id, dr.drep_hash_id, dr.deposit, @@ -34,8 +35,10 @@ JOIN ( on dr_deposit.drep_hash_id = dh.id and dr_deposit.rn = 1 LEFT JOIN ( SELECT dr.id, dr.drep_hash_id, dr.voting_anchor_id, - ROW_NUMBER() OVER(PARTITION BY dr.drep_hash_id ORDER BY dr.tx_id DESC) AS rn + ROW_NUMBER() OVER(PARTITION BY dr.drep_hash_id ORDER BY dr.tx_id DESC) AS rn, + tx.hash as tx_hash FROM drep_registration dr + JOIN tx on tx.id = dr.tx_id ) as dr_voting_anchor on dr_voting_anchor.drep_hash_id = dh.id and dr_voting_anchor.rn = 1 LEFT JOIN ( @@ -65,4 +68,4 @@ on tx_first_register.id = dr_first_register.tx_id JOIN block as block_first_register ON block_first_register.id = tx_first_register.block_id -GROUP BY dh.raw, second_to_newest_drep_registration.voting_anchor_id, dh.view, va.url, va.data_hash, dr_deposit.deposit, DRepDistr.amount, DRepActivity.epoch_no, DRepActivity.drep_activity +GROUP BY dh.raw, second_to_newest_drep_registration.voting_anchor_id, dh.view, va.url, va.data_hash, dr_deposit.deposit, DRepDistr.amount, DRepActivity.epoch_no, DRepActivity.drep_activity, dr_voting_anchor.tx_hash diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 293e0c2bd..08b067dd2 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -97,7 +97,8 @@ drepRegistrationToDrep Types.DRepRegistration {..} = dRepDeposit = dRepRegistrationDeposit, dRepVotingPower = dRepRegistrationVotingPower, dRepStatus = mapDRepStatus dRepRegistrationStatus, - dRepType = mapDRepType dRepRegistrationType + dRepType = mapDRepType dRepRegistrationType, + dRepLatestTxHash = HexText <$> dRepRegistrationLatestTxHash } drepList :: App m => Maybe Text -> m [DRep] @@ -208,6 +209,7 @@ drepInfo (unHexText -> dRepId) = do , dRepInfoResponseUrl = dRepInfoUrl , dRepInfoResponseDataHash = HexText <$> dRepInfoDataHash , dRepInfoResponseVotingPower = dRepInfoVotingPower + , dRepInfoResponseLatestTxHash = HexText <$> dRepInfoLatestTxHash } getCurrentDelegation :: App m => HexText -> m (Maybe HexText) diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index a591305b6..8c1cb49d4 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -441,6 +441,7 @@ data DRepInfoResponse , dRepInfoResponseUrl :: Maybe Text , dRepInfoResponseDataHash :: Maybe HexText , dRepInfoResponseVotingPower :: Maybe Integer + , dRepInfoResponseLatestTxHash :: Maybe HexText } deriving (Generic, Show) @@ -455,7 +456,8 @@ exampleDRepInfoResponse = <> "\"deposit\": 2000000," <> "\"url\": \"https://drep.metadata.xyz\"," <> "\"dataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," - <> "\"votingPower\": 1000000}" + <> "\"votingPower\": 1000000," + <> "\"latestTxHash\": \"47c14a128cd024f1b990c839d67720825921ad87ed875def42641ddd2169b39c\"}" instance ToSchema DRepInfoResponse where declareNamedSchema proxy = do @@ -632,6 +634,7 @@ data DRep , dRepVotingPower :: Maybe Integer , dRepStatus :: DRepStatus , dRepType :: DRepType + , dRepLatestTxHash :: Maybe HexText } deriving (Generic, Show) @@ -640,14 +643,15 @@ deriveJSON (jsonOptions "dRep") ''DRep exampleDrep :: Text exampleDrep = - "{\"drepId\": \"d3a62ffe9c214e1a6a9809f7ab2a104c117f85e1f171f8f839d94be5\"," - <> "\"view\": \"drep1l8uyy66sm8u82h82gc8hkcy2xu24dl8ffsh58aa0v7d37yp48u8\"," - <> "\"url\": \"https://proposal.metadata.xyz\"," - <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," - <> "\"deposit\": 0," - <> "\"votingPower\": 0," - <> "\"status\": \"Active\"," - <> "\"type\": \"DRep\"}" + "{\"drepId\": \"d3a62ffe9c214e1a6a9809f7ab2a104c117f85e1f171f8f839d94be5\"," + <> "\"view\": \"drep1l8uyy66sm8u82h82gc8hkcy2xu24dl8ffsh58aa0v7d37yp48u8\"," + <> "\"url\": \"https://proposal.metadata.xyz\"," + <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," + <> "\"deposit\": 0," + <> "\"votingPower\": 0," + <> "\"status\": \"Active\"," + <> "\"type\": \"DRep\"," + <> "\"latestTxHash\": \"47c14a128cd024f1b990c839d67720825921ad87ed875def42641ddd2169b39c\"}" -- ToSchema instance for DRep instance ToSchema DRep where diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index eede66a81..9bc5c9e03 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -60,8 +60,8 @@ listDReps :: listDReps = withPool $ \conn -> do results <- liftIO $ SQL.query_ conn listDRepsSql return - [ DRepRegistration drepHash drepView url dataHash (floor @Scientific deposit) votingPower status drepType - | (drepHash, drepView, url, dataHash, deposit, votingPower, isActive, wasDRep) <- results + [ DRepRegistration drepHash drepView url dataHash (floor @Scientific deposit) votingPower status drepType txHash + | (drepHash, drepView, url, dataHash, deposit, votingPower, isActive, wasDRep, txHash) <- results , let status = case (isActive, deposit) of (_, d) | d < 0 -> Retired (isActive, d) | d >= 0 && isActive -> Active @@ -117,6 +117,7 @@ getDRepInfo drepId = withPool $ \conn -> do , url , dataHash , votingPower + , txHash )] -> return $ DRepInfo { dRepInfoIsRegisteredAsDRep = fromMaybe False isRegisteredAsDRep @@ -127,5 +128,6 @@ getDRepInfo drepId = withPool $ \conn -> do , dRepInfoUrl = url , dRepInfoDataHash = dataHash , dRepInfoVotingPower = votingPower + , dRepInfoLatestTxHash = Just txHash } - [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing + [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing Nothing diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 15ac3595f..cc2f74f17 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -74,6 +74,7 @@ data DRepInfo , dRepInfoUrl :: Maybe Text , dRepInfoDataHash :: Maybe Text , dRepInfoVotingPower :: Maybe Integer + , dRepInfoLatestTxHash :: Maybe Text } data DRepStatus = Retired | Active | Inactive @@ -82,14 +83,15 @@ data DRepType = DRep | SoleVoter data DRepRegistration = DRepRegistration - { dRepRegistrationDRepHash :: Text - , dRepRegistrationView :: Text - , dRepRegistrationUrl :: Maybe Text - , dRepRegistrationDataHash :: Maybe Text - , dRepRegistrationDeposit :: Integer - , dRepRegistrationVotingPower :: Maybe Integer - , dRepRegistrationStatus :: DRepStatus - , dRepRegistrationType :: DRepType + { dRepRegistrationDRepHash :: Text + , dRepRegistrationView :: Text + , dRepRegistrationUrl :: Maybe Text + , dRepRegistrationDataHash :: Maybe Text + , dRepRegistrationDeposit :: Integer + , dRepRegistrationVotingPower :: Maybe Integer + , dRepRegistrationStatus :: DRepStatus + , dRepRegistrationType :: DRepType + , dRepRegistrationLatestTxHash :: Maybe Text } data Proposal From e813b7bf91c4019db1ff261d56d7776991a4b306 Mon Sep 17 00:00:00 2001 From: jankun4 Date: Sun, 7 Apr 2024 19:46:56 +0200 Subject: [PATCH 024/143] [#626] add to endpoint --- CHANGELOG.md | 2 ++ govtool/backend/sql/get-votes.sql | 11 ++++++----- govtool/backend/src/VVA/API.hs | 3 ++- govtool/backend/src/VVA/API/Types.hs | 4 +++- govtool/backend/src/VVA/DRep.hs | 6 +++--- govtool/backend/src/VVA/Types.hs | 1 + 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c975b44..38437d6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ changes. - Integrate frontend with metadata validation service [Issue 617](https://github.com/IntersectMBO/govtool/issues/617) ### Added + - addded latestTxHash to the `drep/info` and `drep/list` endpoints [Issue 627](https://github.com/IntersectMBO/govtool/issues/627) +- added `txHash` to `drep/getVotes` [Issue 626](https://github.com/IntersectMBO/govtool/issues/626) - added `epochNo` and `date` to `drep/getVotes` and `proposal/get` - Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151) diff --git a/govtool/backend/sql/get-votes.sql b/govtool/backend/sql/get-votes.sql index 18ff422fd..1e9a49cfe 100644 --- a/govtool/backend/sql/get-votes.sql +++ b/govtool/backend/sql/get-votes.sql @@ -1,14 +1,15 @@ -select DISTINCT ON (voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter) voting_procedure.gov_action_proposal_id, concat(encode(tx.hash,'hex'),'#',gov_action_proposal.index), encode(drep_hash.raw, 'hex'), voting_procedure.vote::text, voting_anchor.url, encode(voting_anchor.data_hash, 'hex'), block.epoch_no as epoch_no, block.time as time -from voting_procedure +select DISTINCT ON (voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter) voting_procedure.gov_action_proposal_id, concat(encode(gov_action_tx.hash,'hex'),'#',gov_action_proposal.index), encode(drep_hash.raw, 'hex'), voting_procedure.vote::text, voting_anchor.url, encode(voting_anchor.data_hash, 'hex'), block.epoch_no as epoch_no, block.time as time, encode(vote_tx.hash, 'hex') as vote_tx_hash join gov_action_proposal on gov_action_proposal.id = voting_procedure.gov_action_proposal_id join drep_hash on drep_hash.id = voting_procedure.drep_voter left join voting_anchor on voting_anchor.id = voting_procedure.voting_anchor_id -join tx -on tx.id = gov_action_proposal.tx_id +join tx as gov_action_tx +on gov_action_tx.id = gov_action_proposal.tx_id +join tx as vote_tx +on vote_tx.id = voting_procedure.tx_id join block -on block.id = tx.block_id +on block.id = gov_action_tx.block_id where drep_hash.raw = decode(?, 'hex') order by voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter, voting_procedure.id desc diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 08b067dd2..e5ef660a0 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -152,7 +152,8 @@ voteToResponse Types.Vote {..} = voteParamsUrl = voteUrl, voteParamsMetadataHash = HexText <$> voteDocHash, voteParamsEpochNo = voteEpochNo, - voteParamsDate = voteDate + voteParamsDate = voteDate, + voteParamsTxHash = HexText voteTxHash } diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 8c1cb49d4..d1e2024ca 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -372,6 +372,7 @@ data VoteParams , voteParamsMetadataHash :: Maybe HexText , voteParamsEpochNo :: Integer , voteParamsDate :: UTCTime + , voteParamsTxHash :: HexText } deriving (Generic, Show) @@ -385,7 +386,8 @@ exampleVoteParams = <> "\"url\": \"https://vote.metadata.xyz\"," <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," <> "\"epochNo\": 0," - <> "\"date\": \"1970-01-01T00:00:00Z\"}" + <> "\"date\": \"1970-01-01T00:00:00Z\"," + <> "\"txHash\": \"47c14a128cd024f1b990c839d67720825921ad87ed875def42641ddd2169b39c\"}" instance ToSchema VoteParams where declareNamedSchema proxy = do diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 9bc5c9e03..dd0422c4c 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -82,14 +82,14 @@ getVotes :: getVotes drepId selectedProposals = withPool $ \conn -> do results <- liftIO $ SQL.query conn getVotesSql (SQL.Only drepId) let proposalsToSelect = if null selectedProposals - then [ govActionId | (_, govActionId, _, _, _, _, _, _) <- results] + then [ govActionId | (_, govActionId, _, _, _, _, _, _, _) <- results] else selectedProposals proposals <- Proposal.getProposals (Just proposalsToSelect) let proposalMap = M.fromList $ map (\x -> (proposalId x, x)) proposals timeZone <- liftIO getCurrentTimeZone return - ([ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') - | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date') <- results + ([ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash' + | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date', voteTxHash') <- results , govActionId' `elem` proposalsToSelect ], proposals) diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index cc2f74f17..b913270d3 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -62,6 +62,7 @@ data Vote , voteDocHash :: Maybe Text , voteEpochNo :: Integer , voteDate :: UTCTime + , voteTxHash :: Text } data DRepInfo From 7afb1d72e995c29c6905f53e0ba306a1df871f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 8 Apr 2024 16:18:18 +0200 Subject: [PATCH 025/143] [#641] fix lint --- .../organisms/DashboardCards/SoleVoterDashboardCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx index e81784a3b..5656d9c5d 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx @@ -131,6 +131,6 @@ export const SoleVoterDashboardCard = ({ pendingTransaction?.registerAsSoleVoter?.transactionHash || pendingTransaction?.retireAsSoleVoter?.transactionHash } - > + /> ); }; From 77a2a33015656ed619aa058620a2f0d814ef0879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 8 Apr 2024 16:38:05 +0200 Subject: [PATCH 026/143] [#669] update delegation card --- .../molecules/AutomatedVotingCard.tsx | 172 ++++++++++-------- .../molecules/DashboardActionCard.tsx | 26 +-- ...leVoterAction.tsx => DelegationAction.tsx} | 2 +- .../src/components/molecules/index.ts | 6 +- .../src/components/molecules/types.ts | 2 + .../organisms/AutomatedVotingOptions.tsx | 33 +++- .../src/components/organisms/DRepCard.tsx | 37 ++-- .../components/organisms/DashboardCards.tsx | 1 - .../DashboardCards/DRepDashboardCard.tsx | 6 +- .../DashboardCards/DelegateDashboardCard.tsx | 131 +++++++------ .../DashboardCards/SoleVoterDashboardCard.tsx | 22 +-- govtool/frontend/src/i18n/locales/en.ts | 45 ++--- govtool/frontend/src/pages/DRepDetails.tsx | 13 +- .../src/pages/DRepDirectoryContent.tsx | 107 ++++++----- 14 files changed, 318 insertions(+), 285 deletions(-) rename govtool/frontend/src/components/molecules/{SoleVoterAction.tsx => DelegationAction.tsx} (97%) diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx index dd17fa3cf..5a9e0ba3d 100644 --- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -1,26 +1,33 @@ import { Box, Divider } from "@mui/material"; -import { Button, Typography } from "@atoms"; +import { Button, LoadingButton, Typography } from "@atoms"; +import { primaryBlue } from "@consts"; +import { useModal } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; -import { AutomatedVotingCardProps } from "./types"; +import { openInNewTab } from "@utils"; + import { Card } from "./Card"; -import { primaryBlue } from "@/consts"; -import { useModal } from "@/context"; +import { AutomatedVotingCardProps } from "./types"; export const AutomatedVotingCard = ({ description, inProgress, isConnected, + isDelegateLoading, isSelected, onClickDelegate, onClickInfo, title, votingPower, + transactionId, }: AutomatedVotingCardProps) => { const { isMobile, screenWidth } = useScreenDimension(); const { openModal } = useModal(); const { t } = useTranslation(); + const onClickShowTransaction = () => + openInNewTab(`https://sancho.cexplorer.io/tx/${transactionId}`); + return ( {title} - + {description} + {transactionId && ( + + )} - - - - {t("dRepDirectory.votingPower")} - - - {'₳ '} - {votingPower} - - - - - - {!isConnected - ? ( + {!inProgress && !isSelected && ( + <> + + + + {t("dRepDirectory.votingPower")} + + + {"₳ "} + {votingPower} + + + + - ) - : !isSelected && ( - - )} - + {!isConnected ? ( + + ) : ( + !isSelected && ( + + {t("delegate")} + + ) + )} + + + )} ); }; diff --git a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx index feb9f39fb..3f5ac944a 100644 --- a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx +++ b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx @@ -3,8 +3,9 @@ import { FC, ReactNode } from "react"; import { Button, LoadingButton, LoadingButtonProps, Typography } from "@atoms"; import { useScreenDimension, useTranslation } from "@hooks"; +import { openInNewTab } from "@utils"; + import { Card } from "./Card"; -import { openInNewTab } from "@/utils"; export type DashboardActionCardProps = { buttons?: LoadingButtonProps[]; @@ -17,6 +18,7 @@ export type DashboardActionCardProps = { state?: "active" | "inProgress" | "default"; title?: ReactNode; transactionId?: string; + isSpaceBetweenButtons?: boolean; }; export const DashboardActionCard: FC = ({ @@ -33,6 +35,7 @@ export const DashboardActionCard: FC = ({ state = "default", title, transactionId, + isSpaceBetweenButtons, } = props; const { screenWidth } = useScreenDimension(); @@ -103,20 +106,21 @@ export const DashboardActionCard: FC = ({ )} ) : null} + {children} + {transactionId && ( + + )} - {children} - {transactionId && ( - - )} diff --git a/govtool/frontend/src/components/molecules/SoleVoterAction.tsx b/govtool/frontend/src/components/molecules/DelegationAction.tsx similarity index 97% rename from govtool/frontend/src/components/molecules/SoleVoterAction.tsx rename to govtool/frontend/src/components/molecules/DelegationAction.tsx index c58e60485..278e12dcb 100644 --- a/govtool/frontend/src/components/molecules/SoleVoterAction.tsx +++ b/govtool/frontend/src/components/molecules/DelegationAction.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "@hooks"; import { Card } from "./Card"; import { SoleVoterActionProps } from "./types"; -export const SoleVoterAction = ({ +export const DelegationAction = ({ dRepId, onClickArrow, sx, diff --git a/govtool/frontend/src/components/molecules/index.ts b/govtool/frontend/src/components/molecules/index.ts index 2bfb3f8f9..1a33c71f7 100644 --- a/govtool/frontend/src/components/molecules/index.ts +++ b/govtool/frontend/src/components/molecules/index.ts @@ -10,6 +10,7 @@ export * from "./DataActionsBar"; export * from "./DataActionsFilters"; export * from "./DataActionsSorting"; export * from "./DataMissingInfoBox"; +export * from "./DelegationAction"; export * from "./DRepInfoCard"; export * from "./EmptyStateGovernanceActionsCategory"; export * from "./Field"; @@ -27,13 +28,12 @@ export * from "./GovernanceActionsDatesBox"; export * from "./GovernanceVotedOnCard"; export * from "./LinkWithIcon"; export * from "./OrderActionsChip"; +export * from "./PageTitle"; +export * from "./Share"; export * from "./Share"; export * from "./SliderArrow"; export * from "./SliderArrows"; -export * from "./SoleVoterAction"; export * from "./Step"; -export * from "./PageTitle"; -export * from "./Share"; export * from "./VoteActionForm"; export * from "./VotesSubmitted"; export * from "./WalletInfoCard"; diff --git a/govtool/frontend/src/components/molecules/types.ts b/govtool/frontend/src/components/molecules/types.ts index 624129859..9f54e76e2 100644 --- a/govtool/frontend/src/components/molecules/types.ts +++ b/govtool/frontend/src/components/molecules/types.ts @@ -36,4 +36,6 @@ export type AutomatedVotingCardProps = { onClickInfo: () => void; title: string; votingPower: string | number; + isDelegateLoading?: boolean; + transactionId?: string; }; diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index f0482abab..1ba7a6a1b 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { Accordion, AccordionDetails, @@ -8,16 +9,18 @@ import { import { Typography } from "@atoms"; import { ICONS } from "@consts"; +import { PendingTransaction } from "@context"; import { useTranslation } from "@hooks"; import { AutomatedVotingCard } from "@molecules"; -import { useState } from "react"; type AutomatedVotingOptionsProps = { currentDelegation: string | undefined; delegate: (delegateTo: string) => void; + votingPower: string; delegationInProgress?: string; isConnected?: boolean; - votingPower: string; + isDelegationLoading?: boolean; + pendingTransaction?: PendingTransaction; }; export const AutomatedVotingOptions = ({ @@ -25,6 +28,8 @@ export const AutomatedVotingOptions = ({ delegate, delegationInProgress, isConnected, + isDelegationLoading, + pendingTransaction, votingPower, }: AutomatedVotingOptionsProps) => { const { t } = useTranslation(); @@ -50,12 +55,16 @@ export const AutomatedVotingOptions = ({ // TODO this Chip is temporary, since there were no design for this case theme.palette.neutralWhite, fontWeight: 400, ml: 2, - textTransform: 'uppercase', + textTransform: "uppercase", }} /> )} @@ -72,21 +81,33 @@ export const AutomatedVotingOptions = ({ description={t("dRepDirectory.abstainCardDescription")} inProgress={delegationInProgress === "abstain"} isConnected={isConnected} + isDelegateLoading={isDelegationLoading} isSelected={currentDelegation === "drep_always_abstain"} onClickDelegate={() => delegate("abstain")} - onClickInfo={() => { }} + onClickInfo={() => {}} title={t("dRepDirectory.abstainCardTitle")} votingPower={votingPower} + transactionId={ + pendingTransaction?.delegate?.resourceId === "abstain" + ? pendingTransaction?.delegate?.transactionHash + : undefined + } /> delegate("no confidence")} - onClickInfo={() => { }} + onClickInfo={() => {}} title={t("dRepDirectory.noConfidenceTitle")} votingPower={votingPower} + transactionId={ + pendingTransaction?.delegate?.resourceId === "no confidence" + ? pendingTransaction?.delegate?.transactionHash + : undefined + } /> diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index 786969754..aa388a390 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -15,15 +15,10 @@ type DRepCardProps = { isInProgress?: boolean; isMe?: boolean; onDelegate?: () => void; -} +}; export const DRepCard = ({ - dRep: { - status, - type, - view, - votingPower, - }, + dRep: { status, type, view, votingPower }, isConnected, isInProgress, isMe, @@ -36,12 +31,12 @@ export const DRepCard = ({ return ( @@ -95,7 +90,7 @@ export const DRepCard = ({ - + - ₳ - {' '} - {correctAdaFormat(votingPower)} + ₳ {correctAdaFormat(votingPower)} - {status === "Active" && isConnected && onDelegate && ( + {status === "Active" && isConnected && onDelegate && !isMe && ( )} {status === "Active" && !isConnected && ( diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx index 300e7502c..dcb2d48cc 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx @@ -72,7 +72,6 @@ export const DashboardCards = () => { /> + navigate(PATHS.dashboardDRepDirectoryDRep.replace(":dRepId", dRepIDBech32)); + const cardProps: Partial = (() => { // transaction in progress if (inProgress) { @@ -74,8 +77,7 @@ export const DRepDashboardCard = ({ { children: t("dashboard.cards.drep.viewDetails"), dataTestId: "view-drep-details-button", - // TODO: change navigation to drep explorer - onClick: () => navigate("/"), + onClick: navigateToDrepDirectory, variant: "outlined", sx: { backgroundColor: "arcticWhite" }, }, diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx index 6f68f06a9..67838681a 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx @@ -1,12 +1,13 @@ +import { useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; import { IMAGES, PATHS } from "@consts"; import { useTranslation } from "@hooks"; import { - CopyableInfo, DashboardActionCard, DashboardActionCardProps, + DelegationAction, } from "@molecules"; import { correctAdaFormat, formHexToBech32, openInNewTab } from "@utils"; import { PendingTransaction } from "@/context/pendingTransaction"; @@ -27,27 +28,28 @@ export const DelegateDashboardCard = ({ const navigate = useNavigate(); const { t } = useTranslation(); + const learnMoreButton = { + children: t("learnMore"), + dataTestId: "delegate-learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power", + ), + }; + + const onClickDelegateToAnotherDRep = () => + navigate(PATHS.dashboardDRepDirectory); + const ada = correctAdaFormat(votingPower); const cardProps: Partial = (() => { // transaction in progress if (delegateTx) { return { - buttons: [ - { - children: t("seeTransaction"), - dataTestId: "see-transaction-button", - onClick: () => - openInNewTab("https://adanordic.com/latest_transactions"), - }, - ], - description: getProgressDescription( - delegateTx?.resourceId, - dRepID, - ada - ), + buttons: [learnMoreButton], + description: getProgressDescription(delegateTx?.resourceId, ada), state: "inProgress", - title: t("dashboard.delegation.votingPowerDelegation"), + title: t("dashboard.cards.delegation.inProgress.title"), }; } @@ -55,16 +57,20 @@ export const DelegateDashboardCard = ({ if (currentDelegation) { return { buttons: [ + learnMoreButton, { - children: t("dashboard.delegation.changeDelegation"), - dataTestId: "change-dRep-button", - onClick: () => navigate(PATHS.delegateTodRep), + children: t("dashboard.cards.delegation.delegateToAnotherDRep"), + dataTestId: "delegate-to-another-drep-button", + onClick: onClickDelegateToAnotherDRep, }, ], - description: getDelegationDescription(currentDelegation, dRepID, ada), + description: getDelegationDescription(currentDelegation), state: "active", title: ( - + ), }; } @@ -73,86 +79,75 @@ export const DelegateDashboardCard = ({ return { buttons: [ { - children: t("delegate"), + children: t("dashboard.cards.delegation.noDelegationActionButton"), dataTestId: "delegate-button", - onClick: () => navigate(PATHS.delegateTodRep), + onClick: () => navigate(PATHS.dashboardDRepDirectory), variant: "contained", }, - { - children: t("learnMore"), - dataTestId: "delegate-learn-more-button", - onClick: () => - openInNewTab( - "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power" - ), - }, + learnMoreButton, ], - description: ( - - ), - title: t("dashboard.delegation.useYourVotingPower"), + description: t("dashboard.cards.delegation.noDelegationDescription"), + title: t("dashboard.cards.delegation.noDelegationTitle"), }; })(); const displayedDelegationId = getDisplayedDelegationId( currentDelegation, delegateTx?.resourceId, - dRepID + dRepID, + ); + + const navigateToDRepDetails = useCallback( + () => + navigate( + PATHS.dashboardDRepDirectoryDRep.replace( + ":dRepId", + displayedDelegationId || "", + ), + ), + [displayedDelegationId], ); return ( {displayedDelegationId && ( - )} ); }; -const getDelegationDescription = ( - currentDelegation: string, - dRepID: string, - ada: number -) => { +const getDelegationDescription = (currentDelegation: string) => { const key = - currentDelegation === dRepID - ? "dashboard.delegation.toYourself" - : currentDelegation === "drep_always_no_confidence" - ? "dashboard.delegation.voteNo" - : currentDelegation === "drep_always_abstain" - ? "dashboard.delegation.voteAbstain" - : currentDelegation - ? "dashboard.delegation.toDRep" - : undefined; - return ; + currentDelegation === "drep_always_no_confidence" + ? "dashboard.cards.delegation.no" + : currentDelegation === "drep_always_abstain" + ? "dashboard.cards.delegation.abstain" + : undefined; + return ; }; -const getProgressDescription = ( - delegateTo: string, - dRepID: string, - ada: number -) => { +const getProgressDescription = (delegateTo: string, ada: number) => { const key = (() => { if (!delegateTo) return undefined; switch (delegateTo) { - case dRepID: - return "dashboard.delegation.inProgress.toYourself"; case "no confidence": - return "dashboard.delegation.inProgress.voteNo"; + return "dashboard.cards.delegation.inProgress.no"; case "abstain": - return "dashboard.delegation.inProgress.voteAbstain"; + return "dashboard.cards.delegation.inProgress.abstain"; default: - return "dashboard.delegation.inProgress.toDRep"; + return "dashboard.cards.delegation.inProgress.dRep"; } })(); return ; @@ -161,7 +156,7 @@ const getProgressDescription = ( const getDisplayedDelegationId = ( currentDelegation: string, delegateTo: string | undefined, - dRepID: string + dRepID: string, ) => { const restrictedNames = [ dRepID, diff --git a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx index 68be628f1..71d297c41 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx @@ -4,24 +4,18 @@ import { Trans } from "react-i18next"; import { IMAGES, PATHS } from "@consts"; import { PendingTransaction } from "@context"; import { useTranslation } from "@hooks"; -import { - DashboardActionCard, - DashboardActionCardProps, - SoleVoterAction, -} from "@molecules"; +import { DashboardActionCard, DashboardActionCardProps } from "@molecules"; import { correctAdaFormat, openInNewTab } from "@utils"; import { LoadingButtonProps } from "@atoms"; import { VoterInfo } from "@models"; type SoleVoterDashboardCardProps = { - dRepIDBech32: string; pendingTransaction: PendingTransaction; voter: VoterInfo; votingPower: number; }; export const SoleVoterDashboardCard = ({ - dRepIDBech32, pendingTransaction, voter, votingPower, @@ -31,9 +25,6 @@ export const SoleVoterDashboardCard = ({ const ada = correctAdaFormat(votingPower); - // TODO: Add navigation to DRep explorer - const onClickAction = () => navigate("/"); - // learn more button const learnMoreButton: LoadingButtonProps = { children: t("learnMore"), @@ -140,15 +131,6 @@ export const SoleVoterDashboardCard = ({ pendingTransaction?.registerAsSoleVoter?.transactionHash || pendingTransaction?.retireAsSoleVoter?.transactionHash } - > - {(pendingTransaction?.registerAsSoleVoter || - voter.isRegisteredAsSoleVoter) && ( - - )} - + /> ); }; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 2cb228f61..a78855891 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -119,33 +119,22 @@ export const en = { "You cannot vote on Governance Actions using your own voting power of ₳{{votingPower}}. until you re-register.", youAreSoleVoterTitle: "You are a Sole Voter", }, - }, - delegation: { - changeDelegation: "Change delegation", - delegateOwnPower: - "If you want to delegate your own voting power of ₳{{ada}}.", - dRepDelegatedTo: "DRep you delegated to", - toDRep: - "You have delegated your voting power of ₳{{ada}} to a selected DRep.", - toYourself: - "You have delegated your voting power of ₳{{ada}} to yourself.", - useYourVotingPower: "Use your Voting Power", - voteAbstain: - "You have delegated your voting power of ₳{{ada}}. You are going to vote 'ABSTAIN' as default.", - voteNo: - "You have delegated your voting power of ₳{{ada}}. You are going to vote 'NO' as default.", - votingPowerDelegation: "Voting Power Delegation", - yourVotingPowerIsDelegated: - "Your Voting Power is Delegated", - inProgress: { - toDRep: - "Your own voting power of ₳{{ada}} is progress of being delegated. You are going to delegate your voting power to a selected DRep.", - toYourself: - "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to delegate your voting power to yourself.", - voteAbstain: - "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to vote ‘ABSTAIN’ as default.", - voteNo: - "Your own voting power of ₳{{ada}} is in progress of being delegated. You are going to vote ‘NO’ as default.", + delegation: { + noDelegationTitle: "Delegate your Voting Power", + delegateToAnotherDRep: "Delegate to Another DRep", + noDelegationDescription: "Find a DRep to vote on your behalf.", + noDelegationActionButton: "View DRep Directory", + delegationTitle: + "Your Voting Power of ₳{{ada}}\nis Delegated to:", + abstain: "'ABSTAIN' as default.", + no: "'NO' as default.", + inProgress: { + title: "Delegation", + dRep: "Your voting power of ₳{{ada}} is being delegated to:", + abstain: + "Your have deleated ₳{{ada}} into 'Abstain'.", + no: "Your have deleated ₳{{ada}} into 'No'.", + }, }, }, }, @@ -736,7 +725,7 @@ export const en = { goBack: "Go back", here: "here", info: "Info", - inProgress: "In progress", + inProgress: "In Progress", learnMore: "Learn more", linkCopied: "Link copied", loading: "Loading...", diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 690634bcc..1aa8b95ae 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -3,18 +3,16 @@ import { Navigate, useNavigate, useParams } from "react-router-dom"; import { Box, ButtonBase, Chip, CircularProgress } from "@mui/material"; import { Button, LoadingButton, StatusPill, Typography } from "@atoms"; -import { Card, LinkWithIcon, Share } from "@molecules"; import { ICONS, PATHS } from "@consts"; +import { useCardano, useModal } from "@context"; import { useDelegateTodRep, - useGetAdaHolderCurrentDelegationQuery, useGetDRepListQuery, useScreenDimension, useTranslation, } from "@hooks"; -import { correctAdaFormat, openInNewTab } from "@utils"; -import { useCardano, useModal } from "@/context"; -import { isSameDRep } from "@/utils"; +import { Card, LinkWithIcon, Share } from "@molecules"; +import { correctAdaFormat, isSameDRep, openInNewTab } from "@utils"; const LINKS = [ "darlenelonglink1.DRepwebsiteorwhatever.com", @@ -29,7 +27,7 @@ type DRepDetailsProps = { }; export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { - const { dRepID: myDRepId, pendingTransaction, stakeKey } = useCardano(); + const { dRepID: myDRepId, pendingTransaction } = useCardano(); const { t } = useTranslation(); const navigate = useNavigate(); const { openModal } = useModal(); @@ -38,7 +36,6 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { delegate, isDelegating } = useDelegateTodRep(); - const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const { data, isLoading } = useGetDRepListQuery({ drepView: dRepParam }); const dRep = data?.[0]; @@ -50,7 +47,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { view, status, votingPower, type } = dRep; const isMe = isSameDRep(dRep, myDRepId); - const isMyDrep = isSameDRep(dRep, currentDelegation); + const isMyDrep = isSameDRep(dRep, myDRepId); const isMyDrepInProgress = isSameDRep( dRep, pendingTransaction.delegate?.resourceId, diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 08c14283d..74079482a 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,19 +1,20 @@ -import { Box, CircularProgress } from "@mui/material"; import { FC } from "react"; -import { AutomatedVotingOptions, DRepCard } from "@organisms"; -import { Typography } from "@atoms"; import { Trans, useTranslation } from "react-i18next"; -import { Card, DataActionsBar } from "@molecules"; -import { useCardano } from "@/context"; +import { Box, CircularProgress } from "@mui/material"; + +import { Typography } from "@atoms"; +import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@consts"; +import { useCardano } from "@context"; import { useDataActionsBar, useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, useGetAdaHolderVotingPowerQuery, - useGetDRepListQuery -} from "@/hooks"; -import { correctAdaFormat, formHexToBech32, isSameDRep } from "@/utils"; -import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@/consts"; + useGetDRepListQuery, +} from "@hooks"; +import { Card, DataActionsBar } from "@molecules"; +import { AutomatedVotingOptions, DRepCard } from "@organisms"; +import { correctAdaFormat, formHexToBech32, isSameDRep } from "@utils"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -22,38 +23,39 @@ interface DRepDirectoryContentProps { export const DRepDirectoryContent: FC = ({ isConnected, }) => { - const { - dRepID: myDRepId, - pendingTransaction, - stakeKey, - } = useCardano(); + const { dRepID: myDRepId, pendingTransaction, stakeKey } = useCardano(); const { t } = useTranslation(); const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); const { chosenFilters, chosenSorting } = dataActionsBarProps; - const { delegate } = useDelegateTodRep(); + const { delegate, isDelegating } = useDelegateTodRep(); const { votingPower } = useGetAdaHolderVotingPowerQuery(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const inProgressDelegation = pendingTransaction.delegate?.resourceId; const { data: myDRepList } = useGetDRepListQuery( - { drepView: currentDelegation?.startsWith('drep') - ? currentDelegation - : formHexToBech32(currentDelegation) }, - { enabled: !!inProgressDelegation || !!currentDelegation } + { + drepView: currentDelegation?.startsWith("drep") + ? currentDelegation + : formHexToBech32(currentDelegation), + }, + { enabled: !!inProgressDelegation || !!currentDelegation }, ); const myDrep = myDRepList?.[0]; - const { data: dRepList, isPreviousData } = useGetDRepListQuery({ - drepView: debouncedSearchText, - sort: chosenSorting, - status: chosenFilters, - }, { - keepPreviousData: true, - }); + const { data: dRepList, isPreviousData } = useGetDRepListQuery( + { + drepView: debouncedSearchText, + sort: chosenSorting, + status: chosenFilters, + }, + { + keepPreviousData: true, + }, + ); if (stakeKey && votingPower === undefined) { - return ; + return ; } const ada = correctAdaFormat(votingPower); @@ -81,19 +83,25 @@ export const DRepDirectoryContent: FC = ({ {t("dRepDirectory.delegationOptions")}
)} @@ -101,7 +109,7 @@ export const DRepDirectoryContent: FC = ({ {/* DRep list */}
- {t('dRepDirectory.listTitle')} + {t("dRepDirectory.listTitle")} = ({ gap={3} mt={4} p={0} - sx={{ opacity: isPreviousData ? 0.5 : 1, transition: 'opacity 0.2s' }} + sx={{ opacity: isPreviousData ? 0.5 : 1, transition: "opacity 0.2s" }} > {dRepList?.length === 0 && ( - {t('dRepDirectory.noResultsForTheSearchTitle')} - {t('dRepDirectory.noResultsForTheSearchDescription')} + + {t("dRepDirectory.noResultsForTheSearchTitle")} + + + {t("dRepDirectory.noResultsForTheSearchDescription")} + )} - {dRepList?.map((dRep) => - (isSameDRep(dRep, myDrep?.view) ? null : ( - + {dRepList?.map((dRep) => { + if (isSameDRep(dRep, myDrep?.view)) { + return null; + } + return ( + = ({ onDelegate={() => delegate(dRep.drepId)} /> - )), - )} + ); + })}
From 44cafba20d4b7a968d7880ec285d559d2f83d71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 8 Apr 2024 16:43:57 +0200 Subject: [PATCH 027/143] fix drep card --- govtool/frontend/src/components/organisms/DRepCard.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index aa388a390..fdaf6701c 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -145,9 +145,13 @@ export const DRepCard = ({ > {t("viewDetails")} - {status === "Active" && isConnected && onDelegate && !isMe && ( - - )} + {status === "Active" && + isConnected && + onDelegate && + !isMe && + !isInProgress && ( + + )} {status === "Active" && !isConnected && ( )} From eff5118074639470f802379eff2a52f085d235f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Mon, 8 Apr 2024 19:16:58 +0200 Subject: [PATCH 028/143] [#646] feat: implement a loading modal for the metadata validation --- CHANGELOG.md | 1 + govtool/frontend/public/icons/Loader.svg | 22 ++++++ .../src/components/atoms/Loader/Loader.css | 8 +++ .../src/components/atoms/Loader/Loader.tsx | 15 +++++ .../src/components/atoms/Loader/index.ts | 1 + .../frontend/src/components/atoms/index.ts | 1 + .../StorageInformation.tsx | 19 +++++- .../{ => Modal}/ChooseWalletModal.tsx | 0 .../{ => Modal}/ExternalLinkModal.tsx | 0 .../organisms/Modal/LoadingModal.tsx | 36 ++++++++++ .../organisms/{ => Modal}/StatusModal.tsx | 4 +- .../{ => Modal}/VotingPowerModal.tsx | 0 .../src/components/organisms/Modal/index.ts | 5 ++ .../src/components/organisms/index.ts | 5 +- govtool/frontend/src/consts/icons.ts | 1 + govtool/frontend/src/context/modal.tsx | 5 ++ govtool/frontend/src/i18n/locales/en.ts | 5 ++ .../stories/modals/LoadingModal.stories.tsx | 67 +++++++++++++++++++ govtool/frontend/yarn.lock | 45 ++++++++++--- 19 files changed, 223 insertions(+), 17 deletions(-) create mode 100644 govtool/frontend/public/icons/Loader.svg create mode 100644 govtool/frontend/src/components/atoms/Loader/Loader.css create mode 100644 govtool/frontend/src/components/atoms/Loader/Loader.tsx create mode 100644 govtool/frontend/src/components/atoms/Loader/index.ts rename govtool/frontend/src/components/organisms/{ => Modal}/ChooseWalletModal.tsx (100%) rename govtool/frontend/src/components/organisms/{ => Modal}/ExternalLinkModal.tsx (100%) create mode 100644 govtool/frontend/src/components/organisms/Modal/LoadingModal.tsx rename govtool/frontend/src/components/organisms/{ => Modal}/StatusModal.tsx (96%) rename govtool/frontend/src/components/organisms/{ => Modal}/VotingPowerModal.tsx (100%) create mode 100644 govtool/frontend/src/components/organisms/Modal/index.ts create mode 100644 govtool/frontend/src/stories/modals/LoadingModal.stories.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 38437d6bb..92b74e9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ changes. ## [Unreleased] - Integrate frontend with metadata validation service [Issue 617](https://github.com/IntersectMBO/govtool/issues/617) +- Implement a loading modal for the validation of the metadata [Issue 646](https://github.com/IntersectMBO/govtool/issues/646) ### Added diff --git a/govtool/frontend/public/icons/Loader.svg b/govtool/frontend/public/icons/Loader.svg new file mode 100644 index 000000000..2f2281a1d --- /dev/null +++ b/govtool/frontend/public/icons/Loader.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/govtool/frontend/src/components/atoms/Loader/Loader.css b/govtool/frontend/src/components/atoms/Loader/Loader.css new file mode 100644 index 000000000..99885ceca --- /dev/null +++ b/govtool/frontend/src/components/atoms/Loader/Loader.css @@ -0,0 +1,8 @@ +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/govtool/frontend/src/components/atoms/Loader/Loader.tsx b/govtool/frontend/src/components/atoms/Loader/Loader.tsx new file mode 100644 index 000000000..3224db678 --- /dev/null +++ b/govtool/frontend/src/components/atoms/Loader/Loader.tsx @@ -0,0 +1,15 @@ +import { ICONS } from "@/consts"; +import "./Loader.css"; + +type Props = { + size: number; +}; +export const Loader = ({ size = 100 }: Props) => ( +
+ loader +
+); diff --git a/govtool/frontend/src/components/atoms/Loader/index.ts b/govtool/frontend/src/components/atoms/Loader/index.ts new file mode 100644 index 000000000..f9f5a2b49 --- /dev/null +++ b/govtool/frontend/src/components/atoms/Loader/index.ts @@ -0,0 +1 @@ +export * from "./Loader"; diff --git a/govtool/frontend/src/components/atoms/index.ts b/govtool/frontend/src/components/atoms/index.ts index ceb4bc8db..4fe856a38 100644 --- a/govtool/frontend/src/components/atoms/index.ts +++ b/govtool/frontend/src/components/atoms/index.ts @@ -29,5 +29,6 @@ export * from "./Tooltip"; export * from "./Typography"; export * from "./VotePill"; export * from "./VotingPowerChips"; +export * from "./Loader"; export * from "./types"; diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx index 015ae75d1..5363ab19e 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx @@ -10,8 +10,9 @@ import { useScreenDimension, } from "@hooks"; import { Step } from "@molecules"; -import { BgCard, ControlledField } from "@organisms"; +import { BgCard, ControlledField, LoadingModalState } from "@organisms"; import { URL_REGEX, openInNewTab } from "@utils"; +import { useModal } from "@context"; type StorageInformationProps = { setStep: Dispatch>; @@ -29,6 +30,7 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { onClickDownloadJson, isLoading, } = useCreateGovernanceActionForm(setStep); + const { openModal, closeModal } = useModal(); const { screenWidth } = useScreenDimension(); const fileName = getValues("governance_action_type"); @@ -45,6 +47,21 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { generateMetadata(); }, []); + useEffect(() => { + if (isLoading) { + openModal({ + type: "loadingModal", + state: { + title: t("createGovernanceAction.modals.loading.title"), + message: t("createGovernanceAction.modals.loading.message"), + dataTestId: "storing-information-loading", + }, + }); + } else { + closeModal(); + } + }, [isLoading]); + return ( { + const { state } = useModal(); + const { isMobile } = useScreenDimension(); + + return ( + + + + {state?.title} + + + + {state?.message}{" "} + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/StatusModal.tsx b/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx similarity index 96% rename from govtool/frontend/src/components/organisms/StatusModal.tsx rename to govtool/frontend/src/components/organisms/Modal/StatusModal.tsx index ce8f2665d..4c0a4416b 100644 --- a/govtool/frontend/src/components/organisms/StatusModal.tsx +++ b/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx @@ -3,8 +3,8 @@ import { Button, Link, Typography } from "@mui/material"; import { ModalContents, ModalHeader, ModalWrapper } from "@atoms"; import { ICONS, IMAGES } from "@consts"; import { useModal } from "@context"; -import { openInNewTab } from "@/utils"; -import { useScreenDimension, useTranslation } from "@/hooks"; +import { openInNewTab } from "@utils"; +import { useScreenDimension, useTranslation } from "@hooks"; export interface StatusModalState { buttonText?: string; diff --git a/govtool/frontend/src/components/organisms/VotingPowerModal.tsx b/govtool/frontend/src/components/organisms/Modal/VotingPowerModal.tsx similarity index 100% rename from govtool/frontend/src/components/organisms/VotingPowerModal.tsx rename to govtool/frontend/src/components/organisms/Modal/VotingPowerModal.tsx diff --git a/govtool/frontend/src/components/organisms/Modal/index.ts b/govtool/frontend/src/components/organisms/Modal/index.ts new file mode 100644 index 000000000..8f342f95d --- /dev/null +++ b/govtool/frontend/src/components/organisms/Modal/index.ts @@ -0,0 +1,5 @@ +export * from "./ChooseWalletModal"; +export * from "./ExternalLinkModal"; +export * from "./LoadingModal"; +export * from "./StatusModal"; +export * from "./VotingPowerModal"; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index 6f12d1e6f..98b15c721 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -1,7 +1,6 @@ export * from "./AutomatedVotingOptions"; export * from "./BgCard"; export * from "./ChooseStakeKeyPanel"; -export * from "./ChooseWalletModal"; export * from "./ControlledField"; export * from "./CreateGovernanceActionSteps"; export * from "./DashboardCards"; @@ -17,7 +16,7 @@ export * from "./Drawer"; export * from "./DrawerMobile"; export * from "./DRepCard"; export * from "./EditDRepInfoSteps"; -export * from "./ExternalLinkModal"; +export * from "./Modal"; export * from "./Footer"; export * from "./GovernanceActionDetailsCard"; export * from "./GovernanceActionDetailsCardData"; @@ -30,7 +29,5 @@ export * from "./RegisterAsSoleVoterBoxContent"; export * from "./RetireAsSoleVoterBox"; export * from "./RetireAsSoleVoterBoxContent"; export * from "./Slider"; -export * from "./StatusModal"; export * from "./TopNav"; export * from "./VoteContext"; -export * from "./VotingPowerModal"; diff --git a/govtool/frontend/src/consts/icons.ts b/govtool/frontend/src/consts/icons.ts index 02c6d5740..dff246b62 100644 --- a/govtool/frontend/src/consts/icons.ts +++ b/govtool/frontend/src/consts/icons.ts @@ -34,4 +34,5 @@ export const ICONS = { sortWhiteIcon: "/icons/SortWhite.svg", timerIcon: "/icons/Timer.svg", warningIcon: "/icons/Warning.svg", + loaderIcon: "/icons/Loader.svg", }; diff --git a/govtool/frontend/src/context/modal.tsx b/govtool/frontend/src/context/modal.tsx index cebfab96b..bedfb2515 100644 --- a/govtool/frontend/src/context/modal.tsx +++ b/govtool/frontend/src/context/modal.tsx @@ -7,6 +7,7 @@ import { StatusModal, VoteContextModal, VotingPowerModal, + LoadingModal, } from "@organisms"; import { basicReducer, callAll, BasicReducer } from "@utils"; @@ -23,6 +24,7 @@ interface ContextModal { export type ModalType = | "none" + | "loadingModal" | "chooseWallet" | "statusModal" | "externalLink" @@ -33,6 +35,9 @@ const modals: Record = { none: { component: null, }, + loadingModal: { + component: , + }, chooseWallet: { component: , }, diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 2cb228f61..29f239569 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -219,6 +219,11 @@ export const en = { "Your Governance Action may take a little time to submit to the chain.", title: "Governance Action submitted!", }, + loading: { + title: "GovTool Is Checking Your Data", + message: + "GovTool will read the URL that you supplied and make a check to see if it’s identical with the information that you entered on the form.", + }, }, }, delegation: { diff --git a/govtool/frontend/src/stories/modals/LoadingModal.stories.tsx b/govtool/frontend/src/stories/modals/LoadingModal.stories.tsx new file mode 100644 index 000000000..6bc59375d --- /dev/null +++ b/govtool/frontend/src/stories/modals/LoadingModal.stories.tsx @@ -0,0 +1,67 @@ +import { useEffect } from "react"; +import { Meta, StoryFn } from "@storybook/react"; + +import { Modal } from "@atoms"; +import { LoadingModal, LoadingModalState } from "@organisms"; +import { callAll } from "@utils"; +import { useModal } from "../../context/modal"; + +const meta = { + title: "Example/Modals/LoadingModal", + component: LoadingModal, +} satisfies Meta; + +export default meta; + +const Template: StoryFn = ({ + message, + title, + dataTestId, +}) => { + const { openModal, modal, modals, closeModal } = useModal(); + + const open = () => { + openModal({ + type: "loadingModal", + state: { + title, + message, + dataTestId, + }, + }); + }; + + useEffect(() => { + open(); + }, [openModal]); + + return ( + <> + + + + {modals[modal.type]?.component && ( + + openModal({ type: "none", state: null }), + )} + > + {modals[modal.type].component!} + + )} + + ); +}; + +export const Loading = Template.bind({}); +Loading.args = { + message: + "GovTool will read the URL that you supplied and make a check to see if it’s identical with the information that you entered on the form.", + title: "GovTool Is Checking Your Data", + dataTestId: "loading-modal", +}; diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock index 861210e72..a19b93219 100644 --- a/govtool/frontend/yarn.lock +++ b/govtool/frontend/yarn.lock @@ -1640,7 +1640,7 @@ "@isaacs/cliui@^8.0.2": version "8.0.2" - resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" @@ -4172,7 +4172,7 @@ ansi-styles@^5.0.0: ansi-styles@^6.1.0: version "6.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== anymatch@^3.0.3, anymatch@~3.1.2: @@ -5504,7 +5504,7 @@ duplexify@^3.5.0, duplexify@^3.6.0: eastasianwidth@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ee-first@1.1.1: @@ -7459,7 +7459,7 @@ iterator.prototype@^1.1.2: jackspeak@^2.3.5: version "2.3.6" - resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== dependencies: "@isaacs/cliui" "^8.0.2" @@ -10263,7 +10263,16 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10274,7 +10283,7 @@ string-length@^5.0.1: string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" @@ -10341,7 +10350,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11260,9 +11276,9 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -11278,9 +11294,18 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: ansi-styles "^6.1.0" From 2b11b133caf200e083433f94afa8f34d902e9bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Tue, 9 Apr 2024 00:14:59 +0200 Subject: [PATCH 029/143] [# 669] new translations --- .../DashboardCards/DelegateDashboardCard.tsx | 54 +++++++++++-------- govtool/frontend/src/i18n/locales/en.ts | 16 ++++-- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx index 67838681a..10b5caf83 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx @@ -35,8 +35,15 @@ export const DelegateDashboardCard = ({ openInNewTab( "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power", ), + sx: { backgroundColor: "arcticWhite" }, }; + const displayedDelegationId = getDisplayedDelegationId( + currentDelegation, + delegateTx?.resourceId, + dRepID, + ); + const onClickDelegateToAnotherDRep = () => navigate(PATHS.dashboardDRepDirectory); @@ -56,22 +63,20 @@ export const DelegateDashboardCard = ({ // current delegation if (currentDelegation) { return { - buttons: [ - learnMoreButton, - { - children: t("dashboard.cards.delegation.delegateToAnotherDRep"), - dataTestId: "delegate-to-another-drep-button", - onClick: onClickDelegateToAnotherDRep, - }, - ], + buttons: displayedDelegationId + ? [ + learnMoreButton, + { + children: t("dashboard.cards.delegation.delegateToAnotherDRep"), + dataTestId: "delegate-to-another-drep-button", + onClick: onClickDelegateToAnotherDRep, + sx: { backgroundColor: "arcticWhite" }, + }, + ] + : [learnMoreButton], description: getDelegationDescription(currentDelegation), state: "active", - title: ( - - ), + title: getDelegationTitle(currentDelegation, ada), }; } @@ -91,12 +96,6 @@ export const DelegateDashboardCard = ({ }; })(); - const displayedDelegationId = getDisplayedDelegationId( - currentDelegation, - delegateTx?.resourceId, - dRepID, - ); - const navigateToDRepDetails = useCallback( () => navigate( @@ -128,12 +127,23 @@ export const DelegateDashboardCard = ({ ); }; +const getDelegationTitle = (currentDelegation: string, ada: number) => { + const key = + currentDelegation === "drep_always_no_confidence" + ? "dashboard.cards.delegation.noConfidenceDelegationTitle" + : currentDelegation === "drep_always_abstain" + ? "dashboard.cards.delegation.abstainDelegationTitle" + : "dashboard.cards.delegation.dRepDelegationTitle"; + + return ; +}; + const getDelegationDescription = (currentDelegation: string) => { const key = currentDelegation === "drep_always_no_confidence" - ? "dashboard.cards.delegation.no" + ? "dashboard.cards.delegation.noDescription" : currentDelegation === "drep_always_abstain" - ? "dashboard.cards.delegation.abstain" + ? "dashboard.cards.delegation.abstainDescription" : undefined; return ; }; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index a78855891..9e33ec033 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -124,16 +124,22 @@ export const en = { delegateToAnotherDRep: "Delegate to Another DRep", noDelegationDescription: "Find a DRep to vote on your behalf.", noDelegationActionButton: "View DRep Directory", - delegationTitle: + dRepDelegationTitle: "Your Voting Power of ₳{{ada}}\nis Delegated to:", - abstain: "'ABSTAIN' as default.", - no: "'NO' as default.", + noConfidenceDelegationTitle: + "You have delegated ₳{{ada}}\nto “No Confidence”", + abstainDelegationTitle: + "You have delegated ₳{{ada}}\nto “Abstain”", + abstainDescription: + "You have selected to apply your Voting Power to Abstain on every vote.", + noDescription: + "You have selected to apply your Voting Power to No Confidence on every vote.", inProgress: { title: "Delegation", dRep: "Your voting power of ₳{{ada}} is being delegated to:", abstain: - "Your have deleated ₳{{ada}} into 'Abstain'.", - no: "Your have deleated ₳{{ada}} into 'No'.", + "You have selected to apply your Voting Power to Abstain on every vote.", + no: "You have selected to apply your Voting Power to No Confidence on every vote.", }, }, }, From e9cdbea34a59b031251a4426d48d77f33b1485ab Mon Sep 17 00:00:00 2001 From: jankun4 Date: Sun, 7 Apr 2024 20:18:43 +0200 Subject: [PATCH 030/143] [#636] add references to all proposal related endpoints --- CHANGELOG.md | 1 + govtool/backend/sql/list-proposals.sql | 1 + govtool/backend/src/VVA/API.hs | 1 + govtool/backend/src/VVA/API/Types.hs | 23 +++++++++++++++++++++++ govtool/backend/src/VVA/Proposal.hs | 2 ++ govtool/backend/src/VVA/Types.hs | 1 + 6 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b74e9d3..2524c51f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ changes. - addded latestTxHash to the `drep/info` and `drep/list` endpoints [Issue 627](https://github.com/IntersectMBO/govtool/issues/627) - added `txHash` to `drep/getVotes` [Issue 626](https://github.com/IntersectMBO/govtool/issues/626) +- added `references` to all proposal related endpoints - added `epochNo` and `date` to `drep/getVotes` and `proposal/get` - Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151) diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index f0bc3cc13..e6a67bd0a 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -60,6 +60,7 @@ SELECT off_chain_vote_data.motivation, off_chain_vote_data.rationale, off_chain_vote_data.json, + off_chain_vote_data.json#>'{body, references}' as references, coalesce(Sum(ldd.amount) FILTER (WHERE voting_procedure.vote::text = 'Yes'), 0) +( CASE WHEN gov_action_proposal.type = 'NoConfidence' THEN always_no_confidence_voting_power.amount diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index e5ef660a0..c10c257b6 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -138,6 +138,7 @@ proposalToResponse Types.Proposal {..} = proposalResponseMotivation = proposalMotivaiton, proposalResponseRationale = proposalRationale, proposalResponseMetadata = GovernanceActionMetadata <$> proposalMetadata, + proposalResponseReferences = GovernanceActionReferences <$> proposalReferences, proposalResponseYesVotes = proposalYesVotes, proposalResponseNoVotes = proposalNoVotes, proposalResponseAbstainVotes = proposalAbstainVotes diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index d1e2024ca..354ae54a4 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -267,6 +267,27 @@ instance ToSchema GovernanceActionMetadata where +newtype GovernanceActionReferences + = GovernanceActionReferences Value + deriving newtype (Show) + +instance FromJSON GovernanceActionReferences where + parseJSON v@(Aeson.Array a) = pure (GovernanceActionReferences v) + parseJSON _ = fail "GovernanceActionReferences has to be an array" + +instance ToJSON GovernanceActionReferences where + toJSON (GovernanceActionReferences g) = g + +instance ToSchema GovernanceActionReferences where + declareNamedSchema _ = pure $ NamedSchema (Just "GovernanceActionReferences") $ mempty + & type_ ?~ OpenApiObject + & description ?~ "A Governance Action References" + & example + ?~ toJSON + ("[{\"uri\": \"google.com\", \"@type\": \"Other\", \"label\": \"example label\"}]" :: Text) + + + data ProposalResponse = ProposalResponse { proposalResponseId :: Text @@ -285,6 +306,7 @@ data ProposalResponse , proposalResponseMotivation :: Maybe Text , proposalResponseRationale :: Maybe Text , proposalResponseMetadata :: Maybe GovernanceActionMetadata + , proposalResponseReferences :: Maybe GovernanceActionReferences , proposalResponseYesVotes :: Integer , proposalResponseNoVotes :: Integer , proposalResponseAbstainVotes :: Integer @@ -310,6 +332,7 @@ exampleProposalResponse = "{ \"id\": \"proposalId123\"," <> "\"motivation\": \"Proposal Motivation\"," <> "\"rationale\": \"Proposal Rationale\"," <> "\"metadata\": {\"key\": \"value\"}," + <> "\"references\": [{\"uri\": \"google.com\", \"@type\": \"Other\", \"label\": \"example label\"}]," <> "\"yesVotes\": 0," <> "\"noVotes\": 0," <> "\"abstainVotes\": 0}" diff --git a/govtool/backend/src/VVA/Proposal.hs b/govtool/backend/src/VVA/Proposal.hs index e2c949992..970ebde78 100644 --- a/govtool/backend/src/VVA/Proposal.hs +++ b/govtool/backend/src/VVA/Proposal.hs @@ -87,6 +87,7 @@ getProposals mProposalIds = withPool $ \conn -> do , motivation' , rationale' , metadataJson' + , references' , yesVotes' , noVotes' , abstainVotes' @@ -111,6 +112,7 @@ getProposals mProposalIds = withPool $ \conn -> do motivation' rationale' metadataJson' + references' (floor @Scientific yesVotes') (floor @Scientific noVotes') (floor @Scientific abstainVotes') diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index b913270d3..91f2952b4 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -113,6 +113,7 @@ data Proposal , proposalMotivaiton :: Maybe Text , proposalRationale :: Maybe Text , proposalMetadata :: Maybe Value + , proposalReferences :: Maybe Value , proposalYesVotes :: Integer , proposalNoVotes :: Integer , proposalAbstainVotes :: Integer From efa2df060ba1ab13520137ec13e0dc80a1860828 Mon Sep 17 00:00:00 2001 From: Jan Jaroszczak Date: Fri, 5 Apr 2024 14:13:46 +0200 Subject: [PATCH 031/143] [#637] Create atoms components ui tests --- govtool/frontend/package.json | 1 + .../src/components/atoms/ActionRadio.test.tsx | 90 ++++++++++++++ .../src/components/atoms/CopyButton.test.tsx | 69 +++++++++++ .../src/components/atoms/DrawerLink.test.tsx | 90 ++++++++++++++ .../components/atoms/LoadingButton.test.tsx | 53 +++++++++ .../src/components/atoms/VotePill.test.tsx | 55 +++++++++ .../atoms/VotingPowerChips.test.tsx | 111 ++++++++++++++++++ 7 files changed, 469 insertions(+) create mode 100644 govtool/frontend/src/components/atoms/ActionRadio.test.tsx create mode 100644 govtool/frontend/src/components/atoms/CopyButton.test.tsx create mode 100644 govtool/frontend/src/components/atoms/DrawerLink.test.tsx create mode 100644 govtool/frontend/src/components/atoms/LoadingButton.test.tsx create mode 100644 govtool/frontend/src/components/atoms/VotePill.test.tsx create mode 100644 govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index b27e98257..70ded1242 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -14,6 +14,7 @@ "preview": "vite preview", "storybook": "storybook dev -p 6006", "test": "vitest", + "test:ui": "vitest --ui", "test-storybook": "test-storybook", "test:watch": "vitest watch", "tsc": "npx tsc --noEmit --skipLibCheck" diff --git a/govtool/frontend/src/components/atoms/ActionRadio.test.tsx b/govtool/frontend/src/components/atoms/ActionRadio.test.tsx new file mode 100644 index 000000000..2d4e1a5dd --- /dev/null +++ b/govtool/frontend/src/components/atoms/ActionRadio.test.tsx @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { ActionRadio } from "@atoms"; + +describe("ActionRadio", () => { + it("should execute onChange with the correct value on click", () => { + const handleChange = vi.fn(); + render( + , + ); + + const radio = screen.getByTestId("action-radio"); + fireEvent.click(radio); + + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith("test-value"); + }); + + it("should change styles based on isChecked change", () => { + const { rerender } = render( + {}} + dataTestId="action-radio" + />, + ); + let radio = screen.getByTestId("action-radio"); + + expect(radio).toHaveStyle("borderColor: white"); + expect(radio).toHaveStyle("backgroundColor: rgb(255, 255, 255)"); + + rerender( + {}} + dataTestId="action-radio" + />, + ); + radio = screen.getByTestId("action-radio"); + + expect(radio).toHaveStyle("borderColor: specialCyanBorder"); + expect(radio).toHaveStyle("backgroundColor: specialCyan"); + }); + + it("should display correct title and optional subtitle", () => { + render( + {}} + dataTestId="action-radio" + />, + ); + + const title = screen.getByText("Main Title"); + const subtitle = screen.getByText("Sub Title"); + + expect(title).toBeInTheDocument(); + expect(subtitle).toBeInTheDocument(); + }); + + it("should display tooltip text when InfoOutlinedIcon is hovered over", async () => { + render( + {}} + dataTestId="action-radio" + />, + ); + + const icon = screen.getByTestId("InfoOutlinedIcon"); + fireEvent.mouseOver(icon); + + const tooltip = await screen.findByText("Info Here", {}, { timeout: 500 }); + expect(tooltip).toBeInTheDocument(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/CopyButton.test.tsx b/govtool/frontend/src/components/atoms/CopyButton.test.tsx new file mode 100644 index 000000000..8f7666858 --- /dev/null +++ b/govtool/frontend/src/components/atoms/CopyButton.test.tsx @@ -0,0 +1,69 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { SnackbarProvider } from "@context"; +import { CopyButton } from "@atoms"; + +Object.defineProperty(global.navigator, "clipboard", { + value: { + writeText: vi.fn(), + }, + writable: true, +}); + +vi.mock("@hooks", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), + useScreenDimension: () => ({ + isMobile: false, + }), +})); + +const writeTextMock = navigator.clipboard.writeText as unknown as { + mockClear: () => void; +}; + +describe("CopyButton", () => { + beforeEach(() => { + writeTextMock.mockClear(); + }); + + it("renders correctly with the default icon", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/Copy.svg"); + }); + + it("renders the blue icon when variant is 'blue'", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyBlue.svg"); + }); + + it("renders the blue thin icon when variant is 'blueThin'", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyBlueThin.svg"); + }); + + it("renders the white icon when isChecked prop is true", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyWhite.svg"); + }); + + it("copies text to clipboard and shows success alert on click", async () => { + render( + + , + , + ); + + const copyButton = screen.getByTestId("copy-button"); + await userEvent.click(copyButton); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith("Example Text"); + + expect(screen.getByText("alerts.copiedToClipboard")).toBeInTheDocument(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/DrawerLink.test.tsx b/govtool/frontend/src/components/atoms/DrawerLink.test.tsx new file mode 100644 index 000000000..70449e3a8 --- /dev/null +++ b/govtool/frontend/src/components/atoms/DrawerLink.test.tsx @@ -0,0 +1,90 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { DrawerLink } from "@atoms"; +import { theme } from "@/theme"; + +describe("DrawerLink", () => { + const mockOnClick = vi.fn(); + + it("renders correctly with mandatory props", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).toHaveAttribute("href", "/home"); + expect(screen.getByText("Home")).toBeInTheDocument(); + }); + + it("applies active styles correctly when active", () => { + render( + + + } + /> + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).toHaveStyle( + `backgroundColor: ${theme.palette.highlightBlue}`, + ); + }); + + it("does not apply active styles when not active", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).not.toHaveStyle( + `backgroundColor: ${theme.palette.highlightBlue}`, + ); + }); + + it("renders with an icon and activeIcon", () => { + const icon = "icon-path.png"; + const activeIcon = "active-icon-path.png"; + + render( + + + + } + /> + + , + ); + + const img = screen.getByAltText("icon") as HTMLImageElement; + expect(img.src).toContain("active-icon-path.png"); + }); + + it("executes onClick callback when clicked", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + fireEvent.click(linkElement); + expect(mockOnClick).toHaveBeenCalled(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/LoadingButton.test.tsx b/govtool/frontend/src/components/atoms/LoadingButton.test.tsx new file mode 100644 index 000000000..f6e543bd9 --- /dev/null +++ b/govtool/frontend/src/components/atoms/LoadingButton.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { LoadingButton } from "@atoms"; + +describe("LoadingButton", () => { + it("renders its children", () => { + render(Click me); + expect(screen.getByText("Click me")).toBeInTheDocument(); + }); + + it("is disabled when isLoading is true", () => { + render(Loading...); + expect(screen.getByRole("button", { name: "Loading..." })).toBeDisabled(); + }); + + it("is disabled when disabled prop is true", () => { + render(Disabled); + expect(screen.getByRole("button", { name: "Disabled" })).toBeDisabled(); + }); + + it("shows a CircularProgress when isLoading", () => { + render(Loading...); + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("applies different heights based on size prop", () => { + const { rerender } = render( + Small Button, + ); + + expect(screen.getByText("Small Button")).toHaveStyle({ height: "32px" }); + + rerender(Medium Button); + expect(screen.getByText("Medium Button")).toHaveStyle({ height: "36px" }); + + rerender(Large Button); + expect(screen.getByText("Large Button")).toHaveStyle({ height: "40px" }); + + rerender( + Extra Large Button, + ); + expect(screen.getByText("Extra Large Button")).toHaveStyle("height: 48px"); + }); + + it("applies custom styles via sx prop", () => { + const customStyles = { backgroundColor: "specialCyan" }; + render(Styled Button); + + expect(screen.getByText("Styled Button")).toHaveStyle({ + backgroundColor: "specialCyan", + }); + }); +}); diff --git a/govtool/frontend/src/components/atoms/VotePill.test.tsx b/govtool/frontend/src/components/atoms/VotePill.test.tsx new file mode 100644 index 000000000..24f09a4d0 --- /dev/null +++ b/govtool/frontend/src/components/atoms/VotePill.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect } from "vitest"; +import { render } from "@testing-library/react"; +import { VotePill } from "@atoms"; + +describe("VotePill", () => { + it('renders the VotePill component with "yes" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("yes"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#C0E4BA", + backgroundColor: "#F0F9EE", + }); + }); + + it('renders the VotePill component with "no" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("no"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#EDACAC", + backgroundColor: "#FBEBEB", + }); + }); + + it('renders the VotePill component with "abstain" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("abstain"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#99ADDE", + backgroundColor: "#E6EBF7", + }); + }); + + it("handles custom width and maxWidth props correctly", () => { + const { container } = render( + , + ); + const pillBox = container.firstChild; + expect(pillBox).toHaveStyle({ + width: "100px", + maxWidth: "120px", + }); + }); + + it("defaults width and maxWidth when not provided", () => { + const { container } = render(); + const pillBox = container.firstChild; + expect(pillBox).toHaveStyle({ + width: "auto", + maxWidth: "auto", + }); + }); +}); diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx new file mode 100644 index 000000000..2272cdd6f --- /dev/null +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx @@ -0,0 +1,111 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import * as Hooks from "@hooks"; +import * as Context from "@context"; +import * as Utils from "@utils"; +import { VotingPowerChips } from "@atoms"; + +describe("VotingPowerChips", () => { + const mockUseCardano = vi.spyOn(Context, "useCardano"); + const mockUseGetDRepVotingPowerQuery = vi.spyOn( + Hooks, + "useGetDRepVotingPowerQuery", + ); + const mockUseGetAdaHolderVotingPowerQuery = vi.spyOn( + Hooks, + "useGetAdaHolderVotingPowerQuery", + ); + const mockUseScreenDimension = vi.spyOn(Hooks, "useScreenDimension"); + const mockCorrectAdaFormat = vi.spyOn(Utils, "correctAdaFormat"); + const mockUseTranslation = vi.spyOn(Hooks, "useTranslation"); + const mockUseGetVoterInfo = vi.spyOn(Hooks, "useGetVoterInfo"); + + it("renders loading spinner when data is loading", () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: "demos", + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue( + {} as ReturnType, + ); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue( + {} as ReturnType, + ); + mockUseScreenDimension.mockReturnValue({ + isMobile: false, + screenWidth: 1024, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue( + {} as ReturnType, + ); + + render(); + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("displays formatted ADA amount when data is available and not loading", () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: null, + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue({ + dRepVotingPower: 1000, + } as ReturnType); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue({ + votingPower: 500, + } as ReturnType); + mockUseScreenDimension.mockReturnValue({ + isMobile: false, + screenWidth: 1024, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue({ + voter: { isRegisteredAsDRep: true }, + } as ReturnType); + mockCorrectAdaFormat.mockReturnValue(1000); + + render(); + expect(screen.getByText(/₳ 1000/)).toBeInTheDocument(); + }); + + it("displays the tooltip correctly for DRep registered users", async () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: null, + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue({ + dRepVotingPower: 1000, + } as ReturnType); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue({ + votingPower: 500, + } as ReturnType); + mockUseScreenDimension.mockReturnValue({ + isMobile: true, + screenWidth: 800, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue({ + voter: { isRegisteredAsDRep: true }, + } as ReturnType); + mockCorrectAdaFormat.mockReturnValue(1000); + + render(); + + const icon = screen.getByTestId("InfoOutlinedIcon"); + fireEvent.mouseOver(icon); + + const tooltip = await screen.findByText( + "tooltips.votingPower.heading", + {}, + { timeout: 500 }, + ); + expect(tooltip).toBeInTheDocument(); + }); +}); From 851201f38a391e95dafea4985cdf925131654747 Mon Sep 17 00:00:00 2001 From: jankun4 Date: Tue, 9 Apr 2024 12:13:26 +0200 Subject: [PATCH 032/143] [#676] fix drep/info SQL error Signed-off-by: jankun4 --- CHANGELOG.md | 1 + govtool/backend/sql/get-drep-info.sql | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2524c51f9..5f42ac266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ changes. ### Fixed +- drep/info no longer returns 500 [Issue 676](https://github.com/IntersectMBO/govtool/issues/676) - proposal/list search is case insensitive now [Issue 582](https://github.com/IntersectMBO/govtool/issues/582) - proposal/list now takes optional `search` query param [Issue 566](https://github.com/IntersectMBO/govtool/issues/566) - Fix possible sql error when there would be no predefined drep voting pwoer [Issue 501](https://github.com/IntersectMBO/govtool/issues/501) diff --git a/govtool/backend/sql/get-drep-info.sql b/govtool/backend/sql/get-drep-info.sql index a7b4536a5..899969be7 100644 --- a/govtool/backend/sql/get-drep-info.sql +++ b/govtool/backend/sql/get-drep-info.sql @@ -5,7 +5,7 @@ WITH DRepId AS ( LatestRegistrationEntry AS ( SELECT drep_registration.voting_anchor_id AS voting_anchor_id, - deposit AS deposit + drep_registration.deposit AS deposit, tx.hash as tx_hash FROM drep_registration @@ -98,7 +98,7 @@ SELECT CurrentMetadata.url, CurrentMetadata.data_hash, CurrentVotingPower.amount, - LatestRegistrationEntry.tx_hash + encode(LatestRegistrationEntry.tx_hash, 'hex') as tx_hash FROM IsRegisteredAsDRep CROSS JOIN IsRegisteredAsSoleVoter @@ -107,3 +107,4 @@ FROM CROSS JOIN CurrentDeposit CROSS JOIN CurrentMetadata CROSS JOIN CurrentVotingPower + CROSS JOIN LatestRegistrationEntry From 8e11410d0e9b7ac97b5ef8faeb8cbd536d713fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Tue, 9 Apr 2024 20:58:50 +0200 Subject: [PATCH 033/143] [#609] feat: add metadata validation against cip fields --- govtool/metadata-validation/jest.config.js | 17 +++++++ govtool/metadata-validation/package.json | 18 +------- .../src/app.controller.spec.ts | 39 ++++++++-------- .../metadata-validation/src/app.controller.ts | 6 ++- .../metadata-validation/src/app.service.ts | 13 ++++-- govtool/metadata-validation/src/dto/index.ts | 1 + .../src/dto/validateMetadata.dto.ts | 8 +++- .../src/enums/ValidationError.ts | 1 + .../metadata-validation/src/enums/index.ts | 1 + .../src/health/health.module.ts | 1 + govtool/metadata-validation/src/main.ts | 2 +- .../src/schemas/cipStandardSchema.ts | 45 +++++++++++++++++++ .../metadata-validation/src/schemas/index.ts | 1 + .../metadata-validation/src/types/index.ts | 1 + .../src/types/validateMetadata.ts | 6 ++- .../metadata-validation/src/utils/index.ts | 2 + .../utils/validateMetadataStandard.test.ts | 19 ++++++++ .../src/utils/validateMetadataStandard.ts | 20 +++++++++ govtool/metadata-validation/tsconfig.json | 11 ++++- govtool/metadata-validation/yarn.lock | 40 +++++++++++++++++ 20 files changed, 205 insertions(+), 47 deletions(-) create mode 100644 govtool/metadata-validation/jest.config.js create mode 100644 govtool/metadata-validation/src/dto/index.ts create mode 100644 govtool/metadata-validation/src/enums/index.ts create mode 100644 govtool/metadata-validation/src/schemas/cipStandardSchema.ts create mode 100644 govtool/metadata-validation/src/schemas/index.ts create mode 100644 govtool/metadata-validation/src/utils/validateMetadataStandard.test.ts create mode 100644 govtool/metadata-validation/src/utils/validateMetadataStandard.ts diff --git a/govtool/metadata-validation/jest.config.js b/govtool/metadata-validation/jest.config.js new file mode 100644 index 000000000..7f87b8f22 --- /dev/null +++ b/govtool/metadata-validation/jest.config.js @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { pathsToModuleNameMapper } = require('ts-jest'); +const { compilerOptions } = require('./tsconfig'); + +module.exports = { + preset: 'ts-jest', + moduleFileExtensions: ['js', 'json', 'ts'], + testRegex: '.*\\.(spec|test)\\.ts$', + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + collectCoverageFrom: ['**/*.(t|j)s'], + coverageDirectory: '../coverage', + testEnvironment: 'node', + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths), + modulePaths: [''], +}; diff --git a/govtool/metadata-validation/package.json b/govtool/metadata-validation/package.json index 264d89c61..97c3e9f57 100644 --- a/govtool/metadata-validation/package.json +++ b/govtool/metadata-validation/package.json @@ -31,6 +31,7 @@ "blakejs": "^1.2.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "joi": "^17.12.3", "jsonld": "^8.3.2", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1" @@ -57,22 +58,5 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" } } diff --git a/govtool/metadata-validation/src/app.controller.spec.ts b/govtool/metadata-validation/src/app.controller.spec.ts index a05c9b88a..f74ccb63d 100644 --- a/govtool/metadata-validation/src/app.controller.spec.ts +++ b/govtool/metadata-validation/src/app.controller.spec.ts @@ -1,9 +1,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { HttpModule } from '@nestjs/axios'; +import { MetadataValidationStatus } from '@enums'; + import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { MetadataValidationStatus } from './enums/ValidationError'; // TODO: Mock HttpService describe('AppController', () => { @@ -24,28 +25,26 @@ describe('AppController', () => { appController = app.get(AppController); }); - describe('metadata validation', () => { - it('should throw invalid URL', async () => { - const result = await appController.validateMetadata({ - hash: 'hash', - url: 'url', - }); - expect(result).toEqual({ - status: MetadataValidationStatus.URL_NOT_FOUND, - valid: false, - }); + it('should throw invalid URL', async () => { + const result = await appController.validateMetadata({ + hash: 'hash', + url: 'url', + }); + expect(result).toEqual({ + status: MetadataValidationStatus.URL_NOT_FOUND, + valid: false, }); + }); - it('should throw invalid JSONLD', async () => { - const result = await appController.validateMetadata({ - hash: 'hash', - url: 'http://www.schema.org', - }); + it('should throw invalid JSONLD', async () => { + const result = await appController.validateMetadata({ + hash: 'hash', + url: 'http://www.schema.org', + }); - expect(result).toEqual({ - status: MetadataValidationStatus.INVALID_JSONLD, - valid: false, - }); + expect(result).toEqual({ + status: MetadataValidationStatus.INVALID_JSONLD, + valid: false, }); }); }); diff --git a/govtool/metadata-validation/src/app.controller.ts b/govtool/metadata-validation/src/app.controller.ts index 6b248a789..13e2a7986 100644 --- a/govtool/metadata-validation/src/app.controller.ts +++ b/govtool/metadata-validation/src/app.controller.ts @@ -1,7 +1,9 @@ import { Controller, Body, Post } from '@nestjs/common'; + +import { ValidateMetadataDTO } from '@dto'; +import { ValidateMetadataResult } from '@types'; + import { AppService } from './app.service'; -import { ValidateMetadataDTO } from './dto/validateMetadata.dto'; -import { ValidateMetadataResult } from './types/validateMetadata'; @Controller() export class AppController { diff --git a/govtool/metadata-validation/src/app.service.ts b/govtool/metadata-validation/src/app.service.ts index 22a774851..c29cf16fa 100644 --- a/govtool/metadata-validation/src/app.service.ts +++ b/govtool/metadata-validation/src/app.service.ts @@ -3,10 +3,10 @@ import { catchError, firstValueFrom } from 'rxjs'; import { HttpService } from '@nestjs/axios'; import * as blake from 'blakejs'; -import { ValidateMetadataDTO } from './dto/validateMetadata.dto'; -import { MetadataValidationStatus } from './enums/ValidationError'; -import { canonizeJSON } from './utils/canonizeJSON'; -import { ValidateMetadataResult } from './types/validateMetadata'; +import { ValidateMetadataDTO } from '@dto'; +import { MetadataValidationStatus } from '@enums'; +import { canonizeJSON, validateMetadataStandard } from '@utils'; +import { ValidateMetadataResult } from '@types'; @Injectable() export class AppService { @@ -15,6 +15,7 @@ export class AppService { async validateMetadata({ hash, url, + standard, }: ValidateMetadataDTO): Promise { let status: MetadataValidationStatus; try { @@ -26,6 +27,10 @@ export class AppService { ), ); + if (standard) { + await validateMetadataStandard(data, standard); + } + let canonizedMetadata; try { canonizedMetadata = await canonizeJSON(data); diff --git a/govtool/metadata-validation/src/dto/index.ts b/govtool/metadata-validation/src/dto/index.ts new file mode 100644 index 000000000..7e7770094 --- /dev/null +++ b/govtool/metadata-validation/src/dto/index.ts @@ -0,0 +1 @@ +export * from './validateMetadata.dto'; diff --git a/govtool/metadata-validation/src/dto/validateMetadata.dto.ts b/govtool/metadata-validation/src/dto/validateMetadata.dto.ts index c7f3cd4d1..966e1f377 100644 --- a/govtool/metadata-validation/src/dto/validateMetadata.dto.ts +++ b/govtool/metadata-validation/src/dto/validateMetadata.dto.ts @@ -1,4 +1,6 @@ -import { IsUrl, IsNotEmpty } from 'class-validator'; +import { IsUrl, IsNotEmpty, IsEnum, IsOptional } from 'class-validator'; + +import { MetadataStandard } from '@types'; export class ValidateMetadataDTO { @IsNotEmpty() @@ -6,4 +8,8 @@ export class ValidateMetadataDTO { @IsUrl() url: string; + + @IsOptional() + @IsEnum(MetadataStandard) + standard?: MetadataStandard; } diff --git a/govtool/metadata-validation/src/enums/ValidationError.ts b/govtool/metadata-validation/src/enums/ValidationError.ts index b691cc99b..3471773a8 100644 --- a/govtool/metadata-validation/src/enums/ValidationError.ts +++ b/govtool/metadata-validation/src/enums/ValidationError.ts @@ -2,4 +2,5 @@ export enum MetadataValidationStatus { URL_NOT_FOUND = 'URL_NOT_FOUND', INVALID_JSONLD = 'INVALID_JSONLD', INVALID_HASH = 'INVALID_HASH', + INCORRECT_FORMAT = 'INCORRECT_FORMAT', } diff --git a/govtool/metadata-validation/src/enums/index.ts b/govtool/metadata-validation/src/enums/index.ts new file mode 100644 index 000000000..118d3701d --- /dev/null +++ b/govtool/metadata-validation/src/enums/index.ts @@ -0,0 +1 @@ +export * from './ValidationError'; diff --git a/govtool/metadata-validation/src/health/health.module.ts b/govtool/metadata-validation/src/health/health.module.ts index 0208ef743..9ed184e8f 100644 --- a/govtool/metadata-validation/src/health/health.module.ts +++ b/govtool/metadata-validation/src/health/health.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; + import { HealthController } from './health.controller'; @Module({ diff --git a/govtool/metadata-validation/src/main.ts b/govtool/metadata-validation/src/main.ts index b61303f21..c27be2ac4 100644 --- a/govtool/metadata-validation/src/main.ts +++ b/govtool/metadata-validation/src/main.ts @@ -1,8 +1,8 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); diff --git a/govtool/metadata-validation/src/schemas/cipStandardSchema.ts b/govtool/metadata-validation/src/schemas/cipStandardSchema.ts new file mode 100644 index 000000000..913b9e952 --- /dev/null +++ b/govtool/metadata-validation/src/schemas/cipStandardSchema.ts @@ -0,0 +1,45 @@ +import * as Joi from 'joi'; + +import { MetadataStandard } from '@types'; + +type StandardSpecification = Record>; + +const CIP100_URL = + 'https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#'; +const CIP108_URL = + 'https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#'; + +export const cipStandardSchema: StandardSpecification = { + // Source of CIP-108: https://github.com/Ryun1/CIPs/blob/governance-metadata-actions/CIP-0108/README.md + [MetadataStandard.CIP108]: Joi.object({ + '@context': Joi.object({ + '@language': Joi.string().required(), + CIP100: Joi.string().valid(CIP100_URL).required(), + CIP108: Joi.string().valid(CIP108_URL).required(), + hashAlgorithm: Joi.string().valid('CIP100:hashAlgorithm').required(), + body: Joi.object(), + authors: Joi.object(), + }), + authors: Joi.array(), + hashAlgorithm: Joi.object({ + '@value': Joi.string().valid('blake2b-256').required(), + }), + body: Joi.object({ + title: Joi.object({ '@value': Joi.string().max(80).required() }), + abstract: Joi.object({ '@value': Joi.string().max(2500).required() }), + motivation: Joi.object({ '@value': Joi.string().required() }), + rationale: Joi.object({ '@value': Joi.string().required() }), + references: Joi.array().items( + Joi.object({ + '@type': Joi.string(), + label: Joi.object({ '@value': Joi.string().required() }), + uri: Joi.object({ '@value': Joi.string().uri().required() }), + referenceHash: Joi.object({ + hashDigest: Joi.string().required(), + hashAlgorithm: Joi.string().required(), + }), + }).required(), + ), + }), + }), +}; diff --git a/govtool/metadata-validation/src/schemas/index.ts b/govtool/metadata-validation/src/schemas/index.ts new file mode 100644 index 000000000..874d081db --- /dev/null +++ b/govtool/metadata-validation/src/schemas/index.ts @@ -0,0 +1 @@ +export * from './cipStandardSchema'; diff --git a/govtool/metadata-validation/src/types/index.ts b/govtool/metadata-validation/src/types/index.ts index e69de29bb..085381c12 100644 --- a/govtool/metadata-validation/src/types/index.ts +++ b/govtool/metadata-validation/src/types/index.ts @@ -0,0 +1 @@ +export * from './validateMetadata'; diff --git a/govtool/metadata-validation/src/types/validateMetadata.ts b/govtool/metadata-validation/src/types/validateMetadata.ts index 5225fd265..dcdf28363 100644 --- a/govtool/metadata-validation/src/types/validateMetadata.ts +++ b/govtool/metadata-validation/src/types/validateMetadata.ts @@ -1,4 +1,8 @@ -import { MetadataValidationStatus } from '../enums/ValidationError'; +import { MetadataValidationStatus } from '@enums'; + +export enum MetadataStandard { + CIP108 = 'CIP108', +} export type ValidateMetadataResult = { status?: MetadataValidationStatus; diff --git a/govtool/metadata-validation/src/utils/index.ts b/govtool/metadata-validation/src/utils/index.ts index e69de29bb..648150db2 100644 --- a/govtool/metadata-validation/src/utils/index.ts +++ b/govtool/metadata-validation/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './canonizeJSON'; +export * from './validateMetadataStandard'; diff --git a/govtool/metadata-validation/src/utils/validateMetadataStandard.test.ts b/govtool/metadata-validation/src/utils/validateMetadataStandard.test.ts new file mode 100644 index 000000000..b3b58fc17 --- /dev/null +++ b/govtool/metadata-validation/src/utils/validateMetadataStandard.test.ts @@ -0,0 +1,19 @@ +import { MetadataStandard } from '@types'; + +import { validateMetadataStandard } from './validateMetadataStandard'; + +describe('validateMetadataStandard', () => { + it('should throw MetadataValidationStatus.INCORRECT_FORMAT if validation fails', async () => { + try { + await validateMetadataStandard( + { + testValue: 'test', + anotherValue: 'another', + }, + MetadataStandard.CIP108, + ); + } catch (error) { + expect(error).toBe('INCORRECT_FORMAT'); + } + }); +}); diff --git a/govtool/metadata-validation/src/utils/validateMetadataStandard.ts b/govtool/metadata-validation/src/utils/validateMetadataStandard.ts new file mode 100644 index 000000000..702027769 --- /dev/null +++ b/govtool/metadata-validation/src/utils/validateMetadataStandard.ts @@ -0,0 +1,20 @@ +import { MetadataValidationStatus } from '@enums'; +import { cipStandardSchema } from '@schemas'; +import { MetadataStandard } from '@types'; + +/** + * Validates the metadata against a specific standard. + * @param data - The metadata to be validated. + * @param standard - The metadata standard to validate against. + * @throws {MetadataValidationStatus.INCORRECT_FORMAT} - If the metadata does not conform to the specified standard. + */ +export const validateMetadataStandard = async ( + data: Record, + standard: MetadataStandard, +) => { + try { + await cipStandardSchema[standard]?.validateAsync(data); + } catch (error) { + throw MetadataValidationStatus.INCORRECT_FORMAT; + } +}; diff --git a/govtool/metadata-validation/tsconfig.json b/govtool/metadata-validation/tsconfig.json index 95f5641cf..1044dc39f 100644 --- a/govtool/metadata-validation/tsconfig.json +++ b/govtool/metadata-validation/tsconfig.json @@ -16,6 +16,15 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "@/*": ["src/*"], + "@dto": ["src/dto"], + "@enums": ["src/enums"], + "@health": ["src/health"], + "@schemas": ["src/schemas"], + "@types": ["src/types"], + "@utils": ["src/utils"] + } } } diff --git a/govtool/metadata-validation/yarn.lock b/govtool/metadata-validation/yarn.lock index 3b456c21d..c39cc7697 100644 --- a/govtool/metadata-validation/yarn.lock +++ b/govtool/metadata-validation/yarn.lock @@ -396,6 +396,18 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -858,6 +870,23 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -3517,6 +3546,17 @@ jest@^29.5.0: import-local "^3.0.2" jest-cli "^29.7.0" +joi@^17.12.3: + version "17.12.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" + integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" From 8dba540a81e42522844317e4b3322408c44f9b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Wed, 10 Apr 2024 09:06:27 +0200 Subject: [PATCH 034/143] [#669] add todo comment --- .../src/components/organisms/AutomatedVotingOptions.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx index 1ba7a6a1b..25eacb35b 100644 --- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx +++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx @@ -84,6 +84,7 @@ export const AutomatedVotingOptions = ({ isDelegateLoading={isDelegationLoading} isSelected={currentDelegation === "drep_always_abstain"} onClickDelegate={() => delegate("abstain")} + // TODO: Add onClick info onClickInfo={() => {}} title={t("dRepDirectory.abstainCardTitle")} votingPower={votingPower} @@ -100,6 +101,7 @@ export const AutomatedVotingOptions = ({ isDelegateLoading={isDelegationLoading} isSelected={currentDelegation === "drep_always_no_confidence"} onClickDelegate={() => delegate("no confidence")} + // TODO: Add onClick info onClickInfo={() => {}} title={t("dRepDirectory.noConfidenceTitle")} votingPower={votingPower} From b703d8db2666a80aad8844d555c9ff5bcf4ac2f5 Mon Sep 17 00:00:00 2001 From: jankun4 Date: Wed, 10 Apr 2024 11:26:10 +0200 Subject: [PATCH 035/143] [#685] fix getVotes 500 error Signed-off-by: jankun4 --- CHANGELOG.md | 1 + govtool/backend/sql/get-votes.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f42ac266..018b544e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ changes. ### Fixed +- drep/getVotes no longer returns 500 [Issue 685](https://github.com/IntersectMBO/govtool/issues/685) - drep/info no longer returns 500 [Issue 676](https://github.com/IntersectMBO/govtool/issues/676) - proposal/list search is case insensitive now [Issue 582](https://github.com/IntersectMBO/govtool/issues/582) - proposal/list now takes optional `search` query param [Issue 566](https://github.com/IntersectMBO/govtool/issues/566) diff --git a/govtool/backend/sql/get-votes.sql b/govtool/backend/sql/get-votes.sql index 1e9a49cfe..7a875aba8 100644 --- a/govtool/backend/sql/get-votes.sql +++ b/govtool/backend/sql/get-votes.sql @@ -1,4 +1,5 @@ select DISTINCT ON (voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter) voting_procedure.gov_action_proposal_id, concat(encode(gov_action_tx.hash,'hex'),'#',gov_action_proposal.index), encode(drep_hash.raw, 'hex'), voting_procedure.vote::text, voting_anchor.url, encode(voting_anchor.data_hash, 'hex'), block.epoch_no as epoch_no, block.time as time, encode(vote_tx.hash, 'hex') as vote_tx_hash +from voting_procedure join gov_action_proposal on gov_action_proposal.id = voting_procedure.gov_action_proposal_id join drep_hash From 2790e1598ac760cbd56a1f8763a5fa8cf3e30599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Placzy=C5=84ski?= Date: Thu, 11 Apr 2024 09:20:04 +0200 Subject: [PATCH 036/143] [#678] Apply specific CORS parameters for metadata-validation service In this commit, specific CORS parameters are added to the `docker-compose.yml.tpl` file to facilitate developers' access to the metadata validation service from their individual machines within the dev environment. The CORS configuration includes settings related to allowed methods, headers, origins, and maximum age. By implementing these CORS parameters, developers can utilize the metadata validation service effectively while ensuring secure and controlled access from their local environments. --- scripts/govtool/config/templates/docker-compose.yml.tpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/govtool/config/templates/docker-compose.yml.tpl b/scripts/govtool/config/templates/docker-compose.yml.tpl index cc05931c3..e16588bd7 100644 --- a/scripts/govtool/config/templates/docker-compose.yml.tpl +++ b/scripts/govtool/config/templates/docker-compose.yml.tpl @@ -225,6 +225,10 @@ services: - "traefik.http.routers.metadata-validation.rule=Host(``) && PathPrefix(`/metadata-validation`)" - "traefik.http.middlewares.metadata-validation-stripprefix.stripprefix.prefixes=/metadata-validation" - "traefik.http.routers.metadata-validation.middlewares=metadata-validation-stripprefix@docker" + - "traefik.http.middlewares.backend-cors.headers.accesscontrolallowmethods=*" + - "traefik.http.middlewares.backend-cors.headers.accesscontrolallowheaders=*" + - "traefik.http.middlewares.backend-cors.headers.accesscontrolalloworiginlist=https://" + - "traefik.http.middlewares.backend-cors.headers.accesscontrolmaxage=100" - "traefik.http.routers.metadata-validation.entrypoints=websecure" - "traefik.http.routers.metadata-validation.tls.certresolver=myresolver" - "traefik.http.services.metadata-validation.loadbalancer.server.port=3000" From 51b9431f68ba534223c2636f196e3fbbe20bcc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Placzy=C5=84ski?= Date: Thu, 11 Apr 2024 09:22:45 +0200 Subject: [PATCH 037/143] Use a variable to determine the target The commit makes usage of a variable in the `config.mk` file to determine the target configuration file dynamically based on the environment. This change enhances the flexibility and efficiency of the script, allowing for seamless configuration adjustments across different environments. --- scripts/govtool/config.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/govtool/config.mk b/scripts/govtool/config.mk index 9374da759..4a680e5ba 100644 --- a/scripts/govtool/config.mk +++ b/scripts/govtool/config.mk @@ -50,7 +50,7 @@ clear: $(output_dirs): mkdir -p $@ -$(target_config_dir)/docker-compose.yml: $(template_config_dir)/docker-compose.yml.tpl $(target_config_dir)/ +$(docker_compose_file): $(template_config_dir)/docker-compose.yml.tpl $(target_config_dir)/ if [[ "$(env)" == "dev" ]]; then CSP_ALLOWED_HOSTS=",http://localhost"; else CSP_ALLOWED_HOSTS=; fi; \ sed -e "s||$(domain)|g" \ -e "s||$(docker_user)|g" \ From 332733e56cc3b0d0bfe08caad23bbe80cbbdab4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Thu, 11 Apr 2024 09:50:44 +0200 Subject: [PATCH 038/143] [#687]change voting power displaying --- .../src/components/atoms/VotingPowerChips.tsx | 110 +++++++++--------- .../components/organisms/DashboardTopNav.tsx | 1 + .../queries/useGetDRepVotingPowerQuery.ts | 5 +- 3 files changed, 55 insertions(+), 61 deletions(-) diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx index 4e5381e56..a81cfb582 100644 --- a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx @@ -4,7 +4,6 @@ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Typography, Tooltip } from "@atoms"; import { useCardano } from "@context"; import { - useGetAdaHolderVotingPowerQuery, useGetDRepVotingPowerQuery, useGetVoterInfo, useScreenDimension, @@ -13,67 +12,62 @@ import { import { correctAdaFormat } from "@utils"; export const VotingPowerChips = () => { - const { stakeKey, isEnableLoading } = useCardano(); - const { dRepVotingPower } = useGetDRepVotingPowerQuery(); - const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); + const { isEnableLoading } = useCardano(); + const { voter } = useGetVoterInfo(); + const { dRepVotingPower } = useGetDRepVotingPowerQuery(voter); const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); - const { voter } = useGetVoterInfo(); return ( - - {voter?.isRegisteredAsDRep && ( - - - - )} - {screenWidth >= 1024 && ( - - {t("votingPower")} - - )} - {(voter?.isRegisteredAsDRep && dRepVotingPower === undefined) || - (!voter?.isRegisteredAsDRep && votingPower === undefined) || - isEnableLoading || - !voter ? ( + (voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && ( + + {!isMobile && ( + + + + )} + {screenWidth >= 1024 && ( + + {t("votingPower")}: + + )} + {dRepVotingPower === undefined || isEnableLoading || !voter ? ( - ) : ( - - ₳{" "} - {voter?.isRegisteredAsDRep - ? correctAdaFormat(dRepVotingPower) ?? 0 - : correctAdaFormat(votingPower) ?? 0} - - )} - + ) : ( + + ₳ {correctAdaFormat(dRepVotingPower) ?? 0} + + )} + + ) ); }; diff --git a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx index 787735251..fdf0a29c9 100644 --- a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx +++ b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx @@ -53,6 +53,7 @@ export const DashboardTopNav = ({ display: "flex", justifyContent: "space-between", position: "sticky", + minHeight: isMobile ? 36 : 48, px: isMobile ? 2 : 5, py: 3, top: 0, diff --git a/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerQuery.ts index 03247b518..5c7709fe9 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerQuery.ts @@ -3,11 +3,10 @@ import { useQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { getDRepVotingPower } from "@services"; -import { useGetVoterInfo } from "."; +import { VoterInfo } from "@/models"; -export const useGetDRepVotingPowerQuery = () => { +export const useGetDRepVotingPowerQuery = (voter?: VoterInfo) => { const { dRepID } = useCardano(); - const { voter } = useGetVoterInfo(); const { data, isLoading } = useQuery({ queryKey: [ From 9f2d5abac02da05c915ad468892b0c3a861ad6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Thu, 11 Apr 2024 10:38:08 +0200 Subject: [PATCH 039/143] [#655] fix: fix DRep registration when the DRep name is longer --- CHANGELOG.md | 2 ++ .../StorageInformation.tsx | 24 +++---------------- .../EditDRepStorageInformation.tsx | 5 ++-- .../DRepStorageInformation.tsx | 9 +++---- .../VoteContextStoringInformation.tsx | 5 ++-- .../src/consts/governanceAction/fields.ts | 6 +++++ .../src/hooks/forms/useRegisterAsdRepForm.tsx | 9 +++++-- govtool/frontend/src/utils/ellipsizeText.ts | 12 ++++++++++ govtool/frontend/src/utils/index.ts | 1 + govtool/frontend/src/utils/isValidFormat.ts | 6 ++--- .../src/utils/tests/ellipsizeText.test.ts | 22 +++++++++++++++++ .../src/utils/tests/isValidFormat.test.ts | 13 ++++++---- 12 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 govtool/frontend/src/utils/ellipsizeText.ts create mode 100644 govtool/frontend/src/utils/tests/ellipsizeText.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f42ac266..2e1b2f0b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ changes. - Integrate frontend with metadata validation service [Issue 617](https://github.com/IntersectMBO/govtool/issues/617) - Implement a loading modal for the validation of the metadata [Issue 646](https://github.com/IntersectMBO/govtool/issues/646) +- Change style of url button to trim the file name [Issue 655](https://github.com/IntersectMBO/govtool/issues/655) +- Change regex for parsing urls to match urls without protocol [Issue 655](https://github.com/IntersectMBO/govtool/issues/655) ### Added diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx index 5363ab19e..2a9469196 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx @@ -35,9 +35,10 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { const fileName = getValues("governance_action_type"); - // TODO: Change link to correct const openGuideAboutStoringInformation = () => - openInNewTab("https://sancho.network/"); + openInNewTab( + "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor", + ); const isActionButtonDisabled = !watch("storingURL") || !!errors.storingURL; @@ -120,25 +121,6 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { /> - } - onClick={openGuideAboutStoringInformation} - size="extraLarge" - sx={{ width: "fit-content" }} - variant="text" - > - {t("createGovernanceAction.storingInformationStep2Link")} - - } label={t("createGovernanceAction.storingInformationStep2Label")} stepNumber={2} /> diff --git a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx index 5a7b6293c..119664349 100644 --- a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx @@ -35,9 +35,10 @@ export const EditDRepStorageInformation = ({ const fileName = getValues("dRepName"); - // TODO: Change link to correct const openGuideAboutStoringInformation = () => - openInNewTab("https://sancho.network/"); + openInNewTab( + "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor", + ); const isActionButtonDisabled = !watch("storingURL") || !!errors.storingURL; diff --git a/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStorageInformation.tsx b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStorageInformation.tsx index e021583ff..3588eb1fd 100644 --- a/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStorageInformation.tsx @@ -11,7 +11,7 @@ import { } from "@hooks"; import { Step } from "@molecules"; import { BgCard, ControlledField } from "@organisms"; -import { openInNewTab } from "@utils"; +import { openInNewTab, ellipsizeText } from "@utils"; type StorageInformationProps = { setStep: Dispatch>; @@ -35,9 +35,10 @@ export const DRepStorageInformation = ({ const fileName = getValues("dRepName"); - // TODO: Change link to correct const openGuideAboutStoringInformation = () => - openInNewTab("https://sancho.network/"); + openInNewTab( + "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor", + ); const isActionButtonDisabled = !watch("storingURL") || !!errors.storingURL; @@ -93,7 +94,7 @@ export const DRepStorageInformation = ({ }} variant="outlined" > - {`${fileName}.jsonld`} + {`${ellipsizeText(fileName, 8)}.jsonld`} } label={t("registration.storingInformationStep1Label")} diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx index 96d91b3a8..3646a7cb7 100644 --- a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx @@ -34,9 +34,10 @@ export const VoteContextStoringInformation = ({ onClickDownloadJson, } = useVoteContextForm(setSavedHash, setStep, setErrorMessage); - // TODO: Change link to correct const openGuideAboutStoringInformation = () => - openInNewTab("https://sancho.network/"); + openInNewTab( + "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor", + ); const isContinueDisabled = !watch("storingURL"); diff --git a/govtool/frontend/src/consts/governanceAction/fields.ts b/govtool/frontend/src/consts/governanceAction/fields.ts index b46cc8480..19da762fb 100644 --- a/govtool/frontend/src/consts/governanceAction/fields.ts +++ b/govtool/frontend/src/consts/governanceAction/fields.ts @@ -19,6 +19,12 @@ export const sharedGovernanceActionFields: SharedGovernanceActionFieldSchema = { placeholderI18nKey: "createGovernanceAction.fields.declarations.title.placeholder", rules: { + maxLength: { + value: 80, + message: I18n.t("createGovernanceAction.fields.validations.maxLength", { + maxLength: 80, + }), + }, required: { value: true, message: I18n.t("createGovernanceAction.fields.validations.required"), diff --git a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx index d7f502f43..12f0b73b9 100644 --- a/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useRegisterAsdRepForm.tsx @@ -15,7 +15,12 @@ import { } from "@consts"; import { useCardano, useModal } from "@context"; import { MetadataValidationStatus } from "@models"; -import { canonizeJSON, downloadJson, generateJsonld } from "@utils"; +import { + canonizeJSON, + downloadJson, + ellipsizeText, + generateJsonld, +} from "@utils"; import { useGetVoterInfo } from ".."; import { useValidateMutation } from "../mutations"; @@ -110,7 +115,7 @@ export const useRegisterAsdRepForm = ( const onClickDownloadJson = async () => { if (!json) return; - downloadJson(json, dRepName); + downloadJson(json, ellipsizeText(dRepName, 16, "")); }; const validateHash = useCallback( diff --git a/govtool/frontend/src/utils/ellipsizeText.ts b/govtool/frontend/src/utils/ellipsizeText.ts new file mode 100644 index 000000000..262682bd5 --- /dev/null +++ b/govtool/frontend/src/utils/ellipsizeText.ts @@ -0,0 +1,12 @@ +export const ellipsizeText = ( + text: string, + maxLength: number, + ellipsize = "...", +) => { + if (text.length <= maxLength) { + return text; + } + const slicedText = text.slice(0, maxLength); + + return `${slicedText.trimEnd()}${ellipsize}`; +}; diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts index 22dc4a84f..58333c14d 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -7,6 +7,7 @@ export * from "./canonizeJSON"; export * from "./checkIsMaintenanceOn"; export * from "./checkIsWalletConnected"; export * from "./dRep"; +export * from "./ellipsizeText"; export * from "./formatDate"; export * from "./generateAnchor"; export * from "./generateJsonld"; diff --git a/govtool/frontend/src/utils/isValidFormat.ts b/govtool/frontend/src/utils/isValidFormat.ts index 4b4ccafa8..be9e65822 100644 --- a/govtool/frontend/src/utils/isValidFormat.ts +++ b/govtool/frontend/src/utils/isValidFormat.ts @@ -1,15 +1,15 @@ export const URL_REGEX = - /^(?!.*\s)(ipfs:\/\/[a-zA-Z0-9]+|https?:\/\/(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[^/\s]+?\.[a-zA-Z]{2,})[^\s]*)/; + /^(?:(?:https?:\/\/)?(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?:\/[^\s]*)?)|(?:ipfs:\/\/[a-f0-9]+(?:\/[a-zA-Z0-9_]+)*)$|^$/; export const HASH_REGEX = /^[0-9A-Fa-f]+$/; export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; export const NICKNAME_REGEX = /^\S+$/; export function isValidURLFormat(str: string) { - if (!str.length) return true; + if (!str.length) return false; return URL_REGEX.test(str); } export function isValidHashFormat(str: string) { - if (!str.length) return true; + if (!str.length) return false; return HASH_REGEX.test(str); } diff --git a/govtool/frontend/src/utils/tests/ellipsizeText.test.ts b/govtool/frontend/src/utils/tests/ellipsizeText.test.ts new file mode 100644 index 000000000..d09a3f254 --- /dev/null +++ b/govtool/frontend/src/utils/tests/ellipsizeText.test.ts @@ -0,0 +1,22 @@ +import { ellipsizeText } from "../ellipsizeText"; + +describe("ellipsizeText", () => { + it("should return the original text if it is shorter than or equal to maxLength", () => { + const text = "Hello, World!"; + const maxLength = 20; + + const result = ellipsizeText(text, maxLength); + + expect(result).toEqual(text); + }); + + it("should return the ellipsized text if it is longer than maxLength", () => { + const text = "This is a long text that needs to be ellipsized."; + const maxLength = 8; + const expected = "This is..."; + + const result = ellipsizeText(text, maxLength); + + expect(result).toEqual(expected); + }); +}); diff --git a/govtool/frontend/src/utils/tests/isValidFormat.test.ts b/govtool/frontend/src/utils/tests/isValidFormat.test.ts index fd543b902..fc4534ba3 100644 --- a/govtool/frontend/src/utils/tests/isValidFormat.test.ts +++ b/govtool/frontend/src/utils/tests/isValidFormat.test.ts @@ -11,6 +11,11 @@ describe("isValidURLFormat", () => { expect(isValidURLFormat(validHttpsUrl)).toBe(true); }); + it("returns true for valid URL without protocol", () => { + const validUrl = "example.com"; + expect(isValidURLFormat(validUrl)).toBe(true); + }); + it("returns true for valid HTTPS URLs with IP", () => { const validHttpsUrl = "http://192.168.0.1/resoruce"; expect(isValidURLFormat(validHttpsUrl)).toBe(true); @@ -37,9 +42,9 @@ describe("isValidURLFormat", () => { expect(isValidURLFormat(notUrl)).toBe(false); }); - it("returns true for empty string", () => { + it("returns false for empty string", () => { const empty = ""; - expect(isValidURLFormat(empty)).toBe(true); + expect(isValidURLFormat(empty)).toBe(false); }); }); @@ -59,8 +64,8 @@ describe("isValidHashFormat", () => { expect(isValidHashFormat(invalidHash)).toBe(false); }); - it("returns true for empty string", () => { + it("returns false for empty string", () => { const empty = ""; - expect(isValidHashFormat(empty)).toBe(true); + expect(isValidHashFormat(empty)).toBe(false); }); }); From 93662e4679085cb32eca000ae3ac4f8380ddb51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sza=C5=82owski?= Date: Thu, 11 Apr 2024 12:53:52 +0200 Subject: [PATCH 040/143] fix: fix api connection --- govtool/frontend/src/services/API.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/govtool/frontend/src/services/API.ts b/govtool/frontend/src/services/API.ts index 1815ecbc2..2e084a29b 100644 --- a/govtool/frontend/src/services/API.ts +++ b/govtool/frontend/src/services/API.ts @@ -7,12 +7,12 @@ const TIMEOUT_IN_SECONDS = 30 * 1000; // 1000 ms is 1 s then its 10 s const BASE_URL = import.meta.env.VITE_BASE_URL; export const API = axios.create({ - baseURL: `${BASE_URL}api`, + baseURL: `${BASE_URL}/api`, timeout: TIMEOUT_IN_SECONDS, }); export const METADATA_VALIDATION_API = axios.create({ - baseURL: `${BASE_URL}metadata-validation`, + baseURL: `${BASE_URL}/metadata-validation`, timeout: TIMEOUT_IN_SECONDS, }); From 5f6cbc47b4cf09a5a318af66aab8533c3476b7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Thu, 11 Apr 2024 17:17:06 +0200 Subject: [PATCH 041/143] [#681] add loader in drep direcotry --- govtool/frontend/src/pages/DRepDirectory.tsx | 34 +++++++++++-------- .../src/pages/DRepDirectoryContent.tsx | 32 ++++++++++++++--- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/govtool/frontend/src/pages/DRepDirectory.tsx b/govtool/frontend/src/pages/DRepDirectory.tsx index cf3daac90..4c1d4014a 100644 --- a/govtool/frontend/src/pages/DRepDirectory.tsx +++ b/govtool/frontend/src/pages/DRepDirectory.tsx @@ -1,9 +1,11 @@ -import { useTranslation } from "@hooks"; import { Outlet } from "react-router-dom"; -import { checkIsWalletConnected } from "@/utils"; -import { Background, PagePaddingBox, ContentBox } from "@/components/atoms"; -import { Footer, TopNav } from "@/components/organisms"; -import { PageTitle } from "@/components/molecules"; +import { Box } from "@mui/material"; + +import { Background, PagePaddingBox, ContentBox } from "@atoms"; +import { useTranslation } from "@hooks"; +import { PageTitle } from "@molecules"; +import { Footer, TopNav } from "@organisms"; +import { checkIsWalletConnected } from "@utils"; export const DRepDirectory = () => { const { t } = useTranslation(); @@ -12,7 +14,7 @@ export const DRepDirectory = () => { if (isConnected) { return ( - + ); @@ -20,16 +22,20 @@ export const DRepDirectory = () => { return ( - + + - + - - - - - -