From c4aab10cca243cc355c9b70250ac8a0bd09a4705 Mon Sep 17 00:00:00 2001 From: "Shan8851.eth" Date: Fri, 20 Dec 2024 16:48:04 +0000 Subject: [PATCH] fix(APP-3556): Fix and add block explorer links to proposal actions components (#372) --- CHANGELOG.md | 6 +++++ .../assetTransfer/assetTransfer.stories.tsx | 7 +++--- .../assetTransfer/assetTransfer.test.tsx | 17 +++++++++---- .../asset/assetTransfer/assetTransfer.tsx | 20 +++++++-------- .../proposalActionChangeMembers.test.tsx | 23 ++++++++++++++--- .../proposalActionChangeMembers.tsx | 10 +++++++- .../proposalActionTokenMint.test.tsx | 25 ++++++++++++++++--- .../proposalActionTokenMint.tsx | 8 +++++- .../proposalActionWithdrawToken.api.tsx | 4 +++ .../proposalActionWithdrawToken.stories.tsx | 1 + .../proposalActionWithdrawToken.test.tsx | 11 ++++++-- .../proposalActionWithdrawToken.testUtils.ts | 2 +- .../proposalActionWithdrawToken.tsx | 3 +-- 13 files changed, 105 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cbee429..5f55db37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Changed + +- Remove `hash` property on the `AssetTransfer` module component and add optional `assetAddress` property +- Add link to the block explorer for members on the `ProposalActionChangeMembers` module component +- Add receiver block explorer link to the `ProposalActionTokenMint` module component + ## [1.0.60] - 2024-12-18 ### Changed diff --git a/src/modules/components/asset/assetTransfer/assetTransfer.stories.tsx b/src/modules/components/asset/assetTransfer/assetTransfer.stories.tsx index a03e9bbd5..3b24e5efe 100644 --- a/src/modules/components/asset/assetTransfer/assetTransfer.stories.tsx +++ b/src/modules/components/asset/assetTransfer/assetTransfer.stories.tsx @@ -28,7 +28,6 @@ export const Default: Story = { assetSymbol: 'ETH', assetAmount: 1, assetName: 'Ethereum', - hash: '0xf006e9454ad77c5e8e6f54106c6939d3d8b68ae16fc216d67c752f54adb21fc6', assetFiatPrice: 3850, chainId: 1, }, @@ -41,10 +40,10 @@ export const Fallback: Story = { args: { sender: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' }, recipient: { address: '0x168dAa4529bf88369ac8c1ABA5A2ad8CF2A61Fb9' }, - assetName: 'Ethereum', - assetSymbol: 'ETH', + assetName: 'USDC', + assetSymbol: 'USDC', assetAmount: 1, - hash: '0xf006e9454ad77c5e8e6f54106c6939d3d8b68ae16fc216d67c752f54adb21fc6', + assetAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', }, }; diff --git a/src/modules/components/asset/assetTransfer/assetTransfer.test.tsx b/src/modules/components/asset/assetTransfer/assetTransfer.test.tsx index 470627b9c..57cbddf11 100644 --- a/src/modules/components/asset/assetTransfer/assetTransfer.test.tsx +++ b/src/modules/components/asset/assetTransfer/assetTransfer.test.tsx @@ -14,7 +14,6 @@ describe(' component', () => { assetSymbol: 'ETH', assetAmount: 1, assetName: 'Ethereum', - hash: '0xf006e9454ad77c5e8e6f54106c6939d3d8b68ae16fc216d67c752f54adb21fc6', ...props, }; @@ -56,13 +55,21 @@ describe(' component', () => { expect(screen.getAllByTestId('asset-transfer-address')).toHaveLength(2); }); - it('configures and applies the correct link for transfer tx', () => { - const hash = '0x0ca620e2dd3147658b8a042b3e7b7cd6f5fa043bf3625140c0dbddcabf47dfb9'; - render(createTestComponent({ hash })); + it('configures and applies the correct link for asset when asset address is defined', () => { + const assetAddress = '0x0ca620e2dd3147658b8a042b3e7b7cd6f5fa043bf3625140c0dbddcabf47dfb9'; + render(createTestComponent({ assetAddress })); const links = screen.getByRole('link'); - const expectedTransactionLink = `https://etherscan.io/tx/${hash}`; + const expectedTransactionLink = `https://etherscan.io/token/${assetAddress}`; expect(links).toHaveAttribute('href', expectedTransactionLink); }); + + it('does not render block explorer link for asset when asset address is not defined', () => { + const assetAddress = undefined; + render(createTestComponent({ assetAddress })); + + const links = screen.queryByRole('link'); + expect(links).not.toBeInTheDocument(); + }); }); diff --git a/src/modules/components/asset/assetTransfer/assetTransfer.tsx b/src/modules/components/asset/assetTransfer/assetTransfer.tsx index df9898a96..51bdc0400 100644 --- a/src/modules/components/asset/assetTransfer/assetTransfer.tsx +++ b/src/modules/components/asset/assetTransfer/assetTransfer.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import { Avatar, AvatarIcon, IconType, LinkBase, NumberFormat, formatterUtils } from '../../../../core'; +import { Avatar, AvatarIcon, DataList, IconType, NumberFormat, formatterUtils } from '../../../../core'; import { ChainEntityType, useBlockExplorer } from '../../../hooks'; import { type ICompositeAddress, type IWeb3ComponentProps } from '../../../types'; import { AssetTransferAddress } from './assetTransferAddress'; @@ -17,6 +17,10 @@ export interface IAssetTransferProps extends IWeb3ComponentProps { * Name of the asset transferred. */ assetName: string; + /** + * Address of the asset transferred. + */ + assetAddress?: string; /** * Icon URL of the transferred asset. */ @@ -33,10 +37,6 @@ export interface IAssetTransferProps extends IWeb3ComponentProps { * Price per asset in fiat. */ assetFiatPrice?: number | string; - /** - * Transaction hash. - */ - hash: string; } export const AssetTransfer: React.FC = (props) => { @@ -44,12 +44,12 @@ export const AssetTransfer: React.FC = (props) => { sender, recipient, assetName, + assetAddress, assetIconSrc, assetAmount, assetSymbol, assetFiatPrice, chainId, - hash, wagmiConfig, } = props; @@ -57,7 +57,7 @@ export const AssetTransfer: React.FC = (props) => { const senderUrl = buildEntityUrl({ type: ChainEntityType.ADDRESS, id: sender.address }); const recipientUrl = buildEntityUrl({ type: ChainEntityType.ADDRESS, id: recipient.address }); - const transactionUrl = buildEntityUrl({ type: ChainEntityType.TRANSACTION, id: hash }); + const assetUrl = buildEntityUrl({ type: ChainEntityType.TOKEN, id: assetAddress }); const formattedTokenValue = formatterUtils.formatNumber(assetAmount, { format: NumberFormat.TOKEN_AMOUNT_SHORT, @@ -95,8 +95,8 @@ export const AssetTransfer: React.FC = (props) => { /> - = (props) => { {formattedTokenAmount} {formattedFiatValue} - + ); }; diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.test.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.test.tsx index afcb654e1..38f015386 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.test.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.test.tsx @@ -1,12 +1,16 @@ import { render, screen } from '@testing-library/react'; import { modulesCopy } from '../../../../../assets'; +import { GukModulesProvider } from '../../../../gukModulesProvider'; import { ProposalActionType } from '../../proposalActionsDefinitions'; import { ProposalActionChangeMembers } from './proposalActionChangeMembers'; import type { IProposalActionChangeMembersProps } from './proposalActionChangeMembers.api'; import { generateProposalActionChangeMembers } from './proposalActionChangeMembers.testUtils'; -jest.mock('../../../../member', () => ({ MemberDataListItem: { Structure: () =>
} })); - +jest.mock('../../../../member/memberDataListItem/memberDataListItemStructure', () => ({ + MemberDataListItemStructure: ({ href }: { href: string }) => ( +
+ ), +})); describe(' component', () => { const createTestComponent = (props?: Partial) => { const completeProps: IProposalActionChangeMembersProps = { @@ -15,7 +19,11 @@ describe(' component', () => { ...props, }; - return ; + return ( + + + + ); }; it('renders the existing members correctly', () => { @@ -58,4 +66,13 @@ describe(' component', () => { render(createTestComponent()); expect(screen.getByText(modulesCopy.proposalActionChangeMembers.blockNote)).toBeInTheDocument(); }); + + it('renders the correct block explorer link for the member', () => { + const members = [{ address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' }]; + const action = generateProposalActionChangeMembers({ members }); + render(createTestComponent({ action })); + + const memberItem = screen.getByTestId('member-data-list-item'); + expect(memberItem).toHaveAttribute('data-href', `https://etherscan.io/address/${members[0].address}`); + }); }); diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.tsx index 90a321cc7..294babf6d 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionChangeMembers/proposalActionChangeMembers.tsx @@ -1,13 +1,19 @@ import { DefinitionList, Heading } from '../../../../../../core'; +import { ChainEntityType, useBlockExplorer } from '../../../../../hooks'; import { useGukModulesContext } from '../../../../gukModulesProvider'; import { MemberDataListItem } from '../../../../member'; import { ProposalActionType } from '../../proposalActionsDefinitions'; import type { IProposalActionChangeMembersProps } from './proposalActionChangeMembers.api'; export const ProposalActionChangeMembers: React.FC = (props) => { - const { action } = props; + const { action, wagmiConfig, chainId } = props; const { copy } = useGukModulesContext(); + const { buildEntityUrl } = useBlockExplorer({ chains: wagmiConfig?.chains, chainId }); + + const getMemberBlockExplorerLink = (address: string) => + buildEntityUrl({ type: ChainEntityType.ADDRESS, id: address }); + return (
@@ -18,6 +24,8 @@ export const ProposalActionChangeMembers: React.FC ))}
diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.test.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.test.tsx index 72f747797..e7b0408d0 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.test.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.test.tsx @@ -5,9 +5,15 @@ import type { IProposalActionTokenMintProps } from './proposalActionTokenMint.ap import { generateProposalActionTokenMint } from './proposalActionTokenMint.testUtils'; jest.mock('../../../../member/memberDataListItem/memberDataListItemStructure', () => ({ - MemberDataListItemStructure: ({ tokenAmount, tokenSymbol }: { tokenAmount: number; tokenSymbol: string }) => ( -
{`${tokenAmount.toString()} ${tokenSymbol}`}
- ), + MemberDataListItemStructure: ({ + tokenAmount, + tokenSymbol, + href, + }: { + tokenAmount: number; + tokenSymbol: string; + href: string; + }) =>
{`${tokenAmount.toString()} ${tokenSymbol}`}
, })); describe(' component', () => { @@ -50,4 +56,17 @@ describe(' component', () => { render(createTestComponent()); expect(screen.queryByText('Voting Power')).not.toBeInTheDocument(); }); + + it('renders the block explorer link with the correct URL', () => { + const receiver = { + currentBalance: '0', + newBalance: '10', + address: '0x123456789', + name: 'Some Name', + }; + const action = generateProposalActionTokenMint({ receiver }); + render(createTestComponent({ action })); + const memberItem = screen.getByTestId('member-data-list-item'); + expect(memberItem).toHaveAttribute('data-href', `https://etherscan.io/address/${receiver.address}`); + }); }); diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.tsx index deeab8816..7f9333e0b 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionTokenMint/proposalActionTokenMint.tsx @@ -1,11 +1,15 @@ +import { ChainEntityType, useBlockExplorer } from '../../../../../hooks'; import { MemberDataListItemStructure } from '../../../../member'; import type { IProposalActionTokenMintProps } from './proposalActionTokenMint.api'; export const ProposalActionTokenMint: React.FC = (props) => { - const { action } = props; + const { action, wagmiConfig, chainId } = props; const { tokenSymbol, receiver } = action; const { currentBalance, newBalance, address, name, avatarSrc } = receiver; + const { buildEntityUrl } = useBlockExplorer({ chains: wagmiConfig?.chains, chainId }); + const receiverBlockExplorerLink = buildEntityUrl({ type: ChainEntityType.ADDRESS, id: address }); + const mintedTokenAmount = +newBalance - +currentBalance; return ( @@ -18,6 +22,8 @@ export const ProposalActionTokenMint: React.FC = tokenAmount={mintedTokenAmount} tokenSymbol={tokenSymbol} hideLabelTokenVoting={true} + href={receiverBlockExplorerLink} + target="_blank" />
); diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.api.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.api.tsx index c91295168..fe03c47e9 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.api.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.api.tsx @@ -14,6 +14,10 @@ export interface IProposalActionWithdrawTokenAsset { * Symbol of the token. */ symbol: string; + /** + * Address of the token. + */ + address?: string; /** * URL of the token logo. */ diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.stories.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.stories.tsx index 02eba6bb8..a58cae170 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.stories.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.stories.tsx @@ -29,6 +29,7 @@ export const Default: Story = { logo: 'https://s2.coinmarketcap.com/static/img/coins/64x64/5994.png', priceUsd: '0.00002459', decimals: 18, + address: '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce', }, amount: '9784653197', }), diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.test.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.test.tsx index 3e5bbacec..079fb55d6 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.test.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.test.tsx @@ -25,7 +25,14 @@ describe(' component', () => { it('passes correct props to AssetTransfer', () => { const sender = { address: '0x1D03D98c0aac1f83860cec5156116FE68725642E' }; const receiver = { address: '0x1D03D98c0aac1f83860cec5156116FE687259999' }; - const token = { name: 'Bitcoin', symbol: 'BTC', logo: 'btc-logo.png', priceUsd: '50000', decimals: 6 }; + const token = { + name: 'Bitcoin', + symbol: 'BTC', + logo: 'btc-logo.png', + priceUsd: '50000', + decimals: 6, + address: '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce', + }; const amount = '10'; const action = generateProposalActionWithdrawToken({ sender, receiver, token, amount }); @@ -39,7 +46,7 @@ describe(' component', () => { assetAmount: action.amount, assetSymbol: action.token.symbol, assetIconSrc: action.token.logo, - hash: '', + assetAddress: action.token.address, }), undefined, ); diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.testUtils.ts b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.testUtils.ts index ca42ec8bb..ae64d190f 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.testUtils.ts +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.testUtils.ts @@ -9,7 +9,7 @@ export const generateProposalActionWithdrawToken = ( type: ProposalActionType.WITHDRAW_TOKEN, sender: { address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' }, receiver: { address: '0x3f5CE5FBFe3E9af3971dD833D26BA9b5C936F0bE' }, - token: { name: 'Token name', symbol: 'TTT', logo: '', priceUsd: '1.00', decimals: 18 }, + token: { name: 'Token name', symbol: 'TTT', logo: '', priceUsd: '1.00', decimals: 18, address: '0x1' }, amount: '10000000', ...action, }); diff --git a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.tsx b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.tsx index ee8b7eca5..75dc961bb 100644 --- a/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.tsx +++ b/src/modules/components/proposal/proposalActions/proposalActionsList/proposalActionWithdrawToken/proposalActionWithdrawToken.tsx @@ -9,12 +9,11 @@ export const ProposalActionWithdrawToken: React.FC );