Merge pull request #85 from vegaprotocol/feat/40-tx-styling
Feat/40 Styles for transaction and transaction details page
This commit is contained in:
commit
c888ca1087
@ -7,4 +7,5 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
coverageDirectory: '../../coverage/apps/explorer',
|
coverageDirectory: '../../coverage/apps/explorer',
|
||||||
|
setupFilesAfterEnv: ['./src/app/setupTests.ts'],
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,9 @@ export const RenderFetched = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <StatusMessage className={className}>Error: {error}</StatusMessage>;
|
return (
|
||||||
|
<StatusMessage className={className}>Error retrieving data</StatusMessage>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
|
@ -4,7 +4,7 @@ interface SecondsAgoProps {
|
|||||||
date: string | undefined;
|
date: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SecondsAgo = ({ date }: SecondsAgoProps) => {
|
export const SecondsAgo = ({ date, ...props }: SecondsAgoProps) => {
|
||||||
const [now, setNow] = useState(Date.now());
|
const [now, setNow] = useState(Date.now());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -18,10 +18,11 @@ export const SecondsAgo = ({ date }: SecondsAgoProps) => {
|
|||||||
return <>Date unknown</>;
|
return <>Date unknown</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const timeAgoInSeconds = Math.floor((now - new Date(date).getTime()) / 1000);
|
const timeAgoInSeconds = Math.floor((now - new Date(date).getTime()) / 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div {...props}>
|
||||||
{timeAgoInSeconds === 1 ? '1 second' : `${timeAgoInSeconds} seconds`} ago
|
{timeAgoInSeconds === 1 ? '1 second' : `${timeAgoInSeconds} seconds`} ago
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { render, screen, act } from '@testing-library/react';
|
||||||
|
import { SecondsAgo } from './index';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Seconds ago', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const dateInString = new Date().toString();
|
||||||
|
render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('test-seconds-ago')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the correct amount of seconds ago', () => {
|
||||||
|
const secondsToWait = 10;
|
||||||
|
const dateInString = new Date().toString();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(secondsToWait * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('test-seconds-ago')).toHaveTextContent(
|
||||||
|
`${secondsToWait} seconds ago`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -6,7 +6,15 @@ interface StatusMessageProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusMessage = ({ children, className }: StatusMessageProps) => {
|
export const StatusMessage = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: StatusMessageProps) => {
|
||||||
const classes = classnames('font-alpha text-h4 mb-28', className);
|
const classes = classnames('font-alpha text-h4 mb-28', className);
|
||||||
return <h3 className={classes}>{children}</h3>;
|
return (
|
||||||
|
<h3 className={classes} {...props}>
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { StatusMessage } from './index';
|
||||||
|
|
||||||
|
describe('Status message', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
render(
|
||||||
|
<StatusMessage data-testid="status-message-test">test</StatusMessage>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('status-message-test')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
79
apps/explorer/src/app/components/table/table.spec.tsx
Normal file
79
apps/explorer/src/app/components/table/table.spec.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { Table, TableRow, TableHeader, TableCell } from './index';
|
||||||
|
|
||||||
|
describe('Renders all table components', () => {
|
||||||
|
render(
|
||||||
|
<Table data-testid="test-table">
|
||||||
|
<TableRow data-testid="test-tr">
|
||||||
|
<TableHeader data-testid="test-th">Title</TableHeader>
|
||||||
|
<TableCell data-testid="test-td">Content</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('test-table')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('test-tr')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('test-th')).toHaveTextContent('Title');
|
||||||
|
expect(screen.getByTestId('test-td')).toHaveTextContent('Content');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Table row', () => {
|
||||||
|
it('should include classes based on custom "modifier" prop', () => {
|
||||||
|
render(
|
||||||
|
<Table>
|
||||||
|
<TableRow data-testid="modifier-test" modifier="bordered">
|
||||||
|
<TableCell>With modifier</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('modifier-test')).toHaveClass('border-white-40');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Table header', () => {
|
||||||
|
it('should accept props i.e. scope="row"', () => {
|
||||||
|
render(
|
||||||
|
<Table>
|
||||||
|
<TableRow>
|
||||||
|
<TableHeader data-testid="props-test" scope="row">
|
||||||
|
Test
|
||||||
|
</TableHeader>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('props-test')).toHaveAttribute('scope');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include custom class based on scope="row"', () => {
|
||||||
|
render(
|
||||||
|
<Table>
|
||||||
|
<TableRow>
|
||||||
|
<TableHeader data-testid="scope-class-test" scope="row">
|
||||||
|
With scope attribute
|
||||||
|
</TableHeader>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('scope-class-test')).toHaveClass('text-left');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Table cell', () => {
|
||||||
|
it('should include class based on custom "modifier" prop', () => {
|
||||||
|
render(
|
||||||
|
<Table>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell data-testid="modifier-class-test" modifier="bordered">
|
||||||
|
With modifier
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('modifier-class-test')).toHaveClass('py-4');
|
||||||
|
});
|
||||||
|
});
|
39
apps/explorer/src/app/components/truncate/truncate.spec.tsx
Normal file
39
apps/explorer/src/app/components/truncate/truncate.spec.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { TruncateInline } from './truncate';
|
||||||
|
|
||||||
|
describe('Truncate', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
render(
|
||||||
|
<TruncateInline data-testid="truncate-test" text={'Texty McTextFace'} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('truncate-test')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it truncates as expected', () => {
|
||||||
|
const test = 'randomstringblahblah';
|
||||||
|
const startChars = 3;
|
||||||
|
const endChars = 3;
|
||||||
|
const expectedString = `${test.slice(0, startChars)}…${test.slice(
|
||||||
|
-endChars
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TruncateInline text={test} startChars={startChars} endChars={endChars} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(expectedString)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it doesn't truncate if the string is too short", () => {
|
||||||
|
const test = 'randomstringblahblah';
|
||||||
|
const startChars = test.length;
|
||||||
|
const endChars = test.length;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TruncateInline text={test} startChars={startChars} endChars={endChars} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(test)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -22,6 +22,7 @@ export function TruncateInline({
|
|||||||
children,
|
children,
|
||||||
startChars,
|
startChars,
|
||||||
endChars,
|
endChars,
|
||||||
|
...props
|
||||||
}: TruncateInlineProps) {
|
}: TruncateInlineProps) {
|
||||||
if (text === null) {
|
if (text === null) {
|
||||||
return <span data-testid="empty-truncation" />;
|
return <span data-testid="empty-truncation" />;
|
||||||
@ -31,6 +32,7 @@ export function TruncateInline({
|
|||||||
const wrapperProps = {
|
const wrapperProps = {
|
||||||
title: text,
|
title: text,
|
||||||
className,
|
className,
|
||||||
|
...props,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (children !== undefined) {
|
if (children !== undefined) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response';
|
import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response';
|
||||||
import { Table } from '../../table';
|
|
||||||
import { SyntaxHighlighter } from '../../syntax-highlighter';
|
import { SyntaxHighlighter } from '../../syntax-highlighter';
|
||||||
|
import { Table, TableRow, TableHeader, TableCell } from '../../table';
|
||||||
|
import { TxOrderType } from '../tx-order-type';
|
||||||
|
import { StatusMessage } from '../../status-message';
|
||||||
|
|
||||||
interface TxContentProps {
|
interface TxContentProps {
|
||||||
data: ChainExplorerTxResponse | undefined;
|
data: ChainExplorerTxResponse | undefined;
|
||||||
@ -8,29 +10,26 @@ interface TxContentProps {
|
|||||||
|
|
||||||
export const TxContent = ({ data }: TxContentProps) => {
|
export const TxContent = ({ data }: TxContentProps) => {
|
||||||
if (!data?.Command) {
|
if (!data?.Command) {
|
||||||
return <>Awaiting decoded transaction data</>;
|
return (
|
||||||
|
<StatusMessage>Could not retrieve transaction content</StatusMessage>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { marketId, type, side, size } = JSON.parse(data.Command);
|
|
||||||
|
|
||||||
const displayCode = {
|
|
||||||
market: marketId,
|
|
||||||
type,
|
|
||||||
side,
|
|
||||||
size,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table>
|
<Table className="mb-12">
|
||||||
<tr>
|
<TableRow modifier="bordered">
|
||||||
<td>Type</td>
|
<TableHeader scope="row" className="w-[160px]">
|
||||||
<td>{data.Type}</td>
|
Type
|
||||||
</tr>
|
</TableHeader>
|
||||||
|
<TableCell modifier="bordered">
|
||||||
|
<TxOrderType orderType={data.Type} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<h3>Decoded transaction content</h3>
|
<h3 className="font-mono mb-8">Decoded transaction content</h3>
|
||||||
<SyntaxHighlighter data={displayCode} />
|
<SyntaxHighlighter data={JSON.parse(data.Command)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,50 +1,63 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Routes } from '../../../routes/router-config';
|
import { Routes } from '../../../routes/router-config';
|
||||||
import { Result } from '../../../routes/txs/tendermint-transaction-response.d';
|
import { Result } from '../../../routes/txs/tendermint-transaction-response.d';
|
||||||
import { Table, TableRow, TableCell } from '../../table';
|
import { Table, TableRow, TableCell, TableHeader } from '../../table';
|
||||||
|
import { TruncateInline } from '../../truncate/truncate';
|
||||||
|
|
||||||
interface TxDetailsProps {
|
interface TxDetailsProps {
|
||||||
txData: Result | undefined;
|
txData: Result | undefined;
|
||||||
pubKey: string | undefined;
|
pubKey: string | undefined;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
const truncateLength = 30;
|
||||||
|
|
||||||
|
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
|
||||||
if (!txData) {
|
if (!txData) {
|
||||||
return <>Awaiting Tendermint transaction details</>;
|
return <>Awaiting Tendermint transaction details</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table className={className}>
|
||||||
<TableRow>
|
<TableRow modifier="bordered">
|
||||||
<TableCell>Hash</TableCell>
|
<TableCell>Hash</TableCell>
|
||||||
<TableCell data-testid="hash">{txData.hash}</TableCell>
|
<TableCell modifier="bordered" data-testid="hash">
|
||||||
|
{txData.hash}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{pubKey ? (
|
<TableRow modifier="bordered">
|
||||||
<TableRow>
|
<TableHeader scope="row" className="w-[160px]">
|
||||||
<td>Submitted by</td>
|
Submitted by
|
||||||
<td data-testid="submitted-by">
|
</TableHeader>
|
||||||
<Link to={`/${Routes.PARTIES}/${pubKey}`}>{pubKey}</Link>
|
<TableCell modifier="bordered" data-testid="submitted-by">
|
||||||
</td>
|
<Link
|
||||||
</TableRow>
|
className="text-vega-yellow"
|
||||||
) : (
|
to={`/${Routes.PARTIES}/${pubKey}`}
|
||||||
<TableRow>
|
>
|
||||||
<td>Submitted by</td>
|
{pubKey}
|
||||||
<td>Awaiting decoded transaction data</td>
|
</Link>
|
||||||
</TableRow>
|
</TableCell>
|
||||||
)}
|
</TableRow>
|
||||||
{txData.height ? (
|
<TableRow modifier="bordered">
|
||||||
<TableRow>
|
<TableCell>Block</TableCell>
|
||||||
<td>Block</td>
|
<TableCell modifier="bordered" data-testid="block">
|
||||||
<td data-testid="block">
|
<Link
|
||||||
<Link to={`/${Routes.BLOCKS}/${txData.height}`}>
|
className="text-vega-yellow"
|
||||||
{txData.height}
|
to={`/${Routes.BLOCKS}/${txData.height}`}
|
||||||
</Link>
|
>
|
||||||
</td>
|
{txData.height}
|
||||||
</TableRow>
|
</Link>
|
||||||
) : null}
|
</TableCell>
|
||||||
<TableRow>
|
</TableRow>
|
||||||
<td>Encoded tnx</td>
|
<TableRow modifier="bordered">
|
||||||
<td data-testid="encoded-tnx">{txData.tx}</td>
|
<TableCell>Encoded tnx</TableCell>
|
||||||
|
<TableCell modifier="bordered" data-testid="encoded-tnx">
|
||||||
|
<TruncateInline
|
||||||
|
text={txData.tx}
|
||||||
|
startChars={truncateLength}
|
||||||
|
endChars={truncateLength}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
|
43
apps/explorer/src/app/components/txs/tx-order-type/index.tsx
Normal file
43
apps/explorer/src/app/components/txs/tx-order-type/index.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
interface TxOrderTypeProps {
|
||||||
|
orderType: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StringMap {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using https://github.com/vegaprotocol/protos/blob/e0f646ce39aab1fc66a9200ceec0262306d3beb3/commands/transaction.go#L93 as a reference
|
||||||
|
const displayString: StringMap = {
|
||||||
|
OrderSubmission: 'Order Submission',
|
||||||
|
OrderCancellation: 'Order Cancellation',
|
||||||
|
OrderAmendment: 'Order Amendment',
|
||||||
|
VoteSubmission: 'Vote Submission',
|
||||||
|
WithdrawSubmission: 'Withdraw Submission',
|
||||||
|
LiquidityProvisionSubmission: 'Liquidity Provision',
|
||||||
|
LiquidityProvisionCancellation: 'Liquidity Cancellation',
|
||||||
|
LiquidityProvisionAmendment: 'Liquidity Amendment',
|
||||||
|
ProposalSubmission: 'Governance Proposal',
|
||||||
|
AnnounceNode: 'Node Announcement',
|
||||||
|
NodeVote: 'Node Vote',
|
||||||
|
NodeSignature: 'Node Signature',
|
||||||
|
ChainEvent: 'Chain Event',
|
||||||
|
OracleDataSubmission: 'Oracle Data',
|
||||||
|
DelegateSubmission: 'Delegation',
|
||||||
|
UndelegateSubmission: 'Undelegation',
|
||||||
|
KeyRotateSubmission: 'Key Rotation',
|
||||||
|
StateVariableProposal: 'State Variable Proposal',
|
||||||
|
Transfer: 'Transfer',
|
||||||
|
CancelTransfer: 'Cancel Transfer',
|
||||||
|
ValidatorHeartbeat: 'Validator Heartbeat',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TxOrderType = ({ orderType, className }: TxOrderTypeProps) => {
|
||||||
|
return (
|
||||||
|
<Lozenge className={className}>
|
||||||
|
{displayString[orderType] || orderType}
|
||||||
|
</Lozenge>
|
||||||
|
);
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { DATA_SOURCES } from '../../../config';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { RenderFetched } from '../../render-fetched';
|
import { RenderFetched } from '../../render-fetched';
|
||||||
import { TruncateInline } from '../../truncate/truncate';
|
import { TruncateInline } from '../../truncate/truncate';
|
||||||
|
import { TxOrderType } from '../tx-order-type';
|
||||||
|
|
||||||
interface TxsPerBlockProps {
|
interface TxsPerBlockProps {
|
||||||
blockHeight: string | undefined;
|
blockHeight: string | undefined;
|
||||||
@ -62,7 +63,9 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
|||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{Type}</td>
|
<td>
|
||||||
|
<TxOrderType className="mb-4" orderType={Type} />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { TxContent, TxDetails } from '../../../components/txs';
|
|
||||||
import { DATA_SOURCES } from '../../../config';
|
|
||||||
import useFetch from '../../../hooks/use-fetch';
|
import useFetch from '../../../hooks/use-fetch';
|
||||||
import { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
|
|
||||||
import { TendermintTransactionResponse } from '../tendermint-transaction-response.d';
|
import { TendermintTransactionResponse } from '../tendermint-transaction-response.d';
|
||||||
|
import { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
|
||||||
|
import { DATA_SOURCES } from '../../../config';
|
||||||
|
import { TxContent, TxDetails } from '../../../components/txs';
|
||||||
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
|
import { RenderFetched } from '../../../components/render-fetched';
|
||||||
|
|
||||||
const Tx = () => {
|
const Tx = () => {
|
||||||
const { txHash } = useParams<{ txHash: string }>();
|
const { txHash } = useParams<{ txHash: string }>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: { data: transactionData },
|
state: { data: tTxData, loading: tTxLoading, error: tTxError },
|
||||||
} = useFetch<TendermintTransactionResponse>(
|
} = useFetch<TendermintTransactionResponse>(
|
||||||
`${DATA_SOURCES.tendermintUrl}/tx?hash=${txHash}`
|
`${DATA_SOURCES.tendermintUrl}/tx?hash=${txHash}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: { data: decodedData },
|
state: { data: ceTxData, loading: ceTxLoading, error: ceTxError },
|
||||||
} = useFetch<ChainExplorerTxResponse>(DATA_SOURCES.chainExplorerUrl, {
|
} = useFetch<ChainExplorerTxResponse>(DATA_SOURCES.chainExplorerUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -25,13 +29,20 @@ const Tx = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h1>Transaction details</h1>
|
<RouteTitle>Transaction details</RouteTitle>
|
||||||
<TxDetails
|
|
||||||
txData={transactionData?.result}
|
<RenderFetched error={tTxError} loading={tTxLoading}>
|
||||||
pubKey={decodedData?.PubKey}
|
<TxDetails
|
||||||
/>
|
className="mb-28"
|
||||||
<h2>Transaction content</h2>
|
txData={tTxData?.result}
|
||||||
<TxContent data={decodedData} />
|
pubKey={ceTxData?.PubKey}
|
||||||
|
/>
|
||||||
|
</RenderFetched>
|
||||||
|
|
||||||
|
<h2 className="text-h4 uppercase mb-16">Transaction content</h2>
|
||||||
|
<RenderFetched error={ceTxError} loading={ceTxLoading}>
|
||||||
|
<TxContent data={ceTxData} />
|
||||||
|
</RenderFetched>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,7 @@ module.exports = {
|
|||||||
prompt: '#EDFF22',
|
prompt: '#EDFF22',
|
||||||
success: '#26FF8A',
|
success: '#26FF8A',
|
||||||
help: '#494949',
|
help: '#494949',
|
||||||
|
highlight: '#E5E5E5',
|
||||||
},
|
},
|
||||||
'intent-background': {
|
'intent-background': {
|
||||||
danger: '#9E0025', // for white text
|
danger: '#9E0025', // for white text
|
||||||
@ -93,6 +94,14 @@ module.exports = {
|
|||||||
1: '1px',
|
1: '1px',
|
||||||
4: '4px',
|
4: '4px',
|
||||||
},
|
},
|
||||||
|
borderRadius: {
|
||||||
|
none: '0',
|
||||||
|
sm: '0.125rem',
|
||||||
|
DEFAULT: '0.225rem',
|
||||||
|
md: '0.3rem',
|
||||||
|
lg: '0.5rem',
|
||||||
|
full: '9999px',
|
||||||
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
mono: defaultTheme.fontFamily.mono,
|
mono: defaultTheme.fontFamily.mono,
|
||||||
serif: defaultTheme.fontFamily.serif,
|
serif: defaultTheme.fontFamily.serif,
|
||||||
|
1
libs/ui-toolkit/src/components/lozenge/index.ts
Normal file
1
libs/ui-toolkit/src/components/lozenge/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lozenge';
|
10
libs/ui-toolkit/src/components/lozenge/lozenge.spec.tsx
Normal file
10
libs/ui-toolkit/src/components/lozenge/lozenge.spec.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { Lozenge } from './lozenge';
|
||||||
|
|
||||||
|
describe('Lozenge', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<Lozenge>Lozenge</Lozenge>);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
36
libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx
Normal file
36
libs/ui-toolkit/src/components/lozenge/lozenge.stories.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Story, Meta } from '@storybook/react';
|
||||||
|
import { Lozenge } from './lozenge';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Lozenge,
|
||||||
|
title: 'Lozenge',
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story = (args) => <Lozenge {...args}>lozenge</Lozenge>;
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
|
||||||
|
export const WithDetails = Template.bind({});
|
||||||
|
WithDetails.args = {
|
||||||
|
details: 'details text',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Highlight = Template.bind({});
|
||||||
|
Highlight.args = {
|
||||||
|
variant: 'highlight',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Success = Template.bind({});
|
||||||
|
Success.args = {
|
||||||
|
variant: 'success',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Warning = Template.bind({});
|
||||||
|
Warning.args = {
|
||||||
|
variant: 'warning',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Danger = Template.bind({});
|
||||||
|
Danger.args = {
|
||||||
|
variant: 'danger',
|
||||||
|
};
|
38
libs/ui-toolkit/src/components/lozenge/lozenge.tsx
Normal file
38
libs/ui-toolkit/src/components/lozenge/lozenge.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
interface LozengeProps {
|
||||||
|
children: ReactNode;
|
||||||
|
variant?: 'success' | 'warning' | 'danger' | 'highlight';
|
||||||
|
className?: string;
|
||||||
|
details?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWrapperClasses = (className: LozengeProps['className']) => {
|
||||||
|
return classNames('inline-flex items-center gap-4', className);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLozengeClasses = (variant: LozengeProps['variant']) => {
|
||||||
|
return classNames(['rounded-md', 'font-mono', 'leading-none', 'p-4'], {
|
||||||
|
'bg-intent-success text-black': variant === 'success',
|
||||||
|
'bg-intent-danger text-white': variant === 'danger',
|
||||||
|
'bg-intent-warning text-black': variant === 'warning',
|
||||||
|
'bg-intent-highlight text-black': variant === 'highlight',
|
||||||
|
'bg-intent-help text-white': !variant,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Lozenge = ({
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
className,
|
||||||
|
details,
|
||||||
|
}: LozengeProps) => {
|
||||||
|
return (
|
||||||
|
<span className={getWrapperClasses(className)}>
|
||||||
|
<span className={getLozengeClasses(variant)}>{children}</span>
|
||||||
|
|
||||||
|
{details && <span>{details}</span>}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
@ -10,6 +10,7 @@ export { FormGroup } from './components/form-group';
|
|||||||
export { Icon } from './components/icon';
|
export { Icon } from './components/icon';
|
||||||
export { Input } from './components/input';
|
export { Input } from './components/input';
|
||||||
export { InputError } from './components/input-error';
|
export { InputError } from './components/input-error';
|
||||||
|
export { Lozenge } from './components/lozenge';
|
||||||
export { Loader } from './components/loader';
|
export { Loader } from './components/loader';
|
||||||
export { Select } from './components/select';
|
export { Select } from './components/select';
|
||||||
export { Splash } from './components/splash';
|
export { Splash } from './components/splash';
|
||||||
|
Loading…
Reference in New Issue
Block a user