diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRegistrationContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRegistrationContainer.tsx index f9a32a49c9..3b349e0452 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRegistrationContainer.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRegistrationContainer.tsx @@ -2,10 +2,12 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { ConfirmDRepRegistration } from '@lace/core'; import { SignTxData } from './types'; -import { dRepRegistrationInspector, drepIDasBech32FromHash } from './utils'; +import { certificateInspectorFactory, drepIDasBech32FromHash } from './utils'; import { Wallet } from '@lace/cardano'; import { useWalletStore } from '@src/stores'; +const { CertificateType } = Wallet.Cardano; + interface Props { signTxData: SignTxData; errorMessage?: string; @@ -13,11 +15,12 @@ interface Props { export const ConfirmDRepRegistrationContainer = ({ signTxData, errorMessage }: Props): React.ReactElement => { const { t } = useTranslation(); - const certificate = dRepRegistrationInspector(signTxData.tx); const { walletUI: { cardanoCoin } } = useWalletStore(); - + const certificate = certificateInspectorFactory( + CertificateType.RegisterDelegateRepresentative + )(signTxData.tx); const depositPaidWithCardanoSymbol = `${Wallet.util.lovelacesToAdaString(certificate.deposit.toString())} ${ cardanoCoin.symbol }`; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx index 1bb2cb14ab..dc926c88e3 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmDRepRetirementContainer.tsx @@ -2,11 +2,13 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { ConfirmDRepRetirement } from '@lace/core'; import { SignTxData } from './types'; -import { dRepRetirementInspector, drepIDasBech32FromHash, getOwnRetirementMessageKey } from './utils'; +import { certificateInspectorFactory, drepIDasBech32FromHash, getOwnRetirementMessageKey } from './utils'; import { Wallet } from '@lace/cardano'; import { useWalletStore } from '@src/stores'; import { useIsOwnPubDRepKey } from './hooks'; +const { CertificateType } = Wallet.Cardano; + interface Props { signTxData: SignTxData; errorMessage?: string; @@ -14,12 +16,13 @@ interface Props { export const ConfirmDRepRetirementContainer = ({ signTxData, errorMessage }: Props): React.ReactElement => { const { t } = useTranslation(); - const certificate = dRepRetirementInspector(signTxData.tx); const { walletUI: { cardanoCoin }, inMemoryWallet } = useWalletStore(); - + const certificate = certificateInspectorFactory( + CertificateType.UnregisterDelegateRepresentative + )(signTxData.tx); const depositPaidWithCardanoSymbol = `${Wallet.util.lovelacesToAdaString(certificate.deposit.toString())} ${ cardanoCoin.symbol }`; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx index 9df9c6f88b..85b8aed71c 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransactionContent.tsx @@ -5,6 +5,7 @@ import { DappTransactionContainer } from './DappTransactionContainer'; import { TxType } from './utils'; import { SignTxData } from './types'; import { ConfirmDRepRetirementContainer } from './ConfirmDRepRetirementContainer'; +import { ConfirmVoteDelegationContainer } from './ConfirmVoteDelegationContainer'; import { VotingProceduresContainer } from './VotingProceduresContainer'; interface Props { @@ -23,6 +24,9 @@ export const ConfirmTransactionContent = ({ txType, signTxData, errorMessage }: if (txType === TxType.DRepRetirement) { return ; } + if (txType === TxType.VoteDelegation) { + return ; + } if (txType === TxType.VotingProcedures) { return ; } diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmVoteDelegationContainer.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmVoteDelegationContainer.tsx new file mode 100644 index 0000000000..3e578cc900 --- /dev/null +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmVoteDelegationContainer.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ConfirmVoteDelegation } from '@lace/core'; +import { SignTxData } from './types'; +import { certificateInspectorFactory } from './utils'; +import { Wallet } from '@lace/cardano'; + +const { CertificateType } = Wallet.Cardano; + +interface Props { + signTxData: SignTxData; + errorMessage?: string; +} + +export const ConfirmVoteDelegationContainer = ({ signTxData, errorMessage }: Props): React.ReactElement => { + const { t } = useTranslation(); + const certificate = certificateInspectorFactory( + CertificateType.VoteDelegation + )(signTxData.tx); + const dRep = certificate.dRep; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts index e3765947d0..8381f0c291 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts @@ -8,6 +8,8 @@ import { runtime } from 'webextension-polyfill'; import { of } from 'rxjs'; import { sectionTitle, DAPP_VIEWS } from '../../config'; +const { CertificateType } = Wallet.Cardano; + const DAPP_TOAST_DURATION = 50; export enum TxType { @@ -16,6 +18,7 @@ export enum TxType { Burn = 'Burn', DRepRegistration = 'DRepRegistration', DRepRetirement = 'DRepRetirement', + VoteDelegation = 'VoteDelegation', VotingProcedures = 'VotingProcedures' } @@ -28,6 +31,10 @@ export const getTitleKey = (txType: TxType): string => { return 'core.drepRetirement.title'; } + if (txType === TxType.VoteDelegation) { + return 'core.voteDelegation.title'; + } + if (txType === TxType.VotingProcedures) { return 'core.votingProcedures.title'; } @@ -79,25 +86,10 @@ export const getTransactionAssetsId = (outputs: CardanoTxOut[]): Wallet.Cardano. return assetIds; }; -const isDRepRegistrationCertificate = (type: Wallet.Cardano.CertificateType) => - type === Wallet.Cardano.CertificateType.RegisterDelegateRepresentative; - -const isDRepRetirementCertificate = (type: Wallet.Cardano.CertificateType) => - type === Wallet.Cardano.CertificateType.UnregisterDelegateRepresentative; - -export const dRepRegistrationInspector = ( - tx: Wallet.Cardano.Tx -): Wallet.Cardano.RegisterDelegateRepresentativeCertificate | undefined => - tx?.body?.certificates?.find(({ __typename }) => isDRepRegistrationCertificate(__typename)) as - | Wallet.Cardano.RegisterDelegateRepresentativeCertificate - | undefined; - -export const dRepRetirementInspector = ( - tx: Wallet.Cardano.Tx -): Wallet.Cardano.UnRegisterDelegateRepresentativeCertificate | undefined => - tx?.body?.certificates?.find(({ __typename }) => isDRepRetirementCertificate(__typename)) as - | Wallet.Cardano.UnRegisterDelegateRepresentativeCertificate - | undefined; +export const certificateInspectorFactory = + (type: Wallet.Cardano.CertificateType) => + (tx: Wallet.Cardano.Tx): T | undefined => + tx?.body?.certificates?.find((certificate) => certificate.__typename === type) as T | undefined; export const votingProceduresInspector = (tx: Wallet.Cardano.Tx): Wallet.Cardano.VotingProcedures | undefined => tx?.body?.votingProcedures; @@ -106,12 +98,13 @@ export const getTxType = (tx: Wallet.Cardano.Tx): TxType => { const inspector = createTxInspector({ minted: assetsMintedInspector, burned: assetsBurnedInspector, - dRepRegistration: dRepRegistrationInspector, - dRepRetirement: dRepRetirementInspector, - votingProcedures: votingProceduresInspector + votingProcedures: votingProceduresInspector, + dRepRegistration: certificateInspectorFactory(CertificateType.RegisterDelegateRepresentative), + dRepRetirement: certificateInspectorFactory(CertificateType.UnregisterDelegateRepresentative), + voteDelegation: certificateInspectorFactory(CertificateType.VoteDelegation) }); - const { minted, burned, dRepRegistration, dRepRetirement, votingProcedures } = inspector( + const { minted, burned, dRepRegistration, dRepRetirement, voteDelegation, votingProcedures } = inspector( tx as Wallet.Cardano.HydratedTx ); const isMintTransaction = minted.length > 0; @@ -137,6 +130,10 @@ export const getTxType = (tx: Wallet.Cardano.Tx): TxType => { return TxType.DRepRetirement; } + if (voteDelegation) { + return TxType.VoteDelegation; + } + return TxType.Send; }; diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 0c1cffb22d..9055887c8c 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -1137,6 +1137,14 @@ "isOwnRetirement": "This is your DRep retirement.", "isNotOwnRetirement": "The presented DRepID does not match your wallet's DRepID." }, + "voteDelegation": { + "title": "Confirm vote delegation", + "metadata": "Metadata", + "drepId": "Drep ID", + "alwaysAbstain": "Abstain", + "alwaysNoConfidence": "No Confidence", + "option": "Yes" + }, "destinationAddressInput": { "recipientAddress": "Recipient's address or $handle" }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b6ed521622..4b2863db8a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -30,4 +30,5 @@ export * from '@ui/components/MnemonicWordsAutoComplete'; export * from '@ui/components/AddressCard'; export * from '@ui/components/ConfirmDRepRegistration'; export * from '@ui/components/ConfirmDRepRetirement'; +export * from '@ui/components/ConfirmVoteDelegation'; export * from '@ui/components/VotingProcedures'; diff --git a/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.stories.ts b/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.stories.ts new file mode 100644 index 0000000000..5d23ca14bf --- /dev/null +++ b/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.stories.ts @@ -0,0 +1,69 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { ConfirmVoteDelegation } from './ConfirmVoteDelegation'; +import { ComponentProps } from 'react'; + +const meta: Meta = { + title: 'ConfirmVoteDelegation', + component: ConfirmVoteDelegation, + parameters: { + layout: 'centered' + } +}; + +export default meta; +type Story = StoryObj; + +const data: ComponentProps = { + dappInfo: { + logo: 'https://cdn.mint.handle.me/favicon.png', + name: 'Mint', + url: 'https://preprod.mint.handle.me' + }, + translations: { + labels: { + drepId: 'Drep ID', + alwaysAbstain: 'Abstain', + alwaysNoConfidence: 'No Confidence' + }, + option: 'Yes', + metadata: 'Metadata' + }, + metadata: { + drepId: 'drep1ruvgm0auzdplfn7g2jf3kcnpnw5mlhwxaxj8crag8h6t2ye9y9g', + alwaysAbstain: false, + alwaysNoConfidence: false + } +}; + +export const Overview: Story = { + args: { + ...data + } +}; +export const WithError: Story = { + args: { + ...data, + errorMessage: 'Something went wrong' + } +}; + +export const WithAbstain: Story = { + args: { + ...data, + metadata: { + alwaysAbstain: true, + alwaysNoConfidence: false + } + } +}; + +export const WithNoConfidence: Story = { + args: { + ...data, + metadata: { + alwaysAbstain: false, + alwaysNoConfidence: true + } + } +}; diff --git a/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.tsx b/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.tsx new file mode 100644 index 0000000000..cad483cc3c --- /dev/null +++ b/packages/core/src/ui/components/ConfirmVoteDelegation/ConfirmVoteDelegation.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Box, Cell, Grid, TransactionSummary, Flex } from '@lace/ui'; +import { DappInfo, DappInfoProps } from '../DappInfo'; +import { ErrorPane } from '@lace/common'; + +interface Props { + dappInfo: Omit; + errorMessage?: string; + translations: { + labels: { + drepId: string; + alwaysAbstain: string; + alwaysNoConfidence: string; + }; + option: string; + metadata: string; + }; + metadata: { + drepId?: string; + alwaysAbstain: boolean; + alwaysNoConfidence: boolean; + }; +} + +export const ConfirmVoteDelegation = ({ dappInfo, errorMessage, translations, metadata }: Props): JSX.Element => ( + + + + + {errorMessage && ( + + + + )} + + + + + {metadata.drepId && ( + + + + )} + {metadata.alwaysAbstain && ( + + + + )} + {metadata.alwaysNoConfidence && ( + + + + )} + + +); diff --git a/packages/core/src/ui/components/ConfirmVoteDelegation/index.ts b/packages/core/src/ui/components/ConfirmVoteDelegation/index.ts new file mode 100644 index 0000000000..33e8e67554 --- /dev/null +++ b/packages/core/src/ui/components/ConfirmVoteDelegation/index.ts @@ -0,0 +1 @@ +export { ConfirmVoteDelegation } from './ConfirmVoteDelegation'; diff --git a/packages/core/src/ui/components/VotingProcedures/VotingProcedures.tsx b/packages/core/src/ui/components/VotingProcedures/VotingProcedures.tsx index 654f4b76f5..c7824f9880 100644 --- a/packages/core/src/ui/components/VotingProcedures/VotingProcedures.tsx +++ b/packages/core/src/ui/components/VotingProcedures/VotingProcedures.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { Box, Cell, Grid, Flex, Metadata, MetadataLink, Text, Divider, sx } from '@lace/ui'; import { DappInfo, DappInfoProps } from '../DappInfo'; -import { ErrorPane, Ellipsis } from '@lace/common'; +import { ErrorPane } from '@lace/common'; type VotingProcedure = { voter: { diff --git a/packages/ui/package.json b/packages/ui/package.json index d132baa605..c5e31ae811 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -29,6 +29,18 @@ "typecheck": "tsc --noEmit", "watch": "yarn build --watch" }, + "resolutions": { + "@storybook/addon-actions": "^6.5.16", + "@storybook/addon-essentials": "^6.5.16", + "@storybook/addon-interactions": "^6.5.16", + "@storybook/addon-links": "^6.5.16", + "@storybook/builder-webpack5": "6.5.16", + "@storybook/jest": "^0.0.10", + "@storybook/manager-webpack5": "6.5.16", + "@storybook/react": "^6.5.16", + "@storybook/test-runner": "^0.10.0", + "@storybook/testing-library": "^0.0.13" + }, "dependencies": { "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-avatar": "^1.0.2", @@ -94,17 +106,5 @@ "wait-on": "^7.0.1", "webpack": "^5.76.1", "webpack-dev-server": "^4.11.1" - }, - "resolutions": { - "@storybook/addon-actions": "^6.5.16", - "@storybook/addon-essentials": "^6.5.16", - "@storybook/addon-interactions": "^6.5.16", - "@storybook/addon-links": "^6.5.16", - "@storybook/builder-webpack5": "6.5.16", - "@storybook/jest": "^0.0.10", - "@storybook/manager-webpack5": "6.5.16", - "@storybook/react": "^6.5.16", - "@storybook/test-runner": "^0.10.0", - "@storybook/testing-library": "^0.0.13" } }