From 2fcb90e4a9780106d121a2f76dcd2ae6a679fe6e Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Thu, 27 Nov 2025 17:28:44 +0200 Subject: [PATCH 01/13] add card state from provider --- .../GovernanceStatusRevamp.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx index fd3fea8d85..e28f7c49eb 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx @@ -4,6 +4,7 @@ import { GovernanceStatusRevampCard } from './GovernanceStatusRevampCard'; import { useNavigateTo } from '../../common/useNavigateTo'; import { useStrings } from '../../common/useStrings'; import { GOVERNANCE_STATUS, GovernanceStatusState } from '../../common/constants'; +import { useGovernance } from '../../module/GovernanceContextProvider'; const Container = styled(Box)(() => ({ display: 'flex', @@ -64,9 +65,35 @@ const TextContent = styled(Box)(() => ({ export const GovernanceStatusRevamp = () => { const navigateTo = useNavigateTo(); const strings = useStrings(); + + const { + governanceStatus, + governanceManager, + governanceVoteChanged, + createDrepDelegationTransaction, + walletAdaBalance, + triggerBuySellAdaDialog, + submitedTransactions, + governanceVote, + signDelegationTransaction, + selectedWallet, + networkId, + } = useGovernance(); + + console.log('governanceStatus', governanceStatus); // For now we keep it "idle" - const cardState: GovernanceStatusState = GOVERNANCE_STATUS.IDLE; + const getGovernanceStatusState = () => { + if (governanceStatus.status === 'none' && governanceStatus.drep === null) { + return GOVERNANCE_STATUS.IDLE; + } + if (governanceStatus.status === 'delegate' && governanceStatus.drep !== null) { + return GOVERNANCE_STATUS.DELEGATED; + } + return GOVERNANCE_STATUS.IDLE; // add loading here later + }; + const cardState: GovernanceStatusState = getGovernanceStatusState(); + console.log('@@@cardState', cardState); const onExploreMore = () => { navigateTo.selectRevampOptions(); }; From de1cf8fe6bdc8821beb95043537a36e9ba5afd61 Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Fri, 28 Nov 2025 14:10:32 +0200 Subject: [PATCH 02/13] fix cards hover state --- .../GovernanceStatusRevamp.tsx | 143 ++++++++---------- 1 file changed, 62 insertions(+), 81 deletions(-) diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx index e28f7c49eb..db76b39af2 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx @@ -2,9 +2,68 @@ import { styled } from '@mui/material/styles'; import { Box, Typography, Button } from '@mui/material'; import { GovernanceStatusRevampCard } from './GovernanceStatusRevampCard'; import { useNavigateTo } from '../../common/useNavigateTo'; -import { useStrings } from '../../common/useStrings'; -import { GOVERNANCE_STATUS, GovernanceStatusState } from '../../common/constants'; -import { useGovernance } from '../../module/GovernanceContextProvider'; +import { useStrings } from '../../common/hooks/useStrings'; +import { YOROI_DREP_ID } from '../../common/constants'; +import { useGovernanceDelegationToYoroiDrep } from '../../common/hooks/useGovernanceDelegationToYoroiDrep'; +import { useGovernanceStatusState } from '../../common/hooks/useGovernanceStatusState'; + +export const GovernanceStatusRevamp = () => { + const navigateTo = useNavigateTo(); + const strings = useStrings(); + + const { loadingUnsignTx, error, delegateToDrep } = useGovernanceDelegationToYoroiDrep(); + + const { governanceStatusState: cardState, governanceStatus } = useGovernanceStatusState(); + + console.log('governanceStatus', { governanceStatus, cardState }); + + const onExploreMore = () => { + navigateTo.selectRevampOptions(); + }; + + return ( + + + + {strings.delegationOptions} + + + {strings.chooseDelegationOption} + + + + {/* Optionally render error */} + {error != null && ( + + {error} + + )} + + + delegateToDrep(YOROI_DREP_ID)} + onDetailsClick={() => { + // later: open governance docs / modal + }} + btnLoading={loadingUnsignTx} + /> + + + + + {strings.exploreOtherDRepsOrAbstain} + + + {strings.browseAdditionalDelegation} + + + + + + ); +}; const Container = styled(Box)(() => ({ display: 'flex', @@ -61,81 +120,3 @@ const TextContent = styled(Box)(() => ({ gap: '4px', width: '580px', })); - -export const GovernanceStatusRevamp = () => { - const navigateTo = useNavigateTo(); - const strings = useStrings(); - - const { - governanceStatus, - governanceManager, - governanceVoteChanged, - createDrepDelegationTransaction, - walletAdaBalance, - triggerBuySellAdaDialog, - submitedTransactions, - governanceVote, - signDelegationTransaction, - selectedWallet, - networkId, - } = useGovernance(); - - console.log('governanceStatus', governanceStatus); - // For now we keep it "idle" - const getGovernanceStatusState = () => { - if (governanceStatus.status === 'none' && governanceStatus.drep === null) { - return GOVERNANCE_STATUS.IDLE; - } - if (governanceStatus.status === 'delegate' && governanceStatus.drep !== null) { - return GOVERNANCE_STATUS.DELEGATED; - } - return GOVERNANCE_STATUS.IDLE; // add loading here later - }; - const cardState: GovernanceStatusState = getGovernanceStatusState(); - - console.log('@@@cardState', cardState); - const onExploreMore = () => { - navigateTo.selectRevampOptions(); - }; - - return ( - - - - {strings.delegationOptions} - - - {strings.chooseDelegationOption} - - - - - { - // later: route to delegation flow - // console.log('Delegate clicked') - }} - onDetailsClick={() => { - // later: open governance docs / modal - // console.log('View governance details') - }} - /> - - - - - {strings.exploreOtherDRepsOrAbstain} - - - {strings.browseAdditionalDelegation} - - - - - - ); -}; From 90bf307cc02c525803d8c8190b33a2e32be06b40 Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Fri, 28 Nov 2025 14:10:50 +0200 Subject: [PATCH 03/13] add delegate impl on status page --- .../governace/common/ChooseDRepModal.tsx | 2 +- .../useGovernanceDelegationToYoroiDrep.tsx | 105 ++++++++++++++++++ .../common/hooks/useGovernanceStatusState.tsx | 35 ++++++ .../common/{ => hooks}/useStrings.tsx | 2 +- .../DelagationForm/DelagationForm.tsx | 2 +- .../GovernanceStatusRevamp/DRepOptions.tsx | 18 ++- .../DrepOptionsCard.tsx | 90 +++++++++++---- .../GovernanceStatusRevampCard.tsx | 60 +++++----- .../GovernanceStatusSelection.tsx | 2 +- .../app/UI/pages/Governance-Revamp/layout.tsx | 2 +- .../GovernanceTransactionSubmittedPage.tsx | 2 +- 11 files changed, 260 insertions(+), 60 deletions(-) create mode 100644 packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceDelegationToYoroiDrep.tsx create mode 100644 packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceStatusState.tsx rename packages/yoroi-extension/app/UI/features/governace/common/{ => hooks}/useStrings.tsx (99%) diff --git a/packages/yoroi-extension/app/UI/features/governace/common/ChooseDRepModal.tsx b/packages/yoroi-extension/app/UI/features/governace/common/ChooseDRepModal.tsx index 59b8bfb70e..8c91b4e4eb 100644 --- a/packages/yoroi-extension/app/UI/features/governace/common/ChooseDRepModal.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/common/ChooseDRepModal.tsx @@ -6,7 +6,7 @@ import { dRepToMaybeCredentialHex } from '../../../../api/ada/lib/cardanoCrypto/ import { TextInput } from '../../../components/Input/TextInput'; import { useModal } from '../../../components/modals/ModalContext'; import { useGovernance } from '../module/GovernanceContextProvider'; -import { useStrings } from './useStrings'; +import { useStrings } from './hooks/useStrings'; type ChooseDRepModallProps = { onSubmit?: (drepId: string, drepCredential: string) => void; diff --git a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceDelegationToYoroiDrep.tsx b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceDelegationToYoroiDrep.tsx new file mode 100644 index 0000000000..4d9a879c6c --- /dev/null +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceDelegationToYoroiDrep.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; +import { NotEnoughMoneyToSendError } from '../../../../../api/common/errors'; +import { dRepToMaybeCredentialHex } from '../../../../../api/ada/lib/cardanoCrypto/utils'; +import { TransactionResult } from '../../../transaction-review/common/types'; +import { useGovernance } from '../../module/GovernanceContextProvider'; +import { useTxReviewModal } from '../../../transaction-review/module/ReviewTxProvider'; +import { useStrings } from './useStrings'; +import { Vote } from '../../module/state'; + +type UseGovernanceDelegationResult = { + loadingUnsignTx: boolean; + error: string | null; + setError: (value: string | null) => void; + delegateToDrep: (drepID: string) => Promise; +}; + +export const useGovernanceDelegationToYoroiDrep = (): UseGovernanceDelegationResult => { + const [error, setError] = React.useState(null); + const [loadingUnsignTx, setLoadingUnsignTx] = React.useState(false); + + const strings = useStrings(); + + const { governanceVoteChanged, createDrepDelegationTransaction, signDelegationTransaction, selectedWallet } = useGovernance(); + + const { openTxReviewModal, startLoadingTxReview, stopLoadingTxReview, changePasswordInputValue, showTxResultModal, setDrepId } = + useTxReviewModal(); + + const signGovernanceTx = React.useCallback( + async (password: string) => { + try { + startLoadingTxReview(); + await signDelegationTransaction({ + password, + wallet: selectedWallet, + dialog: null, + }); + stopLoadingTxReview(); + changePasswordInputValue({ type: 'changeInputValue', passswordInput: '' }); + showTxResultModal(TransactionResult.SUCCESS); + } catch (error) { + console.warn('[createDrepDelegationTransaction,signDelegationTransaction]', error); + stopLoadingTxReview(); + showTxResultModal(TransactionResult.FAIL); + } + }, + [ + startLoadingTxReview, + stopLoadingTxReview, + signDelegationTransaction, + selectedWallet, + changePasswordInputValue, + showTxResultModal, + ] + ); + + const createUnsignTx = React.useCallback( + async (dRepCredentialHex: string | null) => { + try { + setLoadingUnsignTx(true); + const txSignRequest: any = await createDrepDelegationTransaction(dRepCredentialHex || ''); + + openTxReviewModal({ + modalView: 'transactionReview', + unsignedTx: txSignRequest.signTxRequest.unsignedTx, + submitTx: (password: string) => { + void signGovernanceTx(password); + }, + operations: { + kind: 'delegate vote', + }, + }); + + setError(null); + } catch (e) { + if (e instanceof NotEnoughMoneyToSendError) { + setError(strings.notEnoughMoneyToSendError); + } else { + setError('Error trying to Vote. Please try again later'); + } + } finally { + setLoadingUnsignTx(false); + } + }, + [createDrepDelegationTransaction, openTxReviewModal, signGovernanceTx, strings] + ); + + const delegateToDrep = React.useCallback( + async (drepID: string) => { + const vote: Vote = { kind: 'delegate', drepID }; + const dRepCredentialHex: string | null = dRepToMaybeCredentialHex(drepID); + + governanceVoteChanged(vote); + setDrepId({ drepID }); + await createUnsignTx(dRepCredentialHex); + }, + [governanceVoteChanged, setDrepId, createUnsignTx] + ); + + return { + loadingUnsignTx, + error, + setError, + delegateToDrep, + }; +}; diff --git a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceStatusState.tsx b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceStatusState.tsx new file mode 100644 index 0000000000..c0c0d57007 --- /dev/null +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useGovernanceStatusState.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {useGovernance} from '../../module/GovernanceContextProvider'; +import {GOVERNANCE_STATUS, GovernanceStatusState} from '../../common/constants'; + +type UseGovernanceStatusStateResult = { + governanceStatusState: GovernanceStatusState; + governanceStatus: ReturnType['governanceStatus']; + isPendingDrepDelegationTx: boolean; +}; + +export const useGovernanceStatusState = (): UseGovernanceStatusStateResult => { + const {governanceStatus, submitedTransactions} = useGovernance(); + + const isPendingDrepDelegationTx = + submitedTransactions.length > 0 && submitedTransactions[0]?.isDrepDelegation === true; + + const governanceStatusState: GovernanceStatusState = React.useMemo(() => { + if (governanceStatus.status === 'none' && governanceStatus.drep === null) { + return GOVERNANCE_STATUS.IDLE; + } + if (governanceStatus.status === 'delegate' && governanceStatus.drep !== null) { + return GOVERNANCE_STATUS.DELEGATED; + } + if (isPendingDrepDelegationTx) { + return GOVERNANCE_STATUS.DISABLED; + } + return GOVERNANCE_STATUS.IDLE; + }, [governanceStatus.status, governanceStatus.drep, isPendingDrepDelegationTx]); + + return { + governanceStatusState, + governanceStatus, + isPendingDrepDelegationTx, + }; +}; diff --git a/packages/yoroi-extension/app/UI/features/governace/common/useStrings.tsx b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx similarity index 99% rename from packages/yoroi-extension/app/UI/features/governace/common/useStrings.tsx rename to packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx index f2b83ed36d..f6b6a78d30 100644 --- a/packages/yoroi-extension/app/UI/features/governace/common/useStrings.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { defineMessages } from 'react-intl'; import { useIntl } from 'react-intl'; -import globalMessages from '../../../../i18n/global-messages'; +import globalMessages from '../../../../../i18n/global-messages'; export const messages = Object.freeze( defineMessages({ diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/DelagationForm/DelagationForm.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/DelagationForm/DelagationForm.tsx index fd035f2dd6..1f51f8b256 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/DelagationForm/DelagationForm.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/DelagationForm/DelagationForm.tsx @@ -11,7 +11,7 @@ import { PasswordInput } from '../../../../components/Input/PasswordInput'; import { DREP_ALWAYS_ABSTAIN, DREP_ALWAYS_NO_CONFIDENCE } from '../../common/constants'; import { dRepNormalize, dRepToPreCip129 } from '../../../../../api/ada/lib/cardanoCrypto/utils'; import { useNavigateTo } from '../../common/useNavigateTo'; -import { useStrings } from '../../common/useStrings'; +import { useStrings } from '../../common/hooks/useStrings'; import { useGovernance } from '../../module/GovernanceContextProvider'; import { mapStatus } from '../SelectGovernanceStatus/GovernanceStatusSelection'; import { maybe } from '../../../../../coreUtils'; diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx index 463878b96d..d54bbe3443 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx @@ -2,9 +2,12 @@ import { Box, Typography, Stack } from '@mui/material'; import { styled } from '@mui/system'; import { Icon } from '../../../../components'; import { useNavigateTo } from '../../common/useNavigateTo'; -import { useStrings } from '../../common/useStrings'; +import { useStrings } from '../../common/hooks/useStrings'; import { useTheme } from '@mui/material/styles'; import { DrepOptionsCard } from './DrepOptionsCard'; +import { useGovernance } from '../../module/GovernanceContextProvider'; +import { useGovernanceDelegationToYoroiDrep } from '../../common/hooks/useGovernanceDelegationToYoroiDrep'; +import { useGovernanceStatusState } from '../../common/hooks/useGovernanceStatusState'; interface DRepOptionsScreenProps {} @@ -13,6 +16,14 @@ export const DRepOptions: React.FC = () => { const strings = useStrings(); const theme: any = useTheme(); + const { submitedTransactions } = useGovernance(); + const { loadingUnsignTx, error, delegateToDrep } = useGovernanceDelegationToYoroiDrep(); + const isPendindDrepDelegationTx = submitedTransactions.length > 0 && submitedTransactions[0]?.isDrepDelegation === true; + + const { governanceStatusState: cardState, governanceStatus } = useGovernanceStatusState(); + + console.log('governanceStatus', { governanceStatus, cardState }); + const onBack = () => { navigateTo.selectRevampStatus(); }; @@ -27,6 +38,7 @@ export const DRepOptions: React.FC = () => { icon: , onAction: () => console.log('Delegate to Yoroi'), onViewDetails: () => console.log('View Yoroi details'), + status: governanceStatus, }, { key: 'others', @@ -36,6 +48,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('Browse DReps'), + status: governanceStatus, }, { key: 'abstain', @@ -45,6 +58,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('Abstain'), + status: governanceStatus, }, { key: 'noConfidence', @@ -54,6 +68,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('No Confidence'), + status: governanceStatus, }, ]; @@ -84,6 +99,7 @@ export const DRepOptions: React.FC = () => { icon={option.icon} onAction={option.onAction} onViewDetails={option.onViewDetails} + status={option.status} /> ))} diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx index 7f79f10fa7..af2548ba7d 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx @@ -1,6 +1,7 @@ import { Box, Typography, Button, Stack, Link } from '@mui/material'; import { styled } from '@mui/system'; -import { useStrings } from '../../common/useStrings'; +import { useStrings } from '../../common/hooks/useStrings'; +import { GovernanceStatusState } from '../../common/constants'; interface ActionCardProps { title: string; @@ -10,6 +11,7 @@ interface ActionCardProps { icon?: React.ReactNode; onAction: () => void; onViewDetails?: () => void; + status: GovernanceStatusState; } export const DrepOptionsCard: React.FC = ({ @@ -20,10 +22,11 @@ export const DrepOptionsCard: React.FC = ({ icon, onAction, onViewDetails, + status, }) => { const strings = useStrings(); return ( - + {icon} @@ -62,28 +65,73 @@ export const DrepOptionsCard: React.FC = ({ ); }; -const ActionCardContainer = styled(Box, { - shouldForwardProp: prop => prop !== 'variant', -})<{ variant: 'primary' | 'outlined' }>(({ theme, variant }) => ({ - boxSizing: 'border-box', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'flex-start', - padding: '16px', - gap: variant === 'primary' ? '16px' : '12px', +type ActionCardVariant = 'primary' | 'outlined'; - width: '294px', - height: '320px', +interface ActionCardContainerProps { + variant: ActionCardVariant; + status: GovernanceStatusState; +} - background: variant === 'primary' ? theme.palette.ds.bg_gradient_2 : theme.palette.ds.bg_color_max, - border: variant === 'outlined' ? `1px solid ${theme.palette.ds.gray_200}` : 'none', - borderRadius: '8px', +export const ActionCardContainer = styled(Box, { + shouldForwardProp: prop => prop !== 'variant' && prop !== 'status', +})(({ theme, variant, status }) => { + console.log('status', { variant, status }); + const base: React.CSSProperties = { + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'flex-start', + padding: '16px', + gap: variant === 'primary' ? '16px' : '12px', - flex: 'none', - alignSelf: 'stretch', - flexGrow: 1, -})); + width: '294px', + height: '320px', + + borderRadius: '8px', + flex: 'none', + alignSelf: 'stretch', + flexGrow: 1, + + cursor: status === 'disabled' ? 'default' : 'pointer', + opacity: status === 'disabled' ? 0.6 : 1, + }; + + // 1. Disabled state (regardless of variant) + if (status === 'disabled') { + return { + ...base, + background: theme.palette.ds.gray_100, + border: `1px solid ${theme.palette.ds.gray_200}`, + pointerEvents: 'none', + }; + } + if (variant === 'primary') { + return { + ...base, + backgroundImage: theme.palette.ds.bg_gradient_1, + '&:hover': { + backgroundImage: theme.palette.ds.bg_gradient_2, + }, + }; + } + + if (status === 'delegated') { + return { + ...base, + background: theme.palette.ds.bg_color_max, + border: `1px solid ${theme.palette.ds.primary_500}`, + }; + } + return { + ...base, + background: theme.palette.ds.bg_color_max, + border: `1px solid ${theme.palette.ds.gray_200}`, + '&:hover': { + borderColor: theme.palette.ds.primary_500, + }, + }; +}); const CardWrapper = styled(Box)(() => ({ display: 'flex', diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx index 9a542864aa..12b0205ee6 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx @@ -1,29 +1,28 @@ // features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusCard.tsx import React from 'react'; import { styled, useTheme } from '@mui/material/styles'; -import { Box, Typography, Button, IconButton, Link, Stack } from '@mui/material'; +import { Box, Typography, IconButton, Link, Stack } from '@mui/material'; import { Icon } from '../../../../components'; -import { useStrings } from '../../common/useStrings'; +import { useStrings } from '../../common/hooks/useStrings'; import { GovernanceStatusState } from '../../common/constants'; +import { LoadingButton } from '@mui/lab'; interface GovernanceStatusCardProps { state: GovernanceStatusState; drepId: string; votingPowerLabel?: string; votingPowerValue?: string; - delegatedAmountLabel?: string; - delegatedAmountValue?: string; onDelegateClick?: () => void; onDetailsClick?: () => void; + btnLoading?: boolean; } export const GovernanceStatusRevampCard: React.FC = ({ state, drepId, - delegatedAmountLabel = 'Delegated Amount', - delegatedAmountValue, onDelegateClick, onDetailsClick, + btnLoading, }) => { const isDisabled = state === 'disabled'; const isDelegated = state === 'delegated'; @@ -51,6 +50,7 @@ export const GovernanceStatusRevampCard: React.FC = ( const statusVariant: 'active' | 'delegated' | 'disabled' = state === 'delegated' ? 'delegated' : state === 'disabled' ? 'disabled' : 'active'; + console.log('GovernanceStatusRevampCard render', { state, statusVariant }); const primaryButtonLabel = isDelegated ? strings.changeToDrep : strings.delegateLabel; return ( @@ -96,7 +96,7 @@ export const GovernanceStatusRevampCard: React.FC = ( - + {/* {isDelegated && ( @@ -107,14 +107,23 @@ export const GovernanceStatusRevampCard: React.FC = ( {delegatedAmountValue} - )} + )} */} - {/* @ts-ignore */} - - {primaryButtonLabel} - + {statusVariant === 'active' && ( + + {primaryButtonLabel} + + )} @@ -154,17 +163,13 @@ const Root = styled(Box, { }; } - if (state === 'hover') { - return { - ...base, - backgroundImage: theme.palette.ds.bg_gradient_2, - }; - } - // idle return { ...base, backgroundImage: theme.palette.ds.bg_gradient_1, + '&:hover': { + backgroundImage: theme.palette.ds.bg_gradient_2, + }, }; }); @@ -245,9 +250,9 @@ const StatusBadge = styled(Box)<{ variant: 'active' | 'delegated' | 'disabled' } height: '30px', borderRadius: '1200px', background: - variant === 'active' + variant === 'active' || variant === 'delegated' ? theme.palette.ds.secondary_600 - : variant === 'delegated' + : variant === 'disabled' ? theme.palette.ds.gray_600 : theme.palette.ds.gray_400, })); @@ -259,14 +264,5 @@ const CtaSet = styled(Box)(() => ({ padding: '0px', gap: '8px', width: '580px', - height: '72px', -})); - -const PrimaryButton = styled(Button)<{ disabledVisual?: boolean }>(({ disabledVisual, theme }: any) => ({ - background: disabledVisual ? theme.palette.ds.gray_600 : theme.palette.ds.primary_500, - width: '100%', - '&:hover': { - background: disabledVisual ? theme.palette.ds.gray_600 : theme.palette.ds.primary_500, - opacity: disabledVisual ? 1 : 0.9, - }, -})); + height: 'auto', +})); \ No newline at end of file diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/SelectGovernanceStatus/GovernanceStatusSelection.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/SelectGovernanceStatus/GovernanceStatusSelection.tsx index c835837c8c..cb897cc569 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/SelectGovernanceStatus/GovernanceStatusSelection.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/SelectGovernanceStatus/GovernanceStatusSelection.tsx @@ -19,7 +19,7 @@ import { YOROI_VOTING_RECORD_LINK, } from '../../common/constants'; import { DRepIlustration } from '../../common/ilustrations/DRepIlustration'; -import { useStrings } from '../../common/useStrings'; +import { useStrings } from '../../common/hooks/useStrings'; import { useGovernance } from '../../module/GovernanceContextProvider'; import { Vote } from '../../module/state'; import { networks } from '../../../../../api/ada/lib/storage/database/prepackaged/networks'; diff --git a/packages/yoroi-extension/app/UI/pages/Governance-Revamp/layout.tsx b/packages/yoroi-extension/app/UI/pages/Governance-Revamp/layout.tsx index f478d21ec3..76a7dd9660 100644 --- a/packages/yoroi-extension/app/UI/pages/Governance-Revamp/layout.tsx +++ b/packages/yoroi-extension/app/UI/pages/Governance-Revamp/layout.tsx @@ -5,7 +5,7 @@ import NavBarTitle from '../../../components/topbar/NavBarTitle'; import { GovernanceProvider as GovernanceExternalPackageProvider } from '@yoroi/staking'; import { useGovernance } from '../../features/governace/module/GovernanceContextProvider'; import { Link, Stack } from '@mui/material'; -import { useStrings } from '../../features/governace/common/useStrings'; +import { useStrings } from '../../features/governace/common/hooks/useStrings'; type Props = { stores: any; diff --git a/packages/yoroi-extension/app/UI/pages/Governance/GovernanceTransactionSubmittedPage.tsx b/packages/yoroi-extension/app/UI/pages/Governance/GovernanceTransactionSubmittedPage.tsx index 299a7dae56..22ba489cdf 100644 --- a/packages/yoroi-extension/app/UI/pages/Governance/GovernanceTransactionSubmittedPage.tsx +++ b/packages/yoroi-extension/app/UI/pages/Governance/GovernanceTransactionSubmittedPage.tsx @@ -1,7 +1,7 @@ import { useNavigate } from 'react-router'; import { ROUTES } from '../../../routes-config'; import { TransactionSubmitted } from '../../components'; -import { useStrings } from '../../features/governace/common/useStrings'; +import { useStrings } from '../../features/governace/common/hooks/useStrings'; import GovernanceLayout from './layout'; type Props = { From 28af8689f61efb87d9271d002d75cb385f3cf9bc Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Fri, 28 Nov 2025 15:26:59 +0200 Subject: [PATCH 04/13] fix card UI --- .../governace/common/hooks/useStrings.tsx | 4 +- .../GovernanceStatusRevamp/DRepOptions.tsx | 15 +- .../DrepOptionsCard.tsx | 131 ++++++++++++++++-- .../GovernanceStatusRevamp.tsx | 7 - .../yoroi-extension/app/utils/formatters.js | 2 +- 5 files changed, 133 insertions(+), 26 deletions(-) diff --git a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx index f6b6a78d30..1dfc4e255e 100644 --- a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx @@ -226,8 +226,8 @@ export const messages = Object.freeze( '!!!You are designating someone else to cast your vote on your behalf for all proposals now and in the future.', }, chooseAbstain: { - id: 'governance.chooseAbstain', - defaultMessage: '!!!Choose to abstain from voting while still participating in the governance process and earning rewards.', + id: 'governance.abstainInfo', + defaultMessage: '!!!You are choosing not to cast a vote on all proposals now and in the future.', }, chooseNoConfidence: { id: 'governance.chooseNoConfidence', diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx index d54bbe3443..4681dd011c 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx @@ -8,6 +8,7 @@ import { DrepOptionsCard } from './DrepOptionsCard'; import { useGovernance } from '../../module/GovernanceContextProvider'; import { useGovernanceDelegationToYoroiDrep } from '../../common/hooks/useGovernanceDelegationToYoroiDrep'; import { useGovernanceStatusState } from '../../common/hooks/useGovernanceStatusState'; +import { YOROI_DREP_ID } from '../../common/constants'; interface DRepOptionsScreenProps {} @@ -21,9 +22,6 @@ export const DRepOptions: React.FC = () => { const isPendindDrepDelegationTx = submitedTransactions.length > 0 && submitedTransactions[0]?.isDrepDelegation === true; const { governanceStatusState: cardState, governanceStatus } = useGovernanceStatusState(); - - console.log('governanceStatus', { governanceStatus, cardState }); - const onBack = () => { navigateTo.selectRevampStatus(); }; @@ -36,9 +34,9 @@ export const DRepOptions: React.FC = () => { buttonText: strings.delegateLabel, variant: 'primary' as const, icon: , - onAction: () => console.log('Delegate to Yoroi'), + onAction: () => delegateToDrep(YOROI_DREP_ID), onViewDetails: () => console.log('View Yoroi details'), - status: governanceStatus, + status: cardState, }, { key: 'others', @@ -48,7 +46,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('Browse DReps'), - status: governanceStatus, + status: cardState, }, { key: 'abstain', @@ -58,7 +56,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('Abstain'), - status: governanceStatus, + status: cardState, }, { key: 'noConfidence', @@ -68,7 +66,7 @@ export const DRepOptions: React.FC = () => { variant: 'outlined' as const, icon: , onAction: () => console.log('No Confidence'), - status: governanceStatus, + status: cardState, }, ]; @@ -100,6 +98,7 @@ export const DRepOptions: React.FC = () => { onAction={option.onAction} onViewDetails={option.onViewDetails} status={option.status} + drepId={governanceStatus.drep ? governanceStatus.drep : YOROI_DREP_ID} /> ))} diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx index 7604860f2c..832d3d9e04 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx @@ -1,8 +1,11 @@ -import { Box, Typography, Button, Stack, Link } from '@mui/material'; +import { Box, Typography, Button, Stack, Link, IconButton } from '@mui/material'; import { styled } from '@mui/system'; import { useStrings } from '../../common/hooks/useStrings'; -import { GovernanceStatusState } from '../../common/constants'; +import { GOVERNANCE_STATUS, GovernanceStatusState } from '../../common/constants'; import { YOROI_VOTING_RECORD_LINK } from '../../common/constants'; +import { LoadingButton } from '@mui/lab'; +import { Icon } from '../../../../components'; +import { truncateFormatter } from '../../../../../utils/formatters'; interface ActionCardProps { title: string; @@ -13,6 +16,7 @@ interface ActionCardProps { onAction: () => void; onViewDetails?: () => void; status: GovernanceStatusState; + drepId?: string; } export const DrepOptionsCard: React.FC = ({ @@ -24,10 +28,18 @@ export const DrepOptionsCard: React.FC = ({ onAction, onViewDetails, status, - // @ts-ignore || it will be used later - key, + drepId, }) => { const strings = useStrings(); + console.log('DrepOptionsCard render', { title, status, variant, drepId }); + const handleCopy = () => { + try { + navigator.clipboard.writeText(drepId || ''); + } catch { + // no-op + } + }; + return ( @@ -39,13 +51,53 @@ export const DrepOptionsCard: React.FC = ({ {description} + {GOVERNANCE_STATUS.DELEGATED === status && ( + + + + + ID + + + + + + {truncateFormatter(drepId, 15)} + + + + + + + + + + + {strings.drepStatus} + + + + + + + {'Active'} + + + + + + )} + {variant === 'primary' ? ( - {/* @ts-ignore */} - + {status === GOVERNANCE_STATUS.IDLE && ( + // @ts-ignore + + {buttonText} + + )} + {onViewDetails && ( ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + padding: '0px', + gap: '12px', + width: '100%', +})); + +const ItemRow = styled(Box)(() => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: '0px', + gap: '24px', + width: '100%', +})); + +const LeftPart = styled(Box)(() => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'flex-start', + padding: '0px', + gap: '4px', + height: '24px', +})); + +const RightPart = styled(Box)(() => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + padding: '0px', + gap: '4px', + height: '24px', + flexShrink: 0, +})); + +const CopyIconButton = styled(IconButton)(() => ({ + width: '24px', + height: '24px', + padding: '0px', +})); + +const StatusBadge = styled(Box)<{ variant: 'active' | 'delegated' | 'disabled' }>(({ variant, theme }: any) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'flex-start', + padding: '4px 8px', + gap: '2px', + width: '58px', + height: '30px', + borderRadius: '1200px', + background: + variant === 'active' || variant === 'delegated' + ? theme.palette.ds.secondary_600 + : variant === 'disabled' + ? theme.palette.ds.gray_600 + : theme.palette.ds.gray_400, +})); + const CTASet = styled(Box)(() => ({ display: 'flex', justifyContent: 'center', width: '100%', + marginTop: '16px', })); diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx index db76b39af2..7cd5955997 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevamp.tsx @@ -10,13 +10,9 @@ import { useGovernanceStatusState } from '../../common/hooks/useGovernanceStatus export const GovernanceStatusRevamp = () => { const navigateTo = useNavigateTo(); const strings = useStrings(); - const { loadingUnsignTx, error, delegateToDrep } = useGovernanceDelegationToYoroiDrep(); - const { governanceStatusState: cardState, governanceStatus } = useGovernanceStatusState(); - console.log('governanceStatus', { governanceStatus, cardState }); - const onExploreMore = () => { navigateTo.selectRevampOptions(); }; @@ -44,9 +40,6 @@ export const GovernanceStatusRevamp = () => { state={cardState} drepId={governanceStatus.drep ? governanceStatus.drep : YOROI_DREP_ID} onDelegateClick={() => delegateToDrep(YOROI_DREP_ID)} - onDetailsClick={() => { - // later: open governance docs / modal - }} btnLoading={loadingUnsignTx} /> diff --git a/packages/yoroi-extension/app/utils/formatters.js b/packages/yoroi-extension/app/utils/formatters.js index feeb8d26f3..08984bfacc 100644 --- a/packages/yoroi-extension/app/utils/formatters.js +++ b/packages/yoroi-extension/app/utils/formatters.js @@ -53,7 +53,7 @@ export const formattedAmountToNaturalUnits: (string, number) => string = (amount return cleanedAmount === '' ? '0' : cleanedAmount; }; -function truncateFormatter(addr: string, cutoff: number): string { +export function truncateFormatter(addr: string, cutoff: number): string { const shortener = '...'; if (addr.length - shortener.length <= cutoff) { return addr; From bf61105f779576a093c1699a0c40544be4e1fbfa Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Mon, 1 Dec 2025 18:04:50 +0200 Subject: [PATCH 05/13] add all options delegation --- .../app/UI/common/helpers/formatters.ts | 7 + .../app/UI/components/icons/YoroiLogo.tsx | 8 +- .../useGovernanceDelegationToYoroiDrep.tsx | 91 ++++++- .../common/hooks/useIsGovernanceAllowed.ts | 21 ++ .../governace/common/hooks/useStrings.tsx | 21 ++ .../GovernanceStatusRevamp/DRepOptions.tsx | 51 ++-- .../DrepOptionsCard.tsx | 170 +++++++------ .../GovernanceStatusRevamp.tsx | 16 +- .../GovernanceStatusRevampCard.tsx | 226 +++++++++++------- .../NotAllowedInGovernance.tsx | 46 ++++ .../common/hooks/useStrings.ts | 20 ++ .../module/ReviewTxManager.tsx | 2 + .../ChooseDrepId/ChooseOtherDrepId.tsx | 94 ++++++++ .../app/i18n/locales/en-US.json | 12 +- 14 files changed, 599 insertions(+), 186 deletions(-) create mode 100644 packages/yoroi-extension/app/UI/common/helpers/formatters.ts create mode 100644 packages/yoroi-extension/app/UI/features/governace/common/hooks/useIsGovernanceAllowed.ts create mode 100644 packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/NotAllowedInGovernance.tsx create mode 100644 packages/yoroi-extension/app/UI/features/transaction-review/useCases/ChooseDrepId/ChooseOtherDrepId.tsx diff --git a/packages/yoroi-extension/app/UI/common/helpers/formatters.ts b/packages/yoroi-extension/app/UI/common/helpers/formatters.ts new file mode 100644 index 0000000000..9867ddd3ba --- /dev/null +++ b/packages/yoroi-extension/app/UI/common/helpers/formatters.ts @@ -0,0 +1,7 @@ +export function truncateFormatter(addr: string, cutoff: number): string { + const shortener = '...'; + if (addr.length - shortener.length <= cutoff) { + return addr; + } + return addr.substring(0, cutoff / 2) + shortener + addr.substring(addr.length - cutoff / 2, addr.length); +} \ No newline at end of file diff --git a/packages/yoroi-extension/app/UI/components/icons/YoroiLogo.tsx b/packages/yoroi-extension/app/UI/components/icons/YoroiLogo.tsx index d4f1ca97d3..eebc64bffb 100644 --- a/packages/yoroi-extension/app/UI/components/icons/YoroiLogo.tsx +++ b/packages/yoroi-extension/app/UI/components/icons/YoroiLogo.tsx @@ -2,7 +2,13 @@ import React from 'react'; export const YoroiLogo = (props: React.SVGProps) => { return ( - + void; + + // 1) direct delegation to a specific DRep (Yoroi or any other) delegateToDrep: (drepID: string) => Promise; + + // 2) open modal to choose DRep id & delegate + openDelegateModalForCustomDrep: () => void; + + // 3) always abstain + delegateToAbstain: () => Promise; + + // 4) always no-confidence + delegateToNoConfidence: () => Promise; }; export const useGovernanceDelegationToYoroiDrep = (): UseGovernanceDelegationResult => { @@ -20,10 +32,19 @@ export const useGovernanceDelegationToYoroiDrep = (): UseGovernanceDelegationRes const strings = useStrings(); - const { governanceVoteChanged, createDrepDelegationTransaction, signDelegationTransaction, selectedWallet } = useGovernance(); + const { governanceVoteChanged, createDrepDelegationTransaction, signDelegationTransaction, selectedWallet, governanceManager } = + useGovernance(); - const { openTxReviewModal, startLoadingTxReview, stopLoadingTxReview, changePasswordInputValue, showTxResultModal, setDrepId } = - useTxReviewModal(); + const { + openTxReviewModal, + startLoadingTxReview, + stopLoadingTxReview, + changePasswordInputValue, + showTxResultModal, + setDrepId, + setUnsignedTx, + drepCredentialHex, + } = useTxReviewModal(); const signGovernanceTx = React.useCallback( async (password: string) => { @@ -84,6 +105,7 @@ export const useGovernanceDelegationToYoroiDrep = (): UseGovernanceDelegationRes [createDrepDelegationTransaction, openTxReviewModal, signGovernanceTx, strings] ); + /** 1) Delegate to a specific DRep (Yoroi or any other) */ const delegateToDrep = React.useCallback( async (drepID: string) => { const vote: Vote = { kind: 'delegate', drepID }; @@ -96,10 +118,73 @@ export const useGovernanceDelegationToYoroiDrep = (): UseGovernanceDelegationRes [governanceVoteChanged, setDrepId, createUnsignTx] ); + /** 2) Open modal to choose a custom DRep */ + const openDelegateModalForCustomDrep = React.useCallback(() => { + if (!governanceManager) { + return; + } + + const vote: Vote = { kind: 'delegate', drepID: drepCredentialHex ?? '' }; + governanceVoteChanged(vote); + + openTxReviewModal({ + title: 'CHOOSE YOUR DREP', + modalView: 'chooseOtherDrepId', + createUnsignedTx: async (value: string) => { + try { + startLoadingTxReview(); + const txSignRequest: any = await createDrepDelegationTransaction(value); + setUnsignedTx({ + type: 'setUnsignedTx', + unsignedTx: txSignRequest.signTxRequest.unsignedTx, + }); + } finally { + stopLoadingTxReview(); + } + }, + submitTx: (password: string) => { + void signGovernanceTx(password); + }, + operations: { + kind: 'delegate vote', + }, + }); + }, [ + governanceManager, + governanceVoteChanged, + drepCredentialHex, + openTxReviewModal, + startLoadingTxReview, + stopLoadingTxReview, + createDrepDelegationTransaction, + setUnsignedTx, + signGovernanceTx, + ]); + + /** 3) Always abstain */ + const delegateToAbstain = React.useCallback(async () => { + const vote: Vote = { kind: DREP_ALWAYS_ABSTAIN }; + + governanceVoteChanged(vote); + // For abstain / no-confidence we usually just pass the constant to the tx creation + await createUnsignTx(DREP_ALWAYS_ABSTAIN); + }, [governanceVoteChanged, createUnsignTx]); + + /** 4) Always no-confidence */ + const delegateToNoConfidence = React.useCallback(async () => { + const vote: Vote = { kind: DREP_ALWAYS_NO_CONFIDENCE }; + + governanceVoteChanged(vote); + await createUnsignTx(DREP_ALWAYS_NO_CONFIDENCE); + }, [governanceVoteChanged, createUnsignTx]); + return { loadingUnsignTx, error, setError, delegateToDrep, + openDelegateModalForCustomDrep, + delegateToAbstain, + delegateToNoConfidence, }; }; diff --git a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useIsGovernanceAllowed.ts b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useIsGovernanceAllowed.ts new file mode 100644 index 0000000000..0b24687070 --- /dev/null +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useIsGovernanceAllowed.ts @@ -0,0 +1,21 @@ +import {networks} from '../../../../../api/ada/lib/storage/database/prepackaged/networks'; +import {useGovernance} from '../../module/GovernanceContextProvider'; + +export const useIsGovernanceAllowed = () => { + const {governanceStatus, walletAdaBalance, networkId} = useGovernance(); + + const isParticipating = + governanceStatus.status != null && governanceStatus.status !== 'none'; + + const hasZeroAda = walletAdaBalance !== null && walletAdaBalance === 0; + const isTestnet = networkId !== networks.CardanoMainnet.NetworkId; + + const isNotAllowed = !isParticipating && hasZeroAda; + + return { + isNotAllowed, + isParticipating, + hasZeroAda, + isTestnet, + }; +}; diff --git a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx index 1dfc4e255e..f4440c4518 100644 --- a/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/common/hooks/useStrings.tsx @@ -237,6 +237,23 @@ export const messages = Object.freeze( id: 'governance.changeToDrep', defaultMessage: '!!!Change to DRep', }, + delegateToOtherDrep: { + id: 'governance.delegateToOtherDrep', + defaultMessage: '!!!Delegate to other DRep', + }, + delegatingLabel: { + id: 'governance.delegatingLabel', + defaultMessage: '!!!Delegateing', + }, + delegationStatus: { + id: 'governance.delegationStatus', + defaultMessage: '!!!Delegation status', + }, + votingPowerInfo: { + id: 'governance.votingPowerInfo', + defaultMessage: + '!!!Your voting power is currently delegated and contributing to Cardano’s decision-making. You remain free to adjust your delegation whenever you choose.', + }, }) ); @@ -302,5 +319,9 @@ export const useStrings = () => { chooseAbstain: intl.formatMessage(messages.chooseAbstain), chooseNoConfidence: intl.formatMessage(messages.chooseNoConfidence), changeToDrep: intl.formatMessage(messages.changeToDrep), + delegateToOtherDrep: intl.formatMessage(messages.delegateToOtherDrep), + delegatingLabel: intl.formatMessage(messages.delegatingLabel), + delegationStatus: intl.formatMessage(messages.delegationStatus), + votingPowerInfo: intl.formatMessage(messages.votingPowerInfo), }).current; }; diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx index 4681dd011c..6a63aa4910 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DRepOptions.tsx @@ -8,7 +8,7 @@ import { DrepOptionsCard } from './DrepOptionsCard'; import { useGovernance } from '../../module/GovernanceContextProvider'; import { useGovernanceDelegationToYoroiDrep } from '../../common/hooks/useGovernanceDelegationToYoroiDrep'; import { useGovernanceStatusState } from '../../common/hooks/useGovernanceStatusState'; -import { YOROI_DREP_ID } from '../../common/constants'; +import { DREP_ALWAYS_ABSTAIN, DREP_ALWAYS_NO_CONFIDENCE, GOVERNANCE_STATUS, YOROI_DREP_ID } from '../../common/constants'; interface DRepOptionsScreenProps {} @@ -18,14 +18,22 @@ export const DRepOptions: React.FC = () => { const theme: any = useTheme(); const { submitedTransactions } = useGovernance(); - const { loadingUnsignTx, error, delegateToDrep } = useGovernanceDelegationToYoroiDrep(); - const isPendindDrepDelegationTx = submitedTransactions.length > 0 && submitedTransactions[0]?.isDrepDelegation === true; + const { loadingUnsignTx, delegateToDrep, openDelegateModalForCustomDrep, delegateToAbstain, delegateToNoConfidence } = + useGovernanceDelegationToYoroiDrep(); + const isPendingDrepDelegationTx = submitedTransactions.length > 0 && submitedTransactions[0]?.isDrepDelegation === true; const { governanceStatusState: cardState, governanceStatus } = useGovernanceStatusState(); const onBack = () => { navigateTo.selectRevampStatus(); }; + const drepID = governanceStatus.drep ? governanceStatus.drep : YOROI_DREP_ID; + const isDelegated = cardState === GOVERNANCE_STATUS.DELEGATED; + const isDelegatingToYoroiDrep = isDelegated && drepID === YOROI_DREP_ID; + const isDelegationToOtherDrep = isDelegated && drepID !== YOROI_DREP_ID; + const isAbstain = drepID === null || governanceStatus.status === DREP_ALWAYS_ABSTAIN; + const isNoConfidence = drepID === null || governanceStatus.status === DREP_ALWAYS_NO_CONFIDENCE; + const drepOptionsConfig = [ { key: 'yoroi', @@ -37,36 +45,44 @@ export const DRepOptions: React.FC = () => { onAction: () => delegateToDrep(YOROI_DREP_ID), onViewDetails: () => console.log('View Yoroi details'), status: cardState, + drepId: governanceStatus.drep, + isDelegated: isDelegatingToYoroiDrep, }, { key: 'others', title: strings.otherDReps, description: strings.designatingSomeoneElse, - buttonText: strings.delegateLabel, + buttonText: isDelegationToOtherDrep ? strings.delegateToOtherDrep : strings.delegateLabel, variant: 'outlined' as const, icon: , - onAction: () => console.log('Browse DReps'), + onAction: () => openDelegateModalForCustomDrep(), status: cardState, + drepId: governanceStatus.drep, + isDelegated: isDelegationToOtherDrep, }, { key: 'abstain', title: strings.abstain, description: strings.chooseAbstain, - buttonText: strings.delegateLabel, + buttonText: isAbstain ? strings.changeToDrep : strings.delegateLabel, variant: 'outlined' as const, icon: , - onAction: () => console.log('Abstain'), + onAction: () => (isAbstain ? openDelegateModalForCustomDrep() : delegateToAbstain()), status: cardState, + drepId: null, + isDelegated: isAbstain, }, { key: 'noConfidence', title: strings.noConfidence, description: strings.chooseNoConfidence, - buttonText: strings.delegateLabel, + buttonText: isNoConfidence ? strings.changeToDrep : strings.delegateLabel, variant: 'outlined' as const, icon: , - onAction: () => console.log('No Confidence'), + onAction: () => (isNoConfidence ? openDelegateModalForCustomDrep() : delegateToNoConfidence()), status: cardState, + drepId: null, + isDelegated: isNoConfidence, }, ]; @@ -98,7 +114,9 @@ export const DRepOptions: React.FC = () => { onAction={option.onAction} onViewDetails={option.onViewDetails} status={option.status} - drepId={governanceStatus.drep ? governanceStatus.drep : YOROI_DREP_ID} + drepId={option.drepId} + isDelegated={option.isDelegated} + pending={isPendingDrepDelegationTx || loadingUnsignTx} /> ))} @@ -120,25 +138,12 @@ const TitleSection = styled(Box)(() => ({ alignItems: 'center', padding: '0px', gap: '8px', - width: '1248px', - height: '58px', - flex: 'none', - order: 0, - alignSelf: 'stretch', - flexGrow: 0, })); const CardsRow = styled(Box)(() => ({ display: 'flex', flexDirection: 'row', - alignItems: 'center', flexWrap: 'wrap', padding: '0px', gap: '24px', - width: '1248px', - height: '320px', - flex: 'none', - order: 1, - alignSelf: 'stretch', - flexGrow: 0, })); diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx index 832d3d9e04..a181373ec4 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/DrepOptionsCard.tsx @@ -5,7 +5,7 @@ import { GOVERNANCE_STATUS, GovernanceStatusState } from '../../common/constants import { YOROI_VOTING_RECORD_LINK } from '../../common/constants'; import { LoadingButton } from '@mui/lab'; import { Icon } from '../../../../components'; -import { truncateFormatter } from '../../../../../utils/formatters'; +import { truncateFormatter } from '../../../../common/helpers/formatters'; interface ActionCardProps { title: string; @@ -16,7 +16,9 @@ interface ActionCardProps { onAction: () => void; onViewDetails?: () => void; status: GovernanceStatusState; - drepId?: string; + drepId: string | null; + isDelegated: boolean; + pending?: boolean; } export const DrepOptionsCard: React.FC = ({ @@ -29,19 +31,13 @@ export const DrepOptionsCard: React.FC = ({ onViewDetails, status, drepId, + isDelegated, + pending = false, }) => { const strings = useStrings(); - console.log('DrepOptionsCard render', { title, status, variant, drepId }); - const handleCopy = () => { - try { - navigator.clipboard.writeText(drepId || ''); - } catch { - // no-op - } - }; return ( - + {icon} @@ -49,51 +45,16 @@ export const DrepOptionsCard: React.FC = ({ {description} + {GOVERNANCE_STATUS.DELEGATED === status && drepId && isDelegated && } + {GOVERNANCE_STATUS.IDLE === status && drepId === null && isDelegated && } - {GOVERNANCE_STATUS.DELEGATED === status && ( - - - - - ID - - - - - - {truncateFormatter(drepId, 15)} - - - - - - - - - - - {strings.drepStatus} - - - - - - - {'Active'} - - - - - - )} - {variant === 'primary' ? ( - {status === GOVERNANCE_STATUS.IDLE && ( + {(status === GOVERNANCE_STATUS.DELEGATED || drepId === null) && !isDelegated && ( // @ts-ignore - + {buttonText} )} @@ -117,7 +78,7 @@ export const DrepOptionsCard: React.FC = ({ ) : ( // @ts-ignore - From 350f751b7519a73a1a36ca4f2889dc9b7ffddc84 Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Tue, 2 Dec 2025 15:11:31 +0200 Subject: [PATCH 12/13] fix condition --- .../GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx index 18ff5b07b2..d5eea549f7 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx @@ -151,7 +151,7 @@ export const GovernanceStatusRevampCard: React.FC = ( )} - {governanceStatus?.status !== GOVERNANCE_STATUS.IDLE && !isDelegated && ( + {((governanceStatus?.status !== GOVERNANCE_STATUS.IDLE && !isDelegated) || isDelegationToOtherDrep) && ( {strings.delegationStatus} @@ -191,7 +191,7 @@ export const GovernanceStatusRevampCard: React.FC = ( {primaryButtonLabel} )} - {showOnDetailsLink && ( + {showOnDetailsLink && !isDelegationToOtherDrep && ( event.stopPropagation()} From eb147c57fdcd9c806dc7aa4324655a7c877b3b10 Mon Sep 17 00:00:00 2001 From: Sorin Chis Date: Tue, 2 Dec 2025 15:32:53 +0200 Subject: [PATCH 13/13] fix condition --- .../GovernanceStatusRevampCard.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx index d5eea549f7..bdc84d3973 100644 --- a/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx +++ b/packages/yoroi-extension/app/UI/features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusRevampCard.tsx @@ -1,4 +1,3 @@ -// features/governace/useCases/GovernanceStatusRevamp/GovernanceStatusCard.tsx import React from 'react'; import { styled, useTheme } from '@mui/material/styles'; import { Box, Typography, IconButton, Link, Stack } from '@mui/material'; @@ -14,6 +13,7 @@ import { YOROI_VOTING_RECORD_LINK, } from '../../common/constants'; import { truncateFormatter } from '../../../../common/helpers/formatters'; +import { useIsGovernanceAllowed } from '../../common/hooks/useIsGovernanceAllowed'; interface GovernanceStatusCardProps { state: GovernanceStatusState; @@ -38,6 +38,7 @@ export const GovernanceStatusRevampCard: React.FC = ( }) => { const isDisabled = state === GOVERNANCE_STATUS.DISABLED || pending; const isDelegated = state === GOVERNANCE_STATUS.DELEGATED; + const { isParticipating } = useIsGovernanceAllowed(); const strings = useStrings(); const theme: any = useTheme(); @@ -48,6 +49,12 @@ export const GovernanceStatusRevampCard: React.FC = ( const isNoConfidence = governanceStatus?.drep === null && governanceStatus?.status === DREP_ALWAYS_NO_CONFIDENCE; const primaryButtonLabel = forModal ? strings.delegateLabel : isDelegated ? strings.changeToDrep : strings.delegateLabel; const showOnDetailsLink = onDetailsClick && !isAbstain && !isNoConfidence; + const showDrepStatus = isDelegationToOtherDrep || isDelegationToYoroiDrep || !isParticipating; + const showDrepId = isDelegationToOtherDrep || isDelegationToYoroiDrep; + const showDelegatingLabel = isParticipating; + const showDelegateToOtherDrepButton = isAbstain || isNoConfidence; + const showDelegateToYoroiDrepButton = governanceStatus?.status === GOVERNANCE_STATUS.IDLE; + const showVotingRecordLink = showOnDetailsLink && !isDelegationToOtherDrep; const handleCopy = () => { try { @@ -106,7 +113,7 @@ export const GovernanceStatusRevampCard: React.FC = ( return ( - + {handleCardInfo().icon} @@ -119,7 +126,7 @@ export const GovernanceStatusRevampCard: React.FC = ( - {(isDelegationToOtherDrep || isDelegationToYoroiDrep) && ( + {showDrepId && ( ID @@ -136,7 +143,7 @@ export const GovernanceStatusRevampCard: React.FC = ( )} - {isDelegationToOtherDrep && ( + {showDrepStatus && ( {strings.drepStatus} @@ -151,7 +158,7 @@ export const GovernanceStatusRevampCard: React.FC = ( )} - {((governanceStatus?.status !== GOVERNANCE_STATUS.IDLE && !isDelegated) || isDelegationToOtherDrep) && ( + {showDelegatingLabel && ( {strings.delegationStatus} @@ -166,7 +173,7 @@ export const GovernanceStatusRevampCard: React.FC = ( )} - {(isAbstain || isNoConfidence) && ( + {showDelegateToOtherDrepButton && ( = ( {strings.changeToDrep} )} - {governanceStatus?.status === GOVERNANCE_STATUS.IDLE && ( + {showDelegateToYoroiDrepButton && ( = ( {primaryButtonLabel} )} - {showOnDetailsLink && !isDelegationToOtherDrep && ( + {showVotingRecordLink && ( event.stopPropagation()} @@ -279,8 +286,8 @@ const TitleRow = styled(Box)(() => ({ height: '48px', })); -const Avatar = styled(Box)<{ state: GovernanceStatusState; isDelegationToYoroiDrep: boolean }>( - ({ isDelegationToYoroiDrep, theme, state }: any) => ({ +const Avatar = styled(Box)<{ state: GovernanceStatusState; isDelegationToYoroiDrep: boolean; isParticipating: boolean }>( + ({ isDelegationToYoroiDrep, theme, isParticipating }: any) => ({ width: '48px', height: '48px', borderRadius: '1200px', @@ -288,10 +295,7 @@ const Avatar = styled(Box)<{ state: GovernanceStatusState; isDelegationToYoroiDr alignItems: 'center', justifyContent: 'center', flexShrink: 0, - background: - state === (GOVERNANCE_STATUS.IDLE || isDelegationToYoroiDrep) - ? theme.palette.ds.primary_500 - : theme.palette.ds.secondary_200, + background: !isParticipating || isDelegationToYoroiDrep ? theme.palette.ds.primary_500 : theme.palette.ds.secondary_200, }) );