Skip to content

Commit

Permalink
feat: add TxCard component and related (#37)
Browse files Browse the repository at this point in the history
⭐ Closes FRO-471
⭐ Closes FRO-472
⭐ Closes FRO-473
  • Loading branch information
pedronauck authored Sep 19, 2023
1 parent 7906d92 commit 985fa0f
Show file tree
Hide file tree
Showing 19 changed files with 9,769 additions and 102 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@
"@fuels/jest": "^0.0.9",
"@fuels/prettier-config": "^0.0.9",
"@fuels/ts-config": "^0.0.9",
"@fuels/tsup-config": "^0.0.9",
"jest": "29.7.0",
"lint-staged": "14.0.1"
},
"devDependencies": {
"@fuels/tsup-config": "^0.0.9",
"@swc/core": "1.3.84",
"@swc/jest": "0.2.29",
"@types/jest": "29.5.4",
"@types/node": "20.6.0",
"eslint": "^8.49.0",
"husky": "^8.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.3",
"tsup": "7.2.0",
"turbo": "^1.10.13",
"typescript": "5.2.2",
"updates": "^15.0.2"
Expand Down
3 changes: 3 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
"ts:check": "tsc --noEmit"
},
"dependencies": {
"@fuel-explorer/graphql": "workspace:*",
"@fuel-ui/css": "0.21.0-pr-305-04b8434",
"@fuel-ui/icons": "0.21.0-pr-305-04b8434",
"@fuel-ui/react": "0.21.0-pr-305-04b8434",
"csstype": "3.1.2",
"dayjs": "1.11.9",
"fuels": "0.57.0",
"next": "13.4.19",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/systems/Core/utils/dayjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);

export function fromNow(timestamp: string) {
return dayjs.unix(Number(timestamp)).fromNow();
}

export function unixTimestamp(timestamp: number) {
return dayjs(timestamp).unix().toString();
}

export { dayjs };
5 changes: 2 additions & 3 deletions packages/app/src/systems/Home/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Box, Icon } from '@fuel-ui/react';
import { Box, Link } from '@fuel-ui/react';

export function HomePage() {
return (
Expand All @@ -12,8 +12,7 @@ export function HomePage() {
is: ['centered'],
}}
>
<Icon icon="Calendar" />
Hello World
<Link href="/storybook">Go to Storybook</Link>
</Box.Flex>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Meta, StoryObj } from '@storybook/react';

import { TX_CONTRACT_CALL_MOCK } from '../__mocks__/tx';

import { TxCard } from './TxCard';

const meta: Meta<typeof TxCard> = {
title: 'Transaction/TxCard',
component: TxCard,
};

export default meta;
type Story = StoryObj<typeof TxCard>;

export const Usage: Story = {
render: () => <TxCard tx={TX_CONTRACT_CALL_MOCK} css={{ maxW: 300 }} />,
};
70 changes: 70 additions & 0 deletions packages/app/src/systems/Transaction/component/TxCard/TxCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { cssObj } from '@fuel-ui/css';
import type { BaseProps } from '@fuel-ui/react';
import { Box, Card, Text } from '@fuel-ui/react';
import { bn } from 'fuels';
import { fromNow } from '~/systems/Core/utils/dayjs';

import type { TxItem } from '../../types';
import { TxTitle } from '../TxTitle/TxTitle';

type TxCardProps = BaseProps<{
tx: TxItem;
}>;

export function TxCard({ tx, css, ...props }: TxCardProps) {
return (
<Card {...props} css={{ ...styles.root, ...css }}>
<TxTitle
type={tx.type}
status={tx.status}
txHash={tx.transaction.id}
css={styles.title}
/>
<Box.VStack css={styles.body}>
<Box.Flex justify="between">
<Text leftIcon="Users">4 accounts</Text>
</Box.Flex>
<Box.Flex justify="between" css={styles.row}>
<Text leftIcon="Transfer">{tx.totalOperations} operations</Text>
<Text leftIcon="ClockHour1" className="small">
{fromNow(tx.timestamp)}
</Text>
</Box.Flex>
<Box.Flex justify="between" css={styles.row}>
<Text leftIcon="Coins">{tx.totalAssets} assets</Text>
<Text leftIcon="GasStation" className="small">
{bn(tx.gasUsed).format({ units: 3 })} ETH
</Text>
</Box.Flex>
</Box.VStack>
</Card>
);
}

