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 whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||||
const whiteThemeSideMenuBackgroundColor = '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 darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||||
|
@ -205,7 +205,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
|||||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||||
const whiteThemeSideMenuBackgroundColor = '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 darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||||
|
@ -161,7 +161,7 @@ context('Parties page', { tags: '@regression' }, function () {
|
|||||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||||
const whiteThemeSideMenuBackgroundColor = '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 darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||||
|
@ -216,7 +216,7 @@ context('Validator page', { tags: '@smoke' }, function () {
|
|||||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||||
const whiteThemeSideMenuBackgroundColor = '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 darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
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 React, { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { SubHeading } from '../../../components/sub-heading';
|
import { SubHeading } from '../../../components/sub-heading';
|
||||||
import {
|
import { SyntaxHighlighter, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
CopyWithTooltip,
|
|
||||||
Icon,
|
|
||||||
SyntaxHighlighter,
|
|
||||||
AsyncRenderer,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { Panel } from '../../../components/panel';
|
import { Panel } from '../../../components/panel';
|
||||||
import { InfoPanel } from '../../../components/info-panel';
|
import { InfoPanel } from '../../../components/info-panel';
|
||||||
import { toNonHex } from '../../../components/search/detect-search';
|
import { toNonHex } from '../../../components/search/detect-search';
|
||||||
import { TruncateInline } from '../../../components/truncate/truncate';
|
|
||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import type {
|
import type {
|
||||||
PartyAssetsQuery,
|
PartyAssetsQuery,
|
||||||
@ -27,6 +21,7 @@ import type {
|
|||||||
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
|
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
|
||||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||||
import { TxsInfiniteList } from '../../../components/txs';
|
import { TxsInfiniteList } from '../../../components/txs';
|
||||||
|
import { PageHeader } from '../../../components/page-header';
|
||||||
|
|
||||||
const PARTY_ASSETS_QUERY = gql`
|
const PARTY_ASSETS_QUERY = gql`
|
||||||
query PartyAssetsQuery($partyId: ID!) {
|
query PartyAssetsQuery($partyId: ID!) {
|
||||||
@ -91,19 +86,12 @@ const Party = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const header = data?.party?.id ? (
|
const header = data?.party?.id ? (
|
||||||
<header className="flex items-center gap-x-4">
|
<PageHeader
|
||||||
<TruncateInline
|
title={data.party.id}
|
||||||
text={data.party.id}
|
copy
|
||||||
startChars={visibleChars}
|
truncateStart={visibleChars}
|
||||||
endChars={visibleChars}
|
truncateEnd={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>
|
|
||||||
) : (
|
) : (
|
||||||
<Panel>
|
<Panel>
|
||||||
<p>No party found for key {party}</p>
|
<p>No party found for key {party}</p>
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import { RouteTitle } from '../../../components/route-title';
|
|
||||||
import { RenderFetched } from '../../../components/render-fetched';
|
import { RenderFetched } from '../../../components/render-fetched';
|
||||||
import { TxContent } from './tx-content';
|
import { TxContent } from './tx-content';
|
||||||
import { TxDetails } from './tx-details';
|
import { TxDetails } from './tx-details';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
|
||||||
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
|
||||||
import { toNonHex } from '../../../components/search/detect-search';
|
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 Tx = () => {
|
||||||
const { txHash } = useParams<{ txHash: string }>();
|
const { txHash } = useParams<{ txHash: string }>();
|
||||||
@ -22,7 +24,25 @@ const Tx = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<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}>
|
<RenderFetched error={tTxError} loading={tTxLoading}>
|
||||||
<>
|
<>
|
||||||
@ -32,10 +52,6 @@ const Tx = () => {
|
|||||||
pubKey={data?.transaction.submitter}
|
pubKey={data?.transaction.submitter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h2 className="text-2xl uppercase mb-4">
|
|
||||||
{t('Transaction content')}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<TxContent data={data?.transaction} />
|
<TxContent data={data?.transaction} />
|
||||||
</>
|
</>
|
||||||
</RenderFetched>
|
</RenderFetched>
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { StatusMessage } from '../../../components/status-message';
|
import { StatusMessage } from '../../../components/status-message';
|
||||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
import { NestedDataList } from '../../../components/nested-data-list';
|
||||||
import {
|
|
||||||
TableWithTbody,
|
|
||||||
TableCell,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '../../../components/table';
|
|
||||||
import { TxOrderType } from '../../../components/txs';
|
|
||||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
|
||||||
interface TxContentProps {
|
interface TxContentProps {
|
||||||
@ -23,21 +16,5 @@ export const TxContent = ({ data }: TxContentProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <NestedDataList data={data.command} />;
|
||||||
<>
|
|
||||||
<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} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -25,11 +25,6 @@ const renderComponent = (txData: BlockExplorerTransactionResult) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('Transaction details', () => {
|
describe('Transaction details', () => {
|
||||||
it('Renders the tx hash', () => {
|
|
||||||
render(renderComponent(txData));
|
|
||||||
expect(screen.getByText(hash)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the pubKey', () => {
|
it('Renders the pubKey', () => {
|
||||||
render(renderComponent(txData));
|
render(renderComponent(txData));
|
||||||
expect(screen.getByText(pubKey)).toBeInTheDocument();
|
expect(screen.getByText(pubKey)).toBeInTheDocument();
|
||||||
@ -39,9 +34,4 @@ describe('Transaction details', () => {
|
|||||||
render(renderComponent(txData));
|
render(renderComponent(txData));
|
||||||
expect(screen.getByText(height)).toBeInTheDocument();
|
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 { 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 { t } from '@vegaprotocol/react-helpers';
|
||||||
import { HighlightedLink } from '../../../components/highlighted-link';
|
|
||||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { TruncateInline } from '../../../components/truncate/truncate';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface TxDetailsProps {
|
interface TxDetailsProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@ -24,40 +18,32 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
|
|||||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const truncatedSubmitter = (
|
||||||
|
<TruncateInline text={pubKey || ''} startChars={5} endChars={5} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableWithTbody className={className}>
|
<section className="mb-10">
|
||||||
<TableRow modifier="bordered">
|
<h3 className="text-3xl xl:text-4xl uppercase font-alpha mb-4">
|
||||||
<TableCell>{t('Hash')}</TableCell>
|
{txData.type} by{' '}
|
||||||
<TableCell modifier="bordered" data-testid="hash">
|
<Link
|
||||||
{txData.hash}
|
className="font-bold underline"
|
||||||
<CopyWithTooltip text={txData.hash}>
|
to={`/${Routes.PARTIES}/${pubKey}`}
|
||||||
<button
|
>
|
||||||
title={t('Copy tx to clipboard')}
|
{truncatedSubmitter}
|
||||||
data-testid="copy-tx-to-clipboard"
|
</Link>
|
||||||
className="underline"
|
</h3>
|
||||||
>
|
<p className="text-xl xl:text-2xl uppercase font-alpha">
|
||||||
<Icon name="duplicate" className="ml-2" />
|
Block{' '}
|
||||||
</button>
|
<Link
|
||||||
</CopyWithTooltip>
|
className="font-bold underline"
|
||||||
</TableCell>
|
to={`/${Routes.BLOCKS}/${txData.block}`}
|
||||||
</TableRow>
|
>
|
||||||
<TableRow modifier="bordered">
|
{txData.block}
|
||||||
<TableHeader scope="row" className="w-[160px]">
|
</Link>
|
||||||
{t('Submitted by')}
|
{', '}
|
||||||
</TableHeader>
|
Index {txData.index}
|
||||||
<TableCell modifier="bordered" data-testid="submitted-by">
|
</p>
|
||||||
<HighlightedLink to={`/${Routes.PARTIES}/${pubKey}`} text={pubKey} />
|
</section>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
||||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
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');
|
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -11,7 +14,12 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
extend: theme,
|
extend: {
|
||||||
|
...theme,
|
||||||
|
colors: {
|
||||||
|
vega: VegaColours,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [vegaCustomClasses],
|
plugins: [vegaCustomClasses],
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
const theme = require('./theme');
|
const theme = require('./theme');
|
||||||
const themelite = require('./theme-lite');
|
const themelite = require('./theme-lite');
|
||||||
const vegaCustomClasses = require('./vega-custom-classes');
|
const vegaCustomClasses = require('./vega-custom-classes');
|
||||||
|
const { VegaColours } = require('./vega-colours');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
theme,
|
theme,
|
||||||
themelite,
|
themelite,
|
||||||
plugins: [vegaCustomClasses],
|
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