Feat/1416 tx detail (#1953)
* chore(explorer): add new vega colours to tw config * feat(explorer): add new nested data list component * feat(explorer): abstract page header with copy to new component * feat(explorer): update styles tx details page * fix(explorer): linting errors * fix(explorer): fix txs type * fix(explorer): fix strong typing * fix(explorer): fix styling error for e2e test
This commit is contained in:
parent
da99e731fa
commit
5a764d190f
@ -87,7 +87,7 @@ context('Asset page', { tags: '@regression' }, function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(223, 255, 11)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
|
@ -205,7 +205,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(223, 255, 11)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
|
@ -161,7 +161,7 @@ context('Parties page', { tags: '@regression' }, function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(223, 255, 11)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
|
@ -216,7 +216,7 @@ context('Validator page', { tags: '@smoke' }, function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(223, 255, 11)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
|
@ -0,0 +1 @@
|
||||
export * from './nested-data-list';
|
@ -0,0 +1,110 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
BORDER_COLOURS,
|
||||
NestedDataList,
|
||||
sortNestedDataByChildren,
|
||||
} from './nested-data-list';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const mockData = {
|
||||
nonce: '5980890939790185837',
|
||||
validatorHeartbeat: {
|
||||
nodeId: 'e07d2cd299659590c16ec1cc1c69936ad747083c379ea6b6cfeaa6e22c8af0cb',
|
||||
ethereumSignature: {
|
||||
value:
|
||||
'8b157cb43a716ad541065f643e38cd92d7b1857c7beeb7d81e878be4f9a48f5a346be973e478eba3c18907888678625ba6700b086f65406d5ce0af0cae1d419300',
|
||||
algo: 'eth',
|
||||
version: 0,
|
||||
},
|
||||
vegaSignature: {
|
||||
value:
|
||||
'abffba6ddf07b3a732214dd780c35c94f7f0aeb9c5e9ce7d1a3ee641926a6ac568a2ecf0160c859633d327712c7a1b1590db4245d40646559a1ab2cc44d6fa01',
|
||||
algo: 'vega/ed25519',
|
||||
version: 0,
|
||||
},
|
||||
},
|
||||
blockHeight: '174534',
|
||||
};
|
||||
|
||||
describe('NestedDataList', () => {
|
||||
it('should display the parent as a button', () => {
|
||||
const tree = render(<NestedDataList data={mockData} />);
|
||||
const { getAllByRole } = tree;
|
||||
expect(getAllByRole('button')[0]).toHaveTextContent('Validator Heartbeat');
|
||||
});
|
||||
|
||||
it('should display the children when a row is clicked', async () => {
|
||||
const tree = render(<NestedDataList data={mockData} />);
|
||||
const user = userEvent.setup();
|
||||
const { getAllByRole } = tree;
|
||||
|
||||
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
|
||||
const nestedContainer = parent[0].querySelector('[aria-hidden]');
|
||||
const expandBtn = parent[0].querySelector('button');
|
||||
expect(nestedContainer).toHaveAttribute('aria-hidden', 'true');
|
||||
await user.click(expandBtn as HTMLButtonElement);
|
||||
await waitFor(() => nestedContainer);
|
||||
expect(nestedContainer).toHaveAttribute('aria-hidden', 'false');
|
||||
});
|
||||
|
||||
it('add border to the title of the parent', () => {
|
||||
const tree = render(<NestedDataList data={mockData} />);
|
||||
const { getAllByRole } = tree;
|
||||
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
|
||||
expect(parent[0]).toHaveClass('pl-4 border-l-4');
|
||||
});
|
||||
|
||||
it('add a border the children with the same colour', () => {
|
||||
const tree = render(<NestedDataList data={mockData} />);
|
||||
const { getAllByRole } = tree;
|
||||
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
|
||||
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
|
||||
});
|
||||
|
||||
it('should repeat the border colours in the correct order', () => {
|
||||
const colourMockData = {
|
||||
t0: {
|
||||
t1: {
|
||||
t2: {
|
||||
t3: {
|
||||
t4: {
|
||||
t5: {
|
||||
t6: {
|
||||
t7: {
|
||||
t8: {
|
||||
hello: 'world',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const tree = render(<NestedDataList data={colourMockData} />);
|
||||
const { getByTestId } = tree;
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const item = getByTestId(`T${i}`);
|
||||
const expected = BORDER_COLOURS.dark[i % 5];
|
||||
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should sort the data by values with children', () => {
|
||||
const mockData = {
|
||||
nonce: '5980890939790185837',
|
||||
validatorHeartbeat: {
|
||||
nodeId:
|
||||
'e07d2cd299659590c16ec1cc1c69936ad747083c379ea6b6cfeaa6e22c8af0cb',
|
||||
someArray: ['0', '1', '2'],
|
||||
},
|
||||
blockHeight: '174534',
|
||||
};
|
||||
const expected = ['nonce', 'blockHeight', 'validatorHeartbeat'];
|
||||
const result = sortNestedDataByChildren(mockData);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
@ -0,0 +1,178 @@
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import isObject from 'lodash/isObject';
|
||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { VegaColours } from '@vegaprotocol/tailwindcss-config';
|
||||
import isArray from 'lodash/isArray';
|
||||
|
||||
export type UnknownObject = Record<string, unknown>;
|
||||
export type UnknownArray = unknown[];
|
||||
|
||||
interface NestedDataListProps {
|
||||
data: UnknownObject | UnknownArray;
|
||||
level?: number;
|
||||
}
|
||||
|
||||
interface NestedDataListItemProps {
|
||||
label: string;
|
||||
value: unknown;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const BORDER_COLOURS = {
|
||||
dark: [
|
||||
VegaColours.pink.dark,
|
||||
VegaColours.purple.dark,
|
||||
VegaColours.green.dark,
|
||||
VegaColours.blue.dark,
|
||||
VegaColours.yellow.dark,
|
||||
],
|
||||
light: [
|
||||
VegaColours.pink.DEFAULT,
|
||||
VegaColours.purple.DEFAULT,
|
||||
VegaColours.green.DEFAULT,
|
||||
VegaColours.blue.DEFAULT,
|
||||
VegaColours.yellow.DEFAULT,
|
||||
],
|
||||
};
|
||||
|
||||
const camelToTitle = (text: string) => {
|
||||
const result = text.replace(/([A-Z])/g, ' $1');
|
||||
return result.charAt(0).toUpperCase() + result.slice(1);
|
||||
};
|
||||
|
||||
const getBorderColour = (index: number, theme: keyof typeof BORDER_COLOURS) => {
|
||||
const colours = BORDER_COLOURS[theme];
|
||||
const length = colours.length;
|
||||
const modulo = index % length;
|
||||
return colours[modulo];
|
||||
};
|
||||
|
||||
const NestedDataListItem = ({
|
||||
label,
|
||||
value,
|
||||
index,
|
||||
}: NestedDataListItemProps) => {
|
||||
const [isCollapsed, setCollapsed] = useState(true);
|
||||
const toggleVisible = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
setCollapsed(!isCollapsed);
|
||||
},
|
||||
[isCollapsed]
|
||||
);
|
||||
const hasChildren = isObject(value) && !!Object.keys(value).length;
|
||||
const title = useMemo(() => camelToTitle(label), [label]);
|
||||
const theme = useContext(ThemeContext);
|
||||
const currentLevelBorder = useMemo(
|
||||
() => getBorderColour(index, theme),
|
||||
[index, theme]
|
||||
);
|
||||
const nextLevelBorder = useMemo(
|
||||
() => getBorderColour(index + 1, theme),
|
||||
[index, theme]
|
||||
);
|
||||
const isArr = isArray(value);
|
||||
|
||||
const listItemClasses = classNames('pl-4 border-l-4', {
|
||||
'pt-10 last:pb-0': hasChildren,
|
||||
'first:pt-0': hasChildren && !index,
|
||||
'pt-2': !hasChildren && index,
|
||||
});
|
||||
|
||||
const titleClasses = classNames({
|
||||
'text-xl pl-4 border-l-4 font-alpha': hasChildren,
|
||||
'text-base font-medium whitespace-nowrap': !hasChildren,
|
||||
});
|
||||
|
||||
return (
|
||||
<li
|
||||
data-testid={title}
|
||||
title={title}
|
||||
className={listItemClasses}
|
||||
style={{ borderColor: currentLevelBorder }}
|
||||
>
|
||||
<div className="flex flex-wrap">
|
||||
<h4 className={titleClasses} style={{ borderColor: nextLevelBorder }}>
|
||||
{hasChildren ? (
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
type="button"
|
||||
onClick={toggleVisible}
|
||||
>
|
||||
<span className="">{title}</span>
|
||||
<small className="px-1 text-sm rounded bg-vega-light-200 dark:bg-vega-dark-200">
|
||||
{isArr ? 'array' : typeof value}
|
||||
</small>
|
||||
<Icon
|
||||
name={
|
||||
isCollapsed ? IconNames.CHEVRON_DOWN : IconNames.CHEVRON_UP
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<span className="mr-2">{title}:</span>
|
||||
)}
|
||||
</h4>
|
||||
{!hasChildren && (
|
||||
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
|
||||
{JSON.stringify(value, null, ' ')}
|
||||
</code>
|
||||
)}
|
||||
</div>
|
||||
{hasChildren && (
|
||||
<div aria-hidden={isCollapsed} className={isCollapsed ? 'hidden' : ''}>
|
||||
<NestedDataList
|
||||
data={value as UnknownObject | UnknownArray}
|
||||
level={index + 1}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const sortNestedDataByChildren = (data: UnknownObject | UnknownArray) =>
|
||||
Object.keys(data)
|
||||
.filter((key) => key)
|
||||
.sort((a, b) => {
|
||||
const isArr = isArray(data);
|
||||
const valA = isArr ? data[+a] : data[a];
|
||||
const valB = isArr ? data[+b] : data[b];
|
||||
const hasChildrenA = isObject(valA) && !!Object.keys(valA).length;
|
||||
const hasChildrenB = isObject(valB) && !!Object.keys(valB).length;
|
||||
|
||||
if (hasChildrenA && !hasChildrenB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!hasChildrenA && hasChildrenB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
export const NestedDataList = ({ data, level = 0 }: NestedDataListProps) => {
|
||||
const nestedItems = useMemo(() => {
|
||||
const sortedData = sortNestedDataByChildren(data);
|
||||
const isArr = isArray(data);
|
||||
|
||||
if (sortedData.length) {
|
||||
return sortedData.map((key) => (
|
||||
<NestedDataListItem
|
||||
key={key}
|
||||
label={key}
|
||||
value={isArr ? data[Number(key)] : data[key]}
|
||||
index={level}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [data, level]);
|
||||
|
||||
return <ul className="list-none">{nestedItems}</ul>;
|
||||
};
|
1
apps/explorer/src/app/components/page-header/index.ts
Normal file
1
apps/explorer/src/app/components/page-header/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './page-header';
|
49
apps/explorer/src/app/components/page-header/page-header.tsx
Normal file
49
apps/explorer/src/app/components/page-header/page-header.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { TruncateInline } from '../truncate/truncate';
|
||||
import { CopyWithTooltip, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
truncateStart?: number;
|
||||
truncateEnd?: number;
|
||||
copy?: boolean;
|
||||
prefix?: string | ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PageHeader = ({
|
||||
prefix,
|
||||
title,
|
||||
truncateStart,
|
||||
truncateEnd,
|
||||
copy = false,
|
||||
className,
|
||||
}: PageHeaderProps) => {
|
||||
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha';
|
||||
return (
|
||||
<header className={className}>
|
||||
<span className={`${titleClasses} block`}>{prefix}</span>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<h2 className={titleClasses}>
|
||||
{truncateStart && truncateEnd ? (
|
||||
<TruncateInline
|
||||
text={title}
|
||||
startChars={truncateStart}
|
||||
endChars={truncateEnd}
|
||||
/>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</h2>
|
||||
{copy && (
|
||||
<CopyWithTooltip data-testid="copy-to-clipboard" text={title}>
|
||||
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
|
||||
<Icon name="duplicate" className="" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
@ -9,16 +9,10 @@ import {
|
||||
import React, { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Icon,
|
||||
SyntaxHighlighter,
|
||||
AsyncRenderer,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SyntaxHighlighter, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '../../../components/panel';
|
||||
import { InfoPanel } from '../../../components/info-panel';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { TruncateInline } from '../../../components/truncate/truncate';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import type {
|
||||
PartyAssetsQuery,
|
||||
@ -27,6 +21,7 @@ import type {
|
||||
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { PageHeader } from '../../../components/page-header';
|
||||
|
||||
const PARTY_ASSETS_QUERY = gql`
|
||||
query PartyAssetsQuery($partyId: ID!) {
|
||||
@ -91,19 +86,12 @@ const Party = () => {
|
||||
);
|
||||
|
||||
const header = data?.party?.id ? (
|
||||
<header className="flex items-center gap-x-4">
|
||||
<TruncateInline
|
||||
text={data.party.id}
|
||||
startChars={visibleChars}
|
||||
endChars={visibleChars}
|
||||
className="text-4xl xl:text-5xl uppercase font-alpha"
|
||||
/>
|
||||
<CopyWithTooltip text={data.party.id}>
|
||||
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
|
||||
<Icon name="duplicate" className="" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</header>
|
||||
<PageHeader
|
||||
title={data.party.id}
|
||||
copy
|
||||
truncateStart={visibleChars}
|
||||
truncateEnd={visibleChars}
|
||||
/>
|
||||
) : (
|
||||
<Panel>
|
||||
<p>No party found for key {party}</p>
|
||||
|
@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { RenderFetched } from '../../../components/render-fetched';
|
||||
import { TxContent } from './tx-content';
|
||||
import { TxDetails } from './tx-details';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { PageHeader } from '../../../components/page-header';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const Tx = () => {
|
||||
const { txHash } = useParams<{ txHash: string }>();
|
||||
@ -22,7 +24,25 @@ const Tx = () => {
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle>{t('Transaction details')}</RouteTitle>
|
||||
<Link
|
||||
className="font-normal underline underline-offset-4 block mb-5"
|
||||
to={`/${Routes.TX}`}
|
||||
>
|
||||
<Icon
|
||||
className="text-vega-light-300 dark:text-vega-light-300"
|
||||
name={IconNames.CHEVRON_LEFT}
|
||||
/>
|
||||
All Transactions
|
||||
</Link>
|
||||
|
||||
<PageHeader
|
||||
title={hash}
|
||||
prefix="Transaction"
|
||||
copy
|
||||
truncateStart={5}
|
||||
truncateEnd={9}
|
||||
className="mb-5"
|
||||
/>
|
||||
|
||||
<RenderFetched error={tTxError} loading={tTxLoading}>
|
||||
<>
|
||||
@ -32,10 +52,6 @@ const Tx = () => {
|
||||
pubKey={data?.transaction.submitter}
|
||||
/>
|
||||
|
||||
<h2 className="text-2xl uppercase mb-4">
|
||||
{t('Transaction content')}
|
||||
</h2>
|
||||
|
||||
<TxContent data={data?.transaction} />
|
||||
</>
|
||||
</RenderFetched>
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { StatusMessage } from '../../../components/status-message';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
TableWithTbody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '../../../components/table';
|
||||
import { TxOrderType } from '../../../components/txs';
|
||||
import { NestedDataList } from '../../../components/nested-data-list';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
|
||||
interface TxContentProps {
|
||||
@ -23,21 +16,5 @@ export const TxContent = ({ data }: TxContentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-12">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row" className="w-[160px]">
|
||||
{t('Type')}
|
||||
</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<TxOrderType orderType={data.type} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
<h3 className="font-mono mb-8">{t('Decoded transaction content')}</h3>
|
||||
<SyntaxHighlighter data={data.command} />
|
||||
</>
|
||||
);
|
||||
return <NestedDataList data={data.command} />;
|
||||
};
|
||||
|
@ -25,11 +25,6 @@ const renderComponent = (txData: BlockExplorerTransactionResult) => (
|
||||
);
|
||||
|
||||
describe('Transaction details', () => {
|
||||
it('Renders the tx hash', () => {
|
||||
render(renderComponent(txData));
|
||||
expect(screen.getByText(hash)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the pubKey', () => {
|
||||
render(renderComponent(txData));
|
||||
expect(screen.getByText(pubKey)).toBeInTheDocument();
|
||||
@ -39,9 +34,4 @@ describe('Transaction details', () => {
|
||||
render(renderComponent(txData));
|
||||
expect(screen.getByText(height)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders a copy button', () => {
|
||||
render(renderComponent(txData));
|
||||
expect(screen.getByTestId('copy-tx-to-clipboard')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,15 +1,9 @@
|
||||
import { Routes } from '../../route-names';
|
||||
import { CopyWithTooltip, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
TableWithTbody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '../../../components/table';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { HighlightedLink } from '../../../components/highlighted-link';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import React from 'react';
|
||||
import { TruncateInline } from '../../../components/truncate/truncate';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface TxDetailsProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@ -24,40 +18,32 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const truncatedSubmitter = (
|
||||
<TruncateInline text={pubKey || ''} startChars={5} endChars={5} />
|
||||
);
|
||||
|
||||
return (
|
||||
<TableWithTbody className={className}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Hash')}</TableCell>
|
||||
<TableCell modifier="bordered" data-testid="hash">
|
||||
{txData.hash}
|
||||
<CopyWithTooltip text={txData.hash}>
|
||||
<button
|
||||
title={t('Copy tx to clipboard')}
|
||||
data-testid="copy-tx-to-clipboard"
|
||||
className="underline"
|
||||
>
|
||||
<Icon name="duplicate" className="ml-2" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row" className="w-[160px]">
|
||||
{t('Submitted by')}
|
||||
</TableHeader>
|
||||
<TableCell modifier="bordered" data-testid="submitted-by">
|
||||
<HighlightedLink to={`/${Routes.PARTIES}/${pubKey}`} text={pubKey} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Block')}</TableCell>
|
||||
<TableCell modifier="bordered" data-testid="block">
|
||||
<HighlightedLink
|
||||
to={`/${Routes.BLOCKS}/${txData.block}`}
|
||||
text={txData.block}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<section className="mb-10">
|
||||
<h3 className="text-3xl xl:text-4xl uppercase font-alpha mb-4">
|
||||
{txData.type} by{' '}
|
||||
<Link
|
||||
className="font-bold underline"
|
||||
to={`/${Routes.PARTIES}/${pubKey}`}
|
||||
>
|
||||
{truncatedSubmitter}
|
||||
</Link>
|
||||
</h3>
|
||||
<p className="text-xl xl:text-2xl uppercase font-alpha">
|
||||
Block{' '}
|
||||
<Link
|
||||
className="font-bold underline"
|
||||
to={`/${Routes.BLOCKS}/${txData.block}`}
|
||||
>
|
||||
{txData.block}
|
||||
</Link>
|
||||
{', '}
|
||||
Index {txData.index}
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,9 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
||||
const {
|
||||
VegaColours,
|
||||
} = require('../../libs/tailwindcss-config/src/vega-colours');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
module.exports = {
|
||||
@ -11,7 +14,12 @@ module.exports = {
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: theme,
|
||||
extend: {
|
||||
...theme,
|
||||
colors: {
|
||||
vega: VegaColours,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [vegaCustomClasses],
|
||||
};
|
||||
|
@ -1,9 +1,11 @@
|
||||
const theme = require('./theme');
|
||||
const themelite = require('./theme-lite');
|
||||
const vegaCustomClasses = require('./vega-custom-classes');
|
||||
const { VegaColours } = require('./vega-colours');
|
||||
|
||||
module.exports = {
|
||||
theme,
|
||||
themelite,
|
||||
plugins: [vegaCustomClasses],
|
||||
VegaColours,
|
||||
};
|
||||
|
38
libs/tailwindcss-config/src/vega-colours.js
Normal file
38
libs/tailwindcss-config/src/vega-colours.js
Normal file
@ -0,0 +1,38 @@
|
||||
const VegaColours = {
|
||||
yellow: {
|
||||
DEFAULT: '#D7FB50',
|
||||
dark: '#9BE106',
|
||||
},
|
||||
pink: {
|
||||
DEFAULT: '#FF077F',
|
||||
dark: '#CF0064',
|
||||
},
|
||||
green: {
|
||||
DEFAULT: '#00F780',
|
||||
dark: '#00D46E',
|
||||
},
|
||||
blue: {
|
||||
DEFAULT: '#0075FF',
|
||||
dark: '#0046CD',
|
||||
},
|
||||
purple: {
|
||||
DEFAULT: '#8028FF',
|
||||
dark: '#5D0CD2',
|
||||
},
|
||||
dark: {
|
||||
100: '#161616',
|
||||
200: '#404040',
|
||||
300: '#8B8B8B',
|
||||
400: '#C0C0C0',
|
||||
},
|
||||
light: {
|
||||
100: '#F0F0F0',
|
||||
200: '#D2D2D2',
|
||||
300: '#A7A7A7',
|
||||
400: '#626262',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
VegaColours,
|
||||
};
|
Loading…
Reference in New Issue
Block a user