diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4f8ef10..a12869c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ changes. ### Added - Preserve maintenance ending banner state on the wallet connection change [Issue 3681](https://github.com/IntersectMBO/govtool/issues/3681) +- Add authors for Live Voting Governance Actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745) ### Fixed diff --git a/docs/GOVERNANCE_ACTION_SUBMISSION.md b/docs/GOVERNANCE_ACTION_SUBMISSION.md index 1c6a29d99..21327e029 100644 --- a/docs/GOVERNANCE_ACTION_SUBMISSION.md +++ b/docs/GOVERNANCE_ACTION_SUBMISSION.md @@ -134,7 +134,7 @@ const buildProtocolParameterChangeGovernanceAction: ( protocolParameterChangeProps: ProtocolParameterChangeProps ) => Promise; -const buildHardForkInitiationGovernanceAction: ( +const buildHardForkGovernanceAction: ( hardForkInitiationProps: HardForkInitiationProps ) => Promise; @@ -210,7 +210,7 @@ const { buildSignSubmitConwayCertTx, buildNewInfoGovernanceAction, buildProtocolParameterChangeGovernanceAction, - buildHardForkInitiationGovernanceAction, + buildHardForkGovernanceAction, buildTreasuryGovernanceAction, buildNewConstitutionGovernanceAction, buildUpdateCommitteeGovernanceAction, @@ -241,7 +241,7 @@ govActionBuilder = await buildProtocolParameterChangeGovernanceAction({ }); // hash of the previous Governance Action, index of the previous Governance Action, url of the metadata, hash of the metadata, and the major and minor numbers of the hard fork initiation -govActionBuilder = await buildHardForkInitiationGovernanceAction({ +govActionBuilder = await buildHardForkGovernanceAction({ prevGovernanceActionHash, prevGovernanceActionIndex, url, diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index 8ccbfd3d1..a215919f3 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -303,7 +303,18 @@ SELECT COALESCE(cv.ccNoVotes, 0) cc_no_votes, COALESCE(cv.ccAbstainVotes, 0) cc_abstain_votes, prev_gov_action.index as prev_gov_action_index, - encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash + encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash, + COALESCE( + json_agg( + json_build_object( + 'name', off_chain_vote_author.name, + 'witnessAlgorithm', off_chain_vote_author.witness_algorithm, + 'publicKey', off_chain_vote_author.public_key, + 'signature', off_chain_vote_author.signature + ) + ) FILTER (WHERE off_chain_vote_author.id IS NOT NULL), + '[]' + ) authors FROM gov_action_proposal JOIN ActiveProposals ON gov_action_proposal.id = ActiveProposals.id @@ -314,6 +325,7 @@ FROM LEFT JOIN block AS creator_block ON creator_block.id = creator_tx.block_id LEFT JOIN voting_anchor ON voting_anchor.id = gov_action_proposal.voting_anchor_id LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = voting_anchor.id + lEFT JOIN off_chain_vote_author ON off_chain_vote_author.off_chain_vote_data_id = off_chain_vote_data.id LEFT JOIN off_chain_vote_gov_action_data ON off_chain_vote_gov_action_data.off_chain_vote_data_id = off_chain_vote_data.id LEFT JOIN param_proposal AS proposal_params ON gov_action_proposal.param_proposal = proposal_params.id LEFT JOIN cost_model AS cost_model ON proposal_params.cost_model_id = cost_model.id @@ -355,4 +367,4 @@ GROUP BY off_chain_vote_gov_action_data.title, off_chain_vote_gov_action_data.abstract, off_chain_vote_gov_action_data.motivation, - off_chain_vote_gov_action_data.rationale; \ No newline at end of file + off_chain_vote_gov_action_data.rationale; diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index ebc4d9609..f8424d149 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -244,7 +244,8 @@ proposalToResponse timeZone Types.Proposal {..} = proposalResponseCcNoVotes = proposalCcNoVotes, proposalResponseCcAbstainVotes = proposalCcAbstainVotes, proposalResponsePrevGovActionIndex = proposalPrevGovActionIndex, - proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash + proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash, + proposalResponseAuthors = ProposalAuthors <$> proposalAuthors } voteToResponse :: Types.Vote -> VoteParams diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index a270a2872..f649cc855 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -401,9 +401,52 @@ data ProposalResponse , proposalResponseCcAbstainVotes :: Integer , proposalResponsePrevGovActionIndex :: Maybe Integer , proposalResponsePrevGovActionTxHash :: Maybe HexText + , proposalResponseAuthors :: Maybe ProposalAuthors } deriving (Generic, Show) +newtype ProposalAuthors = ProposalAuthors { getProposalAuthors :: Value } + deriving newtype (Show) + +instance FromJSON ProposalAuthors where + parseJSON v@(Array _) = pure $ ProposalAuthors v + parseJSON _ = fail "ProposalAuthors must be a JSON array" + +instance ToJSON ProposalAuthors where + toJSON (ProposalAuthors v) = v + +instance ToSchema ProposalAuthors where + declareNamedSchema _ = pure $ NamedSchema (Just "ProposalAuthors") $ mempty + & type_ ?~ OpenApiArray + & description ?~ "A JSON array of proposal authors" + & example ?~ toJSON + [ object + [ "name" .= ("Alice" :: Text) + , "witnessAlgorithm" .= ("algo" :: Text) + , "publicKey" .= ("key" :: Text) + , "signature" .= ("sig" :: Text) + ] + , object + [ "name" .= ("Bob" :: Text) + , "witnessAlgorithm" .= ("algo2" :: Text) + , "publicKey" .= ("key2" :: Text) + , "signature" .= ("sig2" :: Text) + ] + ] + +exampleProposalAuthors :: Text +exampleProposalAuthors = + "[\ + \ {\"name\": \"Alice\",\ + \ \"witnessAlgorithm\": \"Ed25519\",\ + \ \"publicKey\": \"abcdef123456\",\ + \ \"signature\": \"deadbeef\"},\ + \ {\"name\": \"Bob\",\ + \ \"witnessAlgorithm\": \"Ed25519\",\ + \ \"publicKey\": \"123456abcdef\",\ + \ \"signature\": \"beefdead\"}\ + \]" + deriveJSON (jsonOptions "proposalResponse") ''ProposalResponse exampleProposalResponse :: Text @@ -433,7 +476,9 @@ exampleProposalResponse = "{ \"id\": \"proposalId123\"," <> "\"cCNoVotes\": 0," <> "\"cCAbstainVotes\": 0," <> "\"prevGovActionIndex\": 0," - <> "\"prevGovActionTxHash\": \"47c14a128cd024f1b990c839d67720825921ad87ed875def42641ddd2169b39c\"}" + <> "\"prevGovActionTxHash\": \"47c14a128cd024f1b990c839d67720825921ad87ed875def42641ddd2169b39c\"," + <> "\"authors\": " <> exampleProposalAuthors + <> "}" instance ToSchema Value where declareNamedSchema _ = pure $ NamedSchema (Just "Value") $ mempty diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 217b19a5a..190eb8a78 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -208,6 +208,7 @@ data Proposal , proposalCcAbstainVotes :: Integer , proposalPrevGovActionIndex :: Maybe Integer , proposalPrevGovActionTxHash :: Maybe Text + , proposalAuthors :: Maybe Value } deriving (Show) @@ -241,6 +242,7 @@ instance FromRow Proposal where <*> (floor @Scientific <$> field) -- proposalCcAbstainVotes <*> field -- prevGovActionIndex <*> field -- prevGovActionTxHash + <*> field -- proposalAuthors data TransactionStatus = TransactionStatus { transactionConfirmed :: Bool diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 1adcee40a..612e10188 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -15,7 +15,7 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.4.3", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "0.7.0-beta-36", + "@intersect.mbo/pdf-ui": "1.0.2-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", @@ -3424,9 +3424,9 @@ "license": "ISC" }, "node_modules/@intersect.mbo/pdf-ui": { - "version": "0.7.0-beta-36", - "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.7.0-beta-36.tgz", - "integrity": "sha512-CncItia1kW42+IIWeFh+Mftwd09tull2vADGFDUwjLQGHzMNWNxiKQ4krlSqGSCJT9436XL6k6+OtdiMe06SpQ==", + "version": "1.0.2-beta", + "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-1.0.2-beta.tgz", + "integrity": "sha512-76BspGA1CLDvStXwm52BKk9RKyHoAuwIX5C1kyA0V13KvrgXO+ihhdBoaGmOOx0XGkkN/VVBwpLEpadSc9UvhA==", "dependencies": { "@emurgo/cardano-serialization-lib-asmjs": "^12.0.0-beta.2", "@fontsource/poppins": "^5.0.14", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index bb32c2bc8..c81577f21 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -29,7 +29,7 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.4.3", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "0.7.0-beta-36", + "@intersect.mbo/pdf-ui": "1.0.2-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", diff --git a/govtool/frontend/src/components/atoms/Link.tsx b/govtool/frontend/src/components/atoms/Link.tsx index 14b6862c4..c56e61f60 100644 --- a/govtool/frontend/src/components/atoms/Link.tsx +++ b/govtool/frontend/src/components/atoms/Link.tsx @@ -3,12 +3,17 @@ import { NavLink } from "react-router-dom"; import { Typography } from "@mui/material"; import { useCardano } from "@context"; +const FONT_SIZE = { + small: 14, + big: 22, +}; + type LinkProps = { dataTestId?: string; isConnectWallet?: boolean; label: React.ReactNode; navTo: string; - onClick?: (event: MouseEvent) => void; + onClick?: (event: MouseEvent) => void; size?: "small" | "big"; }; @@ -24,10 +29,7 @@ export const Link = forwardRef( } = props; const { disconnectWallet } = useCardano(); - const fontSize = { - small: 14, - big: 22, - }[size]; + const fontSize = FONT_SIZE[size]; return ( ( ); }, ); + +// This component is used as a placeholder for links that do not navigate anywhere, +// but with the same styling as the Link component. +export const FakeLink = forwardRef>( + (props, ref) => { + const { + dataTestId, + isConnectWallet, + label, + size = "small", + onClick, + } = props; + const { disconnectWallet } = useCardano(); + + const fontSize = FONT_SIZE[size]; + + return ( + { + e.preventDefault(); + if (!isConnectWallet) disconnectWallet(); + if (onClick) onClick(e); + }} + ref={ref} + sx={{ + cursor: "pointer", + fontSize, + fontWeight: 500, + color: "textBlack", + }} + > + {label} + + ); + }, +); diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx index b6a0ae992..c06b9c1c6 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx @@ -15,7 +15,7 @@ import { useModal } from "@/context"; type BaseProps = { label: string; - text?: string | number; + text?: React.ReactNode; dataTestId?: string; isSliderCard?: boolean; tooltipProps?: Omit; @@ -108,7 +108,7 @@ export const GovernanceActionCardElement = ({ ...(isSemiTransparent && { opacity: 0.75 }), }} > - {isMarkdown ? removeMarkdown(text) : text} + {typeof text === "string" && isMarkdown ? removeMarkdown(text) : text} ); diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx index 2d7524e4f..cd844d375 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx @@ -1,8 +1,8 @@ -import { useMemo, useState } from "react"; +import { useMemo, useState, Fragment } from "react"; import { Box, Tabs, Tab, styled, Skeleton } from "@mui/material"; import { useLocation } from "react-router-dom"; -import { CopyButton, ExternalModalButton, Typography } from "@atoms"; +import { CopyButton, ExternalModalButton, Tooltip, Typography } from "@atoms"; import { GovernanceActionCardElement, GovernanceActionDetailsCardLinks, @@ -76,6 +76,7 @@ export const GovernanceActionDetailsCardData = ({ isValidating, proposal: { abstract, + authors, createdDate, createdEpochNo, details, @@ -365,6 +366,35 @@ export const GovernanceActionDetailsCardData = ({ /> )} + ( + + + {author.name} + + {idx < arr.length - 1 && } + + )) + } + textVariant="longText" + dataTestId="authors" + /> diff --git a/govtool/frontend/src/components/organisms/TopNav.tsx b/govtool/frontend/src/components/organisms/TopNav.tsx index 2d3190884..533fe60d7 100644 --- a/govtool/frontend/src/components/organisms/TopNav.tsx +++ b/govtool/frontend/src/components/organisms/TopNav.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState, FC } from "react"; import { NavLink, useNavigate } from "react-router-dom"; import { AppBar, Box, Grid, IconButton, Menu, MenuItem } from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; -import { Button, Link } from "@atoms"; +import { Button, Link, FakeLink } from "@atoms"; import { ICONS, IMAGES, PATHS, NAV_ITEMS, NavMenuItem } from "@consts"; import { useCardano, useFeatureFlag, useModal } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; @@ -48,6 +48,10 @@ export const TopNav = ({ isConnectButton = true }) => { openModal({ type: "chooseWallet" }); }; + const closeDrawer = () => { + setIsDrawerOpen(false); + }; + const renderDesktopNav = () => (