Skip to content

Commit

Permalink
fix(APP-3556): Fix and add block explorer links to proposal actions c…
Browse files Browse the repository at this point in the history
…omponents (#372)
  • Loading branch information
shan8851 authored Dec 20, 2024
1 parent 06f9597 commit c4aab10
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 32 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const Default: Story = {
assetSymbol: 'ETH',
assetAmount: 1,
assetName: 'Ethereum',
hash: '0xf006e9454ad77c5e8e6f54106c6939d3d8b68ae16fc216d67c752f54adb21fc6',
assetFiatPrice: 3850,
chainId: 1,
},
Expand All @@ -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',
},
};

Expand Down
17 changes: 12 additions & 5 deletions src/modules/components/asset/assetTransfer/assetTransfer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ describe('<AssetTransfer /> component', () => {
assetSymbol: 'ETH',
assetAmount: 1,
assetName: 'Ethereum',
hash: '0xf006e9454ad77c5e8e6f54106c6939d3d8b68ae16fc216d67c752f54adb21fc6',
...props,
};

Expand Down Expand Up @@ -56,13 +55,21 @@ describe('<AssetTransfer /> 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();
});
});
20 changes: 10 additions & 10 deletions src/modules/components/asset/assetTransfer/assetTransfer.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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.
*/
Expand All @@ -33,31 +37,27 @@ export interface IAssetTransferProps extends IWeb3ComponentProps {
* Price per asset in fiat.
*/
assetFiatPrice?: number | string;
/**
* Transaction hash.
*/
hash: string;
}

export const AssetTransfer: React.FC<IAssetTransferProps> = (props) => {
const {
sender,
recipient,
assetName,
assetAddress,
assetIconSrc,
assetAmount,
assetSymbol,
assetFiatPrice,
chainId,
hash,
wagmiConfig,
} = props;

const { buildEntityUrl } = useBlockExplorer({ chains: wagmiConfig?.chains, chainId });

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,
Expand Down Expand Up @@ -95,8 +95,8 @@ export const AssetTransfer: React.FC<IAssetTransferProps> = (props) => {
/>
<AssetTransferAddress txRole="recipient" participant={recipient} addressUrl={recipientUrl} />
</div>
<LinkBase
href={transactionUrl}
<DataList.Item
href={assetUrl}
target="_blank"
rel="noopener noreferrer"
className={assetTransferClassNames}
Expand All @@ -109,7 +109,7 @@ export const AssetTransfer: React.FC<IAssetTransferProps> = (props) => {
<span className="text-sm leading-tight text-neutral-800 md:text-base">{formattedTokenAmount}</span>
<span className="text-sm leading-tight text-neutral-500 md:text-base">{formattedFiatValue}</span>
</div>
</LinkBase>
</DataList.Item>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -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: () => <div /> } }));

jest.mock('../../../../member/memberDataListItem/memberDataListItemStructure', () => ({
MemberDataListItemStructure: ({ href }: { href: string }) => (
<div data-testid="member-data-list-item" data-href={href} />
),
}));
describe('<ProposalActionChangeMembers /> component', () => {
const createTestComponent = (props?: Partial<IProposalActionChangeMembersProps>) => {
const completeProps: IProposalActionChangeMembersProps = {
Expand All @@ -15,7 +19,11 @@ describe('<ProposalActionChangeMembers /> component', () => {
...props,
};

return <ProposalActionChangeMembers {...completeProps} />;
return (
<GukModulesProvider>
<ProposalActionChangeMembers {...completeProps} />
</GukModulesProvider>
);
};

it('renders the existing members correctly', () => {
Expand Down Expand Up @@ -58,4 +66,13 @@ describe('<ProposalActionChangeMembers /> 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}`);
});
});
Original file line number Diff line number Diff line change
@@ -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<IProposalActionChangeMembersProps> = (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 (
<div className="flex w-full flex-col gap-y-6">
<div className="flex flex-wrap gap-2">
Expand All @@ -18,6 +24,8 @@ export const ProposalActionChangeMembers: React.FC<IProposalActionChangeMembersP
ensName={member.name}
avatarSrc={member.avatarSrc}
className="grow basis-60"
href={getMemberBlockExplorerLink(member.address)}
target="_blank"
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<div data-testid="member-data-list-item">{`${tokenAmount.toString()} ${tokenSymbol}`}</div>
),
MemberDataListItemStructure: ({
tokenAmount,
tokenSymbol,
href,
}: {
tokenAmount: number;
tokenSymbol: string;
href: string;
}) => <div data-testid="member-data-list-item" data-href={href}>{`${tokenAmount.toString()} ${tokenSymbol}`}</div>,
}));

describe('<ProposalActionTokenMint /> component', () => {
Expand Down Expand Up @@ -50,4 +56,17 @@ describe('<ProposalActionTokenMint /> 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}`);
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { ChainEntityType, useBlockExplorer } from '../../../../../hooks';
import { MemberDataListItemStructure } from '../../../../member';
import type { IProposalActionTokenMintProps } from './proposalActionTokenMint.api';

export const ProposalActionTokenMint: React.FC<IProposalActionTokenMintProps> = (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 (
Expand All @@ -18,6 +22,8 @@ export const ProposalActionTokenMint: React.FC<IProposalActionTokenMintProps> =
tokenAmount={mintedTokenAmount}
tokenSymbol={tokenSymbol}
hideLabelTokenVoting={true}
href={receiverBlockExplorerLink}
target="_blank"
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface IProposalActionWithdrawTokenAsset {
* Symbol of the token.
*/
symbol: string;
/**
* Address of the token.
*/
address?: string;
/**
* URL of the token logo.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ describe('<ProposalActionWithdrawToken /> 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 });

Expand All @@ -39,7 +46,7 @@ describe('<ProposalActionWithdrawToken /> component', () => {
assetAmount: action.amount,
assetSymbol: action.token.symbol,
assetIconSrc: action.token.logo,
hash: '',
assetAddress: action.token.address,
}),
undefined,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ export const ProposalActionWithdrawToken: React.FC<IProposalActionWithdrawTokenP
sender={action.sender}
recipient={action.receiver}
assetName={action.token.name}
assetAddress={action.token.address}
assetAmount={action.amount}
assetFiatPrice={action.token.priceUsd}
assetSymbol={action.token.symbol}
assetIconSrc={action.token.logo}
// TODO: Make hash property on AssetTransfer optional (APP-3430)
hash=""
{...web3Props}
/>
);
Expand Down

0 comments on commit c4aab10

Please sign in to comment.