Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit d27d8ea
Author: KD <[email protected]>
Date:   Wed Apr 3 13:21:23 2024 +0200

    fix: swap out for additonal span tags

commit 2582f01
Author: KD <[email protected]>
Date:   Wed Apr 3 11:15:09 2024 +0200

    fix: remove console.log

commit d215d6e
Merge: 8e1993e 613663c
Author: KD <[email protected]>
Date:   Wed Apr 3 11:05:38 2024 +0200

    Merge branch 'main' of github.com:aragon/ods into feat/APP-2797

commit 8e1993e
Author: KD <[email protected]>
Date:   Wed Apr 3 11:02:28 2024 +0200

    fix: resolve PR convos - spy test, move consts, remove unnecessary checks and styles

commit b02ab99
Author: KD <[email protected]>
Date:   Wed Apr 3 10:14:51 2024 +0200

    chore: update failed state, revise tests

commit 613663c
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Apr 2 12:24:38 2024 +0200

    chore: bump express from 4.18.2 to 4.19.2 (#132)

commit 6d1979e
Author: KD <[email protected]>
Date:   Mon Apr 1 08:32:10 2024 +0200

    fix: TransactionStatus export

commit bcce4a4
Merge: cb5edff 1af7dba
Author: KD <[email protected]>
Date:   Sun Mar 31 23:05:35 2024 +0200

    chore: resolve merge conflict - update CHANGELOG

commit cb5edff
Author: KD <[email protected]>
Date:   Sun Mar 31 23:04:16 2024 +0200

    chore: resolve PR conversations - improved readability, block explorer link, new prop naming, etc

commit f9c96de
Author: KD <[email protected]>
Date:   Wed Mar 27 19:51:29 2024 +0100

    chore: rename effective function

commit 680a2b9
Author: KD <[email protected]>
Date:   Wed Mar 27 19:47:22 2024 +0100

    chore: clean up tests

commit a8962c0
Author: KD <[email protected]>
Date:   Wed Mar 27 19:30:59 2024 +0100

    feat: implement PR fixes from convos -- logic cleanup, props, index barrelin, etc

commit 2d7f6c0
Merge: a18eb16 5ed869c
Author: KD <[email protected]>
Date:   Wed Mar 27 13:30:49 2024 +0100

    On feat/APP-2797: pending PR work

commit 5ed869c
Author: KD <[email protected]>
Date:   Wed Mar 27 13:30:49 2024 +0100

    index on feat/APP-2797: a18eb16 chore: clean up tests

commit a18eb16
Author: KD <[email protected]>
Date:   Tue Mar 26 09:47:16 2024 +0100

    chore: clean up tests

commit 18c445d
Author: KD <[email protected]>
Date:   Mon Mar 25 17:02:26 2024 +0100

    chore: prop cleanup + naming

commit b214cff
Author: KD <[email protected]>
Date:   Mon Mar 25 16:37:18 2024 +0100

    fix: prevent layout shift with spinner wrapper, extra cleanup

commit 64e5894
Author: KD <[email protected]>
Date:   Mon Mar 25 13:10:54 2024 +0100

    fix: update test for unix timestamp to pass on remote CI timezone

commit b50ea0c
Author: KD <[email protected]>
Date:   Mon Mar 25 12:17:32 2024 +0100

    chore: improve test coverage for failed cases

commit e230d1c
Merge: 915d27d bd0f4ce
Author: KD <[email protected]>
Date:   Mon Mar 25 11:50:40 2024 +0100

    chore: update CHANGELOG

commit 915d27d
Author: KD <[email protected]>
Date:   Mon Mar 25 11:40:57 2024 +0100

    feat: handle tx status and relationship with tx type

commit 57f4eee
Author: KD <[email protected]>
Date:   Thu Mar 21 19:29:29 2024 +0100

    chore: update CHANGELOG

commit 2919e21
Author: KD <[email protected]>
Date:   Thu Mar 21 19:28:47 2024 +0100

    chore: update index exports

commit a358d8e
Author: KD <[email protected]>
Date:   Thu Mar 21 19:28:01 2024 +0100

    feat: implement transactionDataListItemStructure component
  • Loading branch information
thekidnamedkd committed Apr 3, 2024
1 parent 1af7dba commit ece7e60
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 22 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Implement `DaoDataListItem.Structure`, `ProposalDataListItem.Structure`, `MemberDataListItem.Structure`,
`AssetDataListItem.Structure` and `AddressInput` module components
- Implement `DaoDataListItem.Structure`, `ProposalDataListItem.Structure`, `TransactionDataListItem.Structure`,
`MemberDataListItem.Structure`, `AssetDataListItem.Structure` and `AddressInput` module components
- Implement `StatePingAnimation` core component
- Implement `addressUtils` and `ensUtils` module utilities
- Implement `useDebouncedValue` core hook and `clipboardUtils` core utility
Expand All @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Update Eslint rules to align usage of boolean properties
- Update default query-client options to set a stale time greater than 0
- Bump `webpack-dev-middleware` from 6.1.1 to 6.1.2
- Bump `express` from 4.18.2 to 4.19.2 #132

### Fixed

Expand Down
1 change: 1 addition & 0 deletions src/modules/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './dao';
export * from './member';
export * from './odsModulesProvider';
export * from './proposal';
export * from './transaction';
1 change: 1 addition & 0 deletions src/modules/components/transaction/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './transactionDataListItem';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { TransactionDataListItemStructure as Structure } from './transactionDataListItemStructure/transactionDataListItemStructure';

export const TransactionDataListItem = {
Structure,
};
export {
ITransactionDataListItemProps,
TransactionStatus,
TransactionType,
} from './transactionDataListItemStructure/transactionDataListItemStructure.api';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { TransactionDataListItemStructure } from './transactionDataListItemStructure';
export {
ITransactionDataListItemProps,
TransactionStatus,
TransactionType,
} from './transactionDataListItemStructure.api';
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { render, screen, waitFor } from '@testing-library/react';
import * as wagmi from 'wagmi';
import { DataList, NumberFormat, formatterUtils } from '../../../../../core';
import { TransactionDataListItemStructure } from './transactionDataListItemStructure';
import {
TransactionStatus,
TransactionType,
type ITransactionDataListItemProps,
} from './transactionDataListItemStructure.api';

describe('<TransactionDataListItem.Structure /> component', () => {
const useChainsMock = jest.spyOn(wagmi, 'useChains');

beforeEach(() => {
useChainsMock.mockReturnValue([
{
id: 1,
blockExplorers: {
default: { name: 'Etherscan', url: 'https://etherscan.io', apiUrl: 'https://api.etherscan.io/api' },
},
name: 'Chain Name',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: { default: { http: ['https://cloudflare-eth.com'] } },
},
]);
});

afterEach(() => {
useChainsMock.mockReset();
});

const createTestComponent = (props?: Partial<ITransactionDataListItemProps>) => {
const defaultProps: ITransactionDataListItemProps = {
chainId: 1,
hash: '0x123',
date: '2023-01-01T00:00:00Z',
...props,
};
return (
<DataList.Root entityLabel="Daos">
<DataList.Container>
<TransactionDataListItemStructure {...defaultProps} />
</DataList.Container>
</DataList.Root>
);
};

it('renders the transaction type heading', () => {
const type = TransactionType.ACTION;
render(createTestComponent({ type }));
const transactionTypeHeading = screen.getByText('Smart contract action');
expect(transactionTypeHeading).toBeInTheDocument();
});

it('renders the token value and symbol in a deposit', () => {
const tokenSymbol = 'ETH';
const tokenAmount = 10;
const type = TransactionType.DEPOSIT;
render(createTestComponent({ tokenSymbol, tokenAmount, type }));
const tokenPrintout = screen.getByText('10 ETH');
expect(tokenPrintout).toBeInTheDocument();
});

it('renders the formatted USD estimate', () => {
const tokenPrice = 100;
const tokenAmount = 10;
const type = TransactionType.DEPOSIT;
const formattedEstimate = formatterUtils.formatNumber(tokenPrice * tokenAmount, {
format: NumberFormat.FIAT_TOTAL_SHORT,
});
render(createTestComponent({ tokenPrice, tokenAmount, type }));
const formattedUsdEstimate = screen.getByText(formattedEstimate as string);
expect(formattedUsdEstimate).toBeInTheDocument();
});

it('renders a failed transaction indicator alongside the transaction type', () => {
render(createTestComponent({ type: TransactionType.DEPOSIT, status: TransactionStatus.FAILED }));
const failedTransactionText = screen.getByText('Deposit');
expect(failedTransactionText).toBeInTheDocument();
const closeIcon = screen.getByTestId('CLOSE');
expect(closeIcon).toBeInTheDocument();
});

it('renders the provided timestamp correctly', () => {
const date = '2000-01-01T00:00:00Z';
render(createTestComponent({ date }));
expect(screen.getByText(date)).toBeInTheDocument();
});

it('renders with the correct block explorer URL', async () => {
const chainId = 1;
const hash = '0x123';
render(createTestComponent({ chainId, hash }));

await waitFor(() => {
const linkElement = screen.getByRole<HTMLAnchorElement>('link');
expect(linkElement).toHaveAttribute('href', 'https://etherscan.io/tx/0x123');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { type Hash } from 'viem';
import { type IDataListItemProps } from '../../../../../core';

export enum TransactionStatus {
PENDING = 'PENDING',
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
}

export enum TransactionType {
DEPOSIT = 'DEPOSIT',
WITHDRAW = 'WITHDRAW',
ACTION = 'ACTION',
}

export interface ITransactionDataListItemProps extends IDataListItemProps {
/**
* The chain ID of the transaction.
*/
chainId: number;
/**
* The address of the token.
*/
tokenAddress?: string;
/**
* The symbol of the token, e.g. 'ETH' as a string
*/
tokenSymbol?: string;
/**
* The token value in the transaction.
*/
tokenAmount?: number | string;
/**
* The estimated fiat value of the transaction.
*/
tokenPrice?: number | string;
/**
* The type of transaction.
* @default TransactionType.ACTION
*/
type?: TransactionType;
/**
* The current status of a blockchain transaction on the network.
* @default TransactionStatus.PENDING
*/
status?: TransactionStatus;
/**
* The Unix timestamp of the transaction.
*/
date: string;
/**
* The transaction hash.
*/
hash: Hash;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Meta, StoryObj } from '@storybook/react';
import { DataList } from '../../../../../core';
import { TransactionDataListItemStructure } from './transactionDataListItemStructure';
import { TransactionStatus, TransactionType } from './transactionDataListItemStructure.api';

const meta: Meta<typeof TransactionDataListItemStructure> = {
title: 'Modules/Components/Transaction/TransactionDataListItem.Structure',
component: TransactionDataListItemStructure,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/P0GeJKqILL7UXvaqu5Jj7V/v1.1.0?type=design&node-id=445-5113&mode=design&t=qzF3muTU7z33q8EX-4',
},
},
argTypes: {
hash: {
control: 'text',
},
},
};

type Story = StoryObj<typeof TransactionDataListItemStructure>;

/**
* Default usage example of the TransactionDataList module component.
*/
export const Default: Story = {
render: (args) => (
<DataList.Root entityLabel="Transactions">
<DataList.Container>
<TransactionDataListItemStructure {...args} />
</DataList.Container>
</DataList.Root>
),
};

export const Withdraw: Story = {
args: {
status: TransactionStatus.SUCCESS,
type: TransactionType.WITHDRAW,
tokenAmount: 10,
tokenSymbol: 'ETH',
},
render: (args) => (
<DataList.Root entityLabel="Transactions">
<DataList.Container>
<TransactionDataListItemStructure {...args} />
</DataList.Container>
</DataList.Root>
),
};

export const Failed: Story = {
args: {
status: TransactionStatus.FAILED,
type: TransactionType.DEPOSIT,
tokenSymbol: 'ETH',
tokenAmount: 10,
tokenPrice: 100,
},
render: (args) => (
<DataList.Root entityLabel="Transactions">
<DataList.Container>
<TransactionDataListItemStructure {...args} />
</DataList.Container>
</DataList.Root>
),
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import classNames from 'classnames';
import { useChains } from 'wagmi';
import {
AvatarIcon,
DataList,
IconType,
NumberFormat,
Spinner,
formatterUtils,
type AvatarIconVariant,
} from '../../../../../core';
import {
TransactionStatus,
TransactionType,
type ITransactionDataListItemProps,
} from './transactionDataListItemStructure.api';

const txHeadingStringList: Record<TransactionType, string> = {
[TransactionType.DEPOSIT]: 'Deposit',
[TransactionType.WITHDRAW]: 'Withdraw',
[TransactionType.ACTION]: 'Smart contract action',
};

const txIconTypeList: Record<TransactionType, IconType> = {
[TransactionType.DEPOSIT]: IconType.DEPOSIT,
[TransactionType.WITHDRAW]: IconType.WITHDRAW,
[TransactionType.ACTION]: IconType.BLOCKCHAIN_SMARTCONTRACT,
};

const txVariantList: Record<TransactionType, AvatarIconVariant> = {
[TransactionType.DEPOSIT]: 'success',
[TransactionType.WITHDRAW]: 'warning',
[TransactionType.ACTION]: 'info',
};

export const TransactionDataListItemStructure: React.FC<ITransactionDataListItemProps> = (props) => {
const {
chainId,
tokenAddress,
tokenSymbol,
tokenAmount,
tokenPrice,
type = TransactionType.ACTION,
status = TransactionStatus.PENDING,
// TO-DO: implement formatter decision
date,
hash,
href,
className,
...otherProps
} = props;
const chains = useChains();

const matchingChain = chains?.find((chain) => chain.id === chainId);
const blockExplorerBaseUrl = matchingChain?.blockExplorers?.default?.url;
const blockExplorerAssembledHref = blockExplorerBaseUrl ? `${blockExplorerBaseUrl}/tx/${hash}` : undefined;

const parsedHref = blockExplorerAssembledHref ?? href;

const formattedTokenValue = formatterUtils.formatNumber(tokenAmount, {
format: NumberFormat.TOKEN_AMOUNT_SHORT,
});

const fiatValue = Number(tokenAmount ?? 0) * Number(tokenPrice ?? 0);
const formattedTokenPrice = formatterUtils.formatNumber(fiatValue, {
format: NumberFormat.FIAT_TOTAL_SHORT,
});

const formattedTokenAmount =
type === TransactionType.ACTION || tokenAmount == null ? '-' : `${formattedTokenValue} ${tokenSymbol}`;

return (
<DataList.Item
className={classNames('px-4 py-0 md:px-6', className)}
href={parsedHref}
target="_blank"
{...otherProps}
>
<div className="flex w-full justify-between py-3 md:py-4">
<div className="flex items-center gap-x-3 md:gap-x-4">
{status === TransactionStatus.SUCCESS && (
<AvatarIcon
className="shrink-0"
variant={txVariantList[type]}
icon={txIconTypeList[type]}
responsiveSize={{ md: 'md' }}
/>
)}
{status === TransactionStatus.FAILED && (
<AvatarIcon
className="shrink-0"
variant="critical"
icon={IconType.CLOSE}
responsiveSize={{ md: 'md' }}
/>
)}
{status === TransactionStatus.PENDING && (
<div className="flex size-6 shrink-0 items-center justify-center md:size-8">
<Spinner className="transition" variant="neutral" responsiveSize={{ md: 'lg' }} />
</div>
)}
<div className="flex w-full flex-col items-start gap-y-0.5">
<span className="text-sm font-normal leading-tight text-neutral-800 md:text-base">
{txHeadingStringList[type]}
</span>
<p className="text-sm font-normal leading-tight text-neutral-500 md:text-base">{date}</p>
</div>
</div>

<div className="flex flex-col items-end gap-y-0.5">
<span className="text-sm font-normal leading-tight text-neutral-800 md:text-base">
{formattedTokenAmount}
</span>
<span className="text-sm font-normal leading-tight text-neutral-500 md:text-base">
{formattedTokenPrice}
</span>
</div>
</div>
</DataList.Item>
);
};
Loading

0 comments on commit ece7e60

Please sign in to comment.