const styles = {
root: cssObj({
transition: 'all 0.2s ease-out',

'&:hover': {
borderColor: '$borderHover',
},
}),
title: cssObj({
py: '$4',
px: '$4',
}),
body: cssObj({
borderTop: '1px solid $cardBorder',
py: '$4',
px: '$4',
}),
row: cssObj({
'.fuel_Text:first-of-type': {
flex: 1,
},
'.small, .fuel_Icon': {
fontSize: '$sm',
color: '$textMuted',
},
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Box } from '@fuel-ui/react';
import type { Meta, StoryObj } from '@storybook/react';

import type { TxStatus, TxType } from '../../types';
import { TX_STATUS, TX_TYPES } from '../../types';

import { TxIcon } from './TxIcon';

const meta: Meta<typeof TxIcon> = {
title: 'Transaction/TxIcon',
component: TxIcon,
};

export default meta;
type Story = StoryObj<typeof TxIcon>;

export const Usage: Story = {
render: () => (
<Box.VStack>
{TX_TYPES.map((type) => (
<Box.HStack key={type}>
{TX_STATUS.map((status) => (
<TxIcon
key={status}
type={type as TxType}
status={status as TxStatus}
/>
))}
</Box.HStack>
))}
</Box.VStack>
),
};

export const Sizes: Story = {
render: () => (
<Box.HStack>
<TxIcon type="contract-call" status="success" size="sm" />
<TxIcon type="contract-call" status="success" size="md" />
<TxIcon type="contract-call" status="success" size="lg" />
</Box.HStack>
),
};
73 changes: 73 additions & 0 deletions packages/app/src/systems/Transaction/component/TxIcon/TxIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { LayerIntent } from '@fuel-ui/css';
import { Badge, Icon } from '@fuel-ui/react';

import { type TxStatus, type TxType } from '../../types';

type TxIconProps = {
type: TxType;
status: TxStatus;
size?: 'sm' | 'md' | 'lg';
};

const TX_ICON_MAP: Record<TxType, string> = {
'contract-call': 'Code',
mint: 'Coins',
transfer: 'Transfer',
burn: 'Flame',
};

const TX_INTENT_MAP: Record<TxStatus, LayerIntent> = {
success: 'success',
error: 'error',
pending: 'warning',
idle: 'base',
};

const TX_STATUS_MAP: Record<TxStatus, string> = {
success: 'Success',
error: 'Error',
pending: 'Pending',
idle: 'Idle',
};

export function TxIcon({ type, status, size = 'md' }: TxIconProps) {
const icon = <Icon icon={TX_ICON_MAP[type]} />;
const label = TX_STATUS_MAP[status];
return (
<Badge
variant="ghost"
intent={TX_INTENT_MAP[status]}
aria-label={label}
css={styles.root}
data-size={size}
>
{icon}
</Badge>
);
}

const styles = {
root: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',

'&[data-size="sm"]': {
width: '$8',
height: '$8',
},
'&[data-size="md"]': {
width: '$10',
height: '$10',
},
'&[data-size="lg"]': {
width: '$12',
height: '$12',

'.fuel_Icon svg': {
width: '24px !important',
height: '24px !important',
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Box } from '@fuel-ui/react';
import type { Meta, StoryObj } from '@storybook/react';

import { TX_TYPES, type TxType } from '../../types';

import { TxTitle } from './TxTitle';

const meta: Meta<typeof TxTitle> = {
title: 'Transaction/TxTitle',
component: TxTitle,
};

export default meta;
type Story = StoryObj<typeof TxTitle>;

export const Usage: Story = {
render: () => (
<TxTitle
type="contract-call"
status="success"
txHash="0x78d13f111bf301324f34f2a7eaffc546d39598d156af38e7c4ef9fe61ea2c46a"
/>
),
};

export const AllTypes: Story = {
render: () => (
<Box.VStack>
{TX_TYPES.map((type) => (
<TxTitle
key={type}
type={type as TxType}
status="idle"
txHash="0x78d13f111bf301324f34f2a7eaffc546d39598d156af38e7c4ef9fe61ea2c46a"
/>
))}
</Box.VStack>
),
};
53 changes: 53 additions & 0 deletions packages/app/src/systems/Transaction/component/TxTitle/TxTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { cssObj } from '@fuel-ui/css';
import type { BaseProps } from '@fuel-ui/react';
import { Box, Copyable, Heading } from '@fuel-ui/react';
import { useMemo } from 'react';
import { shortAddress } from '~/systems/Core/utils/address';

import type { TxStatus, TxType } from '../../types';
import { TxIcon } from '../TxIcon/TxIcon';

const TITLE_MAP: Record<TxType, string> = {
'contract-call': 'Contract Call',
transfer: 'Transfer',
mint: 'Mint',
burn: 'Burn',
};

type TxTitleProps = BaseProps<{
status: TxStatus;
type: TxType;
txHash: string;
}>;

export function TxTitle({ status, type, txHash, css, ...props }: TxTitleProps) {
const title = useMemo(() => TITLE_MAP[type], [type]);
return (
<Box.HStack {...props} css={{ ...styles.root, ...css }} gap="$3">
<TxIcon type={type} status={status} />
<Box>
<Heading as="h4">{title}</Heading>
<Copyable value={txHash}>{shortAddress(txHash)}</Copyable>
</Box>
</Box.HStack>
);
}

const styles = {
root: cssObj({
alignItems: 'center',

'.fuel_Heading': {
margin: '$0',
lineHeight: '$tighter',
fontSize: '$md',
},
'.fuel_Copyable': {
fontSize: '$sm',
},
'.fuel_Copyable .fuel_Icon svg': {
width: '14px !important',
height: '14px !important',
},
}),
};
Loading

0 comments on commit 985fa0f

Please sign in to comment.