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 60d8abe0c..a1577ccff 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -5,6 +5,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";