diff --git a/CHANGELOG.md b/CHANGELOG.md index 90954bacd..6303146a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,19 @@ As a minor extension, we also keep a semantic version for the `UNRELEASED` changes. ## [Unreleased] +- 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) - i18next library added to FE [Issue 80](https://github.com/IntersectMBO/govtool/issues/80) +### Added +- Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) + ### Fixed +- Fix drep type detection when changing metadata [Issue 333](https://github.com/IntersectMBO/govtool/issues/333) +- Fix make button disble when wallet tries connect [Issue 265](https://github.com/IntersectMBO/govtool/issues/265) - Fix drep voting power calculation [Issue 231](https://github.com/IntersectMBO/govtool/issues/231) - Fix proposal/list and network/metrics bug that appeared when noone has delegated their funds either to drep_always_abstain or drep_always_no_confidence [Issue 231](https://github.com/IntersectMBO/govtool/issues/231) - Fix GA details [Issue 272](https://github.com/IntersectMBO/govtool/issues/272) @@ -23,9 +30,11 @@ changes. - Fixed vote calculation problems related to NoConfidence DRep [Issue 59](https://github.com/IntersectMBO/govtool/issues/59) - Fixed ada-holder/get-current-delegation error when delegated to NoConfidence or AlwaysAbstain dreps. [Issue 82](https://github.com/IntersectMBO/govtool/issues/82) - Fixed deployment scripts to address [Issue 171](https://github.com/IntersectMBO/govtool/issues/171). -- Fixed get drep voting power incorrectly executed endpoint [Issue 280](https://github.com/IntersectMBO/govtool/issues/280) +- Fixed get drep voting power incorrectly executed endpoint [Issue 280](https://github.com/IntersectMBO/govtool/issues/280). +- Fixed CSP settings to allow error reports with Sentry [Issue 291](https://github.com/IntersectMBO/govtool/issues/291). ### Changed +- `isRegistered` and `wasRegistered` fields in the drep/info endpoint changed to `isRegisteredAsDRep` and `wasRegisteredAsDRep` respectively [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Update Cardano-Serialization-Lib to 12.0.0-alpha.16 [Issue 156](https://github.com/IntersectMBO/govtool/issues/156) - Changed and improved working conventions docs, PR template and codeowners file, addressing [Issue 88](https://github.com/IntersectMBO/govtool/issues/88). - Changed Node version from 8.7.1-pre to 8.8.0-pre and DbSync version from sancho-2-3-0 to sancho-4-0-0-fix-config, addressing also [Issue 181](https://github.com/IntersectMBO/govtool/issues/181). @@ -33,6 +42,8 @@ changes. - Renamed project from VVA to GovTool [Issue 97](https://github.com/IntersectMBO/govtool/issues/97). - (`docs/update-working-conventions`) Addressing [Issue 25](https://github.com/IntersectMBO/govtool/issues/25) changed working conventions documentation to improve intended flows. - Adjusted Nix configuration to meet projects needs [Issue 187](https://github.com/IntersectMBO/govtool/issues/187). +- Integrated OAuth to securely notify about deployment status in Slack [Issue 194](https://github.com/IntersectMBO/govtool/issues/194). +- Streamlined the application build and deployment process, thereby accelerating continuous delivery (CD) and reducing the resource burden [Issue 246](https://github.com/IntersectMBO/govtool/issues/246). ### Removed - diff --git a/govtool/backend/sql/get-drep-info.sql b/govtool/backend/sql/get-drep-info.sql index 341dbabbc..de5048afb 100644 --- a/govtool/backend/sql/get-drep-info.sql +++ b/govtool/backend/sql/get-drep-info.sql @@ -1,28 +1,86 @@ WITH DRepId AS ( - SELECT decode(?, 'hex') as raw -), IsRegistered AS ( - SELECT (deposit>0) as value, - deposit as deposit - FROM drep_registration - JOIN drep_hash - ON drep_hash.id = drep_registration.drep_hash_id - CROSS JOIN DRepId - WHERE drep_hash.raw = DRepId.raw - and deposit is not null - ORDER BY drep_registration.tx_id DESC + SELECT + decode(?, 'hex') AS raw +), +LatestRegistrationEntry AS ( + SELECT + drep_registration.voting_anchor_id AS voting_anchor_id, + deposit AS deposit + FROM + drep_registration + CROSS JOIN DrepId + JOIN drep_hash ON drep_hash.id = drep_registration.drep_hash_id + WHERE + drep_hash.raw = DRepId.raw + ORDER BY + drep_registration.tx_id DESC LIMIT 1 -), WasRegistered AS ( - select (EXISTS ( - SELECT * - FROM drep_registration - JOIN drep_hash - ON drep_hash.id = drep_registration.drep_hash_id - CROSS JOIN DRepId - WHERE drep_hash.raw = DRepId.raw - and drep_registration.deposit > 0 - )) as value +), +IsRegisteredAsDRep AS ( + SELECT + (LatestRegistrationEntry.deposit is null or LatestRegistrationEntry.deposit > 0) + AND LatestRegistrationEntry.voting_anchor_id IS NOT NULL AS value + FROM + LatestRegistrationEntry +), +IsRegisteredAsSoleVoter AS ( + SELECT + (LatestRegistrationEntry.deposit is null or LatestRegistrationEntry.deposit > 0) + AND LatestRegistrationEntry.voting_anchor_id IS NULL AS value + FROM + LatestRegistrationEntry +), +CurrentDeposit AS ( + SELECT + GREATEST(drep_registration.deposit, 0) AS value +FROM + drep_registration + join drep_hash + on drep_hash.id = drep_registration.drep_hash_id + cross join DRepId + + WHERE + drep_registration.deposit IS NOT NULL + and drep_hash.raw = DRepId.raw + ORDER BY + drep_registration.tx_id DESC + LIMIT 1 +), +WasRegisteredAsDRep AS ( + SELECT + (EXISTS ( + SELECT + * + FROM + drep_registration + JOIN drep_hash ON drep_hash.id = drep_registration.drep_hash_id + CROSS JOIN DRepId + WHERE + drep_hash.raw = DRepId.raw + AND drep_registration.voting_anchor_id IS NOT NULL)) AS value +), +WasRegisteredAsSoleVoter AS ( + SELECT + (EXISTS ( + SELECT + * + FROM + drep_registration + JOIN drep_hash ON drep_hash.id = drep_registration.drep_hash_id + CROSS JOIN DRepId + WHERE + drep_hash.raw = DRepId.raw + AND drep_registration.voting_anchor_id IS NULL)) AS value ) -SELECT IsRegistered.value, WasRegistered.value, IsRegistered.deposit -FROM WasRegistered -LEFT JOIN IsRegistered -ON 1=1 \ No newline at end of file +SELECT + IsRegisteredAsDRep.value, + WasRegisteredAsDRep.value, + IsRegisteredAsSoleVoter.value, + WasRegisteredAsSoleVoter.value, + CurrentDeposit.value +FROM + IsRegisteredAsDRep + CROSS JOIN IsRegisteredAsSoleVoter + CROSS JOIN WasRegisteredAsDRep + CROSS JOIN WasRegisteredAsSoleVoter + CROSS JOIN CurrentDeposit diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index ed0b5f193..30557f6c6 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -153,8 +153,10 @@ drepInfo (unHexText -> dRepId) = do CacheEnv {dRepInfoCache} <- asks vvaCache Types.DRepInfo {..} <- cacheRequest dRepInfoCache dRepId $ DRep.getDRepInfo dRepId return $ DRepInfoResponse - { dRepInfoResponseIsRegistered = dRepInfoIsRegistered - , dRepInfoResponseWasRegistered = dRepInfoWasRegistered + { dRepInfoResponseIsRegisteredAsDRep = dRepInfoIsRegisteredAsDRep + , dRepInfoResponseWasRegisteredAsDRep = dRepInfoWasRegisteredAsDRep + , dRepInfoResponseIsRegisteredAsSoleVoter = dRepInfoIsRegisteredAsSoleVoter + , dRepInfoResponseWasRegisteredAsSoleVoter = dRepInfoWasRegisteredAsSoleVoter , dRepInfoResponseDeposit = dRepInfoDeposit } diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 208e2b4f4..a3c29fc57 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -375,8 +375,10 @@ instance ToSchema VoteResponse where ?~ toJSON exampleVoteResponse data DRepInfoResponse = DRepInfoResponse - { dRepInfoResponseIsRegistered :: Bool - , dRepInfoResponseWasRegistered :: Bool + { dRepInfoResponseIsRegisteredAsDRep :: Bool + , dRepInfoResponseWasRegisteredAsDRep :: Bool + , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool + , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool , dRepInfoResponseDeposit :: Maybe Integer } deriving (Generic, Show) @@ -384,8 +386,10 @@ deriveJSON (jsonOptions "dRepInfoResponse") ''DRepInfoResponse exampleDRepInfoResponse :: Text exampleDRepInfoResponse = - "{\"isRegistered\": false," - <> "\"wasRegistered\": true," + "{\"isRegisteredAsDRep\": false," + <> "\"wasRegisteredAsDRep\": true," + <> "\"isRegisteredAsSoleVoter\": true," + <> "\"wasRegisteredAsSoleVoter\": true," <> "\"deposit\": 2000000}" instance ToSchema DRepInfoResponse where diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 0661f0f82..4a29872ea 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -99,10 +99,12 @@ getDRepInfo getDRepInfo drepId = withPool $ \conn -> do result <- liftIO $ SQL.query conn getDRepInfoSql (SQL.Only drepId) case result of - [(isRegistered, wasRegistered, deposit)] -> + [(isRegisteredAsDRep, wasRegisteredAsDRep, isRegisteredAsSoleVoter, wasRegisteredAsSoleVoter, deposit)] -> return $ DRepInfo - { dRepInfoIsRegistered = fromMaybe False isRegistered - , dRepInfoWasRegistered = fromMaybe False wasRegistered + { dRepInfoIsRegisteredAsDRep = fromMaybe False isRegisteredAsDRep + , dRepInfoWasRegisteredAsDRep = fromMaybe False wasRegisteredAsDRep + , dRepInfoIsRegisteredAsSoleVoter = fromMaybe False isRegisteredAsSoleVoter + , dRepInfoWasRegisteredAsSoleVoter = fromMaybe False wasRegisteredAsSoleVoter , dRepInfoDeposit = deposit } - [] -> return $ DRepInfo False False Nothing \ No newline at end of file + [] -> return $ DRepInfo False False False False Nothing \ No newline at end of file diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index dc4438d18..4d5d2e0db 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -59,8 +59,10 @@ data Vote = Vote } data DRepInfo = DRepInfo - { dRepInfoIsRegistered :: Bool - , dRepInfoWasRegistered :: Bool + { dRepInfoIsRegisteredAsDRep :: Bool + , dRepInfoWasRegisteredAsDRep :: Bool + , dRepInfoIsRegisteredAsSoleVoter :: Bool + , dRepInfoWasRegisteredAsSoleVoter :: Bool , dRepInfoDeposit :: Maybe Integer } diff --git a/govtool/frontend/public/icons/ArrowLeftThin.svg b/govtool/frontend/public/icons/ArrowLeftThin.svg new file mode 100644 index 000000000..38c648693 --- /dev/null +++ b/govtool/frontend/public/icons/ArrowLeftThin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/govtool/frontend/public/images/GovActionsSoleVoter.png b/govtool/frontend/public/images/GovActionsSoleVoter.png new file mode 100644 index 000000000..69048a4c3 Binary files /dev/null and b/govtool/frontend/public/images/GovActionsSoleVoter.png differ diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index 91dab6356..13bc34a7d 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -21,6 +21,7 @@ import { UpdatedRepMetadata, GovernanceActionsCategory, DashboardGovernanceActionsCategory, + RetireAsSoleVoter, } from "@pages"; import { callAll, @@ -29,12 +30,13 @@ import { removeItemFromLocalStorage, } from "@utils"; import { SetupInterceptors } from "./services"; -import { useGetDRepInfo, useWalletConnectionListener } from "./hooks"; +import { useGetVoterInfo, useWalletConnectionListener } from "./hooks"; +import { RegisterAsSoleVoter } from "./pages/RegisterAsSoleVoter"; export default function App() { - const { enable, setDRep, setIsDrepLoading } = useCardano(); + const { enable, setVoter, setIsDrepLoading } = useCardano(); const navigate = useNavigate(); - const { data } = useGetDRepInfo(); + const { data } = useGetVoterInfo(); const { modal, openModal, modals } = useModal(); useWalletConnectionListener(); @@ -45,11 +47,11 @@ export default function App() { useEffect(() => { setIsDrepLoading(true); - setDRep(data); + setVoter(data); const timer = setTimeout(() => setIsDrepLoading(false), 1000); return () => clearTimeout(timer); - }, [data?.isRegistered]); + }, [data?.isRegisteredAsDRep, data?.isRegisteredAsSoleVoter]); const checkTheWalletIsActive = useCallback(() => { const hrefCondition = @@ -115,6 +117,11 @@ export default function App() { } /> } /> + } + /> + } /> } /> } /> } /> diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx index 897ec0ce8..ad707be93 100644 --- a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx @@ -13,7 +13,7 @@ import { correctAdaFormat } from "@utils"; import { Tooltip } from "@atoms"; export const VotingPowerChips = () => { - const { dRep, stakeKey, isDrepLoading } = useCardano(); + const { voter, stakeKey, isDrepLoading } = useCardano(); const { dRepVotingPower, isDRepVotingPowerLoading } = useGetDRepVotingPowerQuery(); const { votingPower, powerIsLoading } = @@ -33,7 +33,7 @@ export const VotingPowerChips = () => { alignItems="center" maxHeight={isMobile ? undefined : 48} > - {dRep?.isRegistered && ( + {voter?.isRegisteredAsDRep && ( { {t("votingPower")} )} - {(dRep?.isRegistered && isDRepVotingPowerLoading) || - (!dRep?.isRegistered && powerIsLoading) || + {(voter?.isRegisteredAsDRep && isDRepVotingPowerLoading) || + (!voter?.isRegisteredAsDRep && powerIsLoading) || isDrepLoading ? ( ) : ( @@ -67,7 +67,7 @@ export const VotingPowerChips = () => { sx={{ whiteSpace: "nowrap" }} > ₳{" "} - {dRep?.isRegistered + {voter?.isRegisteredAsDRep ? correctAdaFormat(dRepVotingPower) ?? 0 : correctAdaFormat(votingPower) ?? 0} diff --git a/govtool/frontend/src/components/molecules/ActionCard.tsx b/govtool/frontend/src/components/molecules/ActionCard.tsx index 63fc9eb03..3418c6471 100644 --- a/govtool/frontend/src/components/molecules/ActionCard.tsx +++ b/govtool/frontend/src/components/molecules/ActionCard.tsx @@ -94,7 +94,6 @@ export const ActionCard: FC = ({ ...props }) => { + ); + }, [isMobile]); + + const renderActionButton = useMemo(() => { + return ( + + {actionButtonText ?? t("continue")} + + ); + }, [isLoading, isMobile]); + + return ( + + {renderBackButton} + + {renderActionButton} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/CenteredBoxPageWrapper.tsx b/govtool/frontend/src/components/molecules/CenteredBoxPageWrapper.tsx new file mode 100644 index 000000000..dc9cbe0da --- /dev/null +++ b/govtool/frontend/src/components/molecules/CenteredBoxPageWrapper.tsx @@ -0,0 +1,108 @@ +import { FC, PropsWithChildren } from "react"; +import { Box, Link } from "@mui/material"; + +import { Background, Typography } from "@atoms"; +import { ICONS } from "@consts"; +import { DashboardTopNav } from "@organisms"; +import { useScreenDimension } from "@hooks"; +import { useNavigate } from "react-router-dom"; +import { theme } from "@/theme"; + +interface Props { + pageTitle: string; + backButtonText: string; + backButtonPath: string; + isVotingPowerHidden?: boolean; +} +export const CenteredBoxPageWrapper: FC> = ({ + pageTitle, + backButtonText, + backButtonPath, + isVotingPowerHidden, + children, +}) => { + const { isMobile, screenWidth, pagePadding } = useScreenDimension(); + const navigate = useNavigate(); + const { + palette: { boxShadow2 }, + } = theme; + + return ( + + + + + {isMobile && ( + + + {pageTitle} + + + )} + navigate(backButtonPath)} + > + arrow + + {backButtonText} + + + + + + {children} + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx index 3d3e9fc26..b654d61a3 100644 --- a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx +++ b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx @@ -2,7 +2,7 @@ import { Box, ButtonProps, Skeleton } from "@mui/material"; import { FC, ReactNode } from "react"; import { CopyButton, LoadingButton, Typography } from "@atoms"; -import { useScreenDimension } from "@hooks"; +import { useScreenDimension, useTranslation } from "@hooks"; import { theme } from "@/theme"; type DashboardActionCardProps = { @@ -18,9 +18,7 @@ type DashboardActionCardProps = { firstButtonIsLoading?: boolean; firstButtonLabel?: string; firstButtonVariant?: ButtonProps["variant"]; - imageHeight?: number; imageURL?: string; - imageWidth?: number; inProgress?: boolean; isLoading?: boolean; secondButtonAction?: () => void; @@ -33,6 +31,7 @@ type DashboardActionCardProps = { export const DashboardActionCard: FC = ({ ...props }) => { + const { t } = useTranslation(); const { cardId, cardTitle, @@ -62,17 +61,15 @@ export const DashboardActionCard: FC = ({ return ( {inProgress && !isLoading && ( = ({ }} > - In progress + {t("inProgress")} )} @@ -116,7 +113,7 @@ export const DashboardActionCard: FC = ({ ) : null} {inProgress && !isLoading ? ( - in progress + {t("inProgress")} ) : null} {description ? ( diff --git a/govtool/frontend/src/components/molecules/VoteActionForm.tsx b/govtool/frontend/src/components/molecules/VoteActionForm.tsx index 3c1645bd2..920021f24 100644 --- a/govtool/frontend/src/components/molecules/VoteActionForm.tsx +++ b/govtool/frontend/src/components/molecules/VoteActionForm.tsx @@ -25,7 +25,7 @@ export const VoteActionForm = ({ const [isContext, setIsContext] = useState(false); const { isMobile, screenWidth } = useScreenDimension(); const { openModal } = useModal(); - const { dRep } = useCardano(); + const { voter } = useCardano(); const { t } = useTranslation(); const { @@ -142,7 +142,7 @@ export const VoteActionForm = ({ /> - {dRep?.isRegistered && ( + {(voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && (