feat(explorer): transfers layout wip
This commit is contained in:
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
useExplorerEpochQuery,
|
||||
useExplorerFutureEpochQuery,
|
||||
} from './__generated__/Epoch';
|
||||
import { useExplorerFutureEpochQuery } from './__generated__/Epoch';
|
||||
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { BlockLink } from '../links';
|
||||
import { Time } from '../time';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import parseISO from 'date-fns/parseISO';
|
||||
import addSeconds from 'date-fns/addSeconds';
|
||||
import parse from 'date-fns/parse';
|
||||
import formatDistance from 'date-fns/formatDistance';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-150 border-collapse';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import isFuture from 'date-fns/isFuture';
|
||||
|
||||
export type EpochMissingOverviewProps = {
|
||||
missingEpochId?: string;
|
||||
@@ -26,11 +16,16 @@ const EpochMissingOverview = ({
|
||||
}: EpochMissingOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerFutureEpochQuery();
|
||||
|
||||
if (!missingEpochId) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
if (!data || loading || error) {
|
||||
return <span>{missingEpochId}</span>;
|
||||
}
|
||||
|
||||
let label = 'Missing data';
|
||||
// Let's assume it is
|
||||
let isInFuture = true;
|
||||
|
||||
const epochLength = data.networkParameter?.value || '';
|
||||
const epochLengthInSeconds = getSeconds(epochLength);
|
||||
@@ -42,19 +37,29 @@ const EpochMissingOverview = ({
|
||||
|
||||
const diff = missing - current;
|
||||
const futureDate = addSeconds(startFrom, diff * epochLengthInSeconds);
|
||||
label = `${futureDate.toLocaleString()} - roughly ${formatDistance(
|
||||
|
||||
label = `Estimate: ${futureDate.toLocaleString()} - ${formatDistance(
|
||||
futureDate,
|
||||
startFrom
|
||||
startFrom,
|
||||
{ addSuffix: true }
|
||||
)} `;
|
||||
|
||||
isInFuture = isFuture(futureDate);
|
||||
}
|
||||
|
||||
const description = <p className="text-xs m-2">{label}</p>;
|
||||
|
||||
return (
|
||||
<details className="inline-block pl-2 cursor-pointer">
|
||||
<summary className="mr-5">{missingEpochId}</summary>
|
||||
<div className="text-xs m-2">
|
||||
<p>{label}</p>
|
||||
</div>
|
||||
</details>
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
{isInFuture ? (
|
||||
<Icon name="calendar" className="mr-1" />
|
||||
) : (
|
||||
<Icon name="outdated" className="mr-1" />
|
||||
)}
|
||||
{missingEpochId}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,12 @@ import { BlockLink } from '../links';
|
||||
import { Time } from '../time';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import EpochMissingOverview from './epoch-missing';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import isPast from 'date-fns/isPast';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-150 border-collapse';
|
||||
'border-solid border-2 border-vega-dark-200 border-collapse';
|
||||
|
||||
export type EpochOverviewProps = {
|
||||
id?: string;
|
||||
@@ -32,54 +35,82 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
return <span>{id}</span>;
|
||||
}
|
||||
|
||||
const description = (
|
||||
<table className="text-xs m-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Block')}</th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('Start')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.firstBlock ? <BlockLink height={ti.firstBlock} /> : '-'}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
<Time date={ti.start} />
|
||||
<br />
|
||||
<TimeAgo date={ti.start} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('End')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.lastBlock ? (
|
||||
<BlockLink height={ti.lastBlock} />
|
||||
) : (
|
||||
t('In progress')
|
||||
)}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.end ? (
|
||||
<>
|
||||
<Time date={ti.end} />
|
||||
<br />
|
||||
<TimeAgo date={ti.end} />
|
||||
</>
|
||||
) : (
|
||||
<span>{t('-')}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return (
|
||||
<details className="inline-block pl-2 cursor-pointer">
|
||||
<summary className="mr-5">{id}</summary>
|
||||
<table className="text-xs m-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Block')}</th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('Epoch start')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.firstBlock ? <BlockLink height={ti.firstBlock} /> : '-'}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
<Time date={ti.start} />
|
||||
<span className="mx-2">—</span>
|
||||
<TimeAgo date={ti.start} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('Epoch end')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.lastBlock ? (
|
||||
<BlockLink height={ti.lastBlock} />
|
||||
) : (
|
||||
t('In progress')
|
||||
)}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.end ? (
|
||||
<>
|
||||
<Time date={ti.end} />
|
||||
<span className="mx-2">—</span>
|
||||
<TimeAgo date={ti.end} />
|
||||
</>
|
||||
) : (
|
||||
<span>{t('-')}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
{id}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export type IconForEpochProps = {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
function IconForEpoch({ start, end }: IconForEpochProps) {
|
||||
const startHasPassed = isPast(new Date(start));
|
||||
const endHasPassed = end ? isPast(new Date(end)) : false;
|
||||
|
||||
let i: IconProps['name'] = 'calendar';
|
||||
|
||||
if (!startHasPassed && !endHasPassed) {
|
||||
i = 'calendar';
|
||||
} else if (startHasPassed && !endHasPassed) {
|
||||
i = 'circle';
|
||||
} else if (startHasPassed && endHasPassed) {
|
||||
i = 'tick-circle';
|
||||
}
|
||||
|
||||
return <Icon name={i} className="mr-2" />;
|
||||
}
|
||||
|
||||
export default EpochOverview;
|
||||
|
||||
@@ -5,16 +5,18 @@ import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error';
|
||||
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const SPECIAL_CASE_NETWORK_ID =
|
||||
export const SPECIAL_CASE_NETWORK_ID =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const SPECIAL_CASE_NETWORK = 'network';
|
||||
export const SPECIAL_CASE_NETWORK = 'network';
|
||||
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
// Some transactions will involve the 'network' party, which is alias for '000...000'
|
||||
// The party page does not handle this nicely, so in this case we render the word 'Network'
|
||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||
@@ -41,7 +43,7 @@ const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
<Hash text={id} />
|
||||
<Hash text={truncate ? truncateMiddle(id) : id} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { AssetLink } from '../links';
|
||||
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
|
||||
|
||||
export type DecimalSource = 'ASSET';
|
||||
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import EpochOverview from '../../../epoch-overview/epoch';
|
||||
import { AssetLink, MarketLink } from '../../../links';
|
||||
import { useExplorerFutureEpochQuery } from '../../../epoch-overview/__generated__/Epoch';
|
||||
import { AssetLink, MarketLink, PartyLink } from '../../../links';
|
||||
import {
|
||||
SPECIAL_CASE_NETWORK,
|
||||
SPECIAL_CASE_NETWORK_ID,
|
||||
} from '../../../links/party-link/party-link';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import SizeInAsset from '../../../size-in-asset/size-in-asset';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
|
||||
const wrapperClasses =
|
||||
'border border-zinc-200 dark:border-zinc-800 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
|
||||
const headerClasses =
|
||||
'bg-solid bg-zinc-200 dark:bg-zinc-800 border-zinc-200 text-center text-xl py-2 font-alpha';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
interface TransferRecurringProps {
|
||||
transfer: components['schemas']['v1RecurringTransfer'];
|
||||
transfer: Transfer;
|
||||
from: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -13,27 +33,144 @@ interface TransferRecurringProps {
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferRecurring({ transfer }: TransferRecurringProps) {
|
||||
export function TransferRecurring({ transfer, from }: TransferRecurringProps) {
|
||||
const recurring = transfer.recurring;
|
||||
const { data } = useExplorerFutureEpochQuery();
|
||||
const metric =
|
||||
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
|
||||
|
||||
const fromAcct =
|
||||
transfer.fromAccountType &&
|
||||
transfer.fromAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
|
||||
? AccountType[transfer.fromAccountType]
|
||||
: AccountType.ACCOUNT_TYPE_GENERAL;
|
||||
const fromAccountTypeLabel = transfer.fromAccountType
|
||||
? AccountTypeMapping[fromAcct]
|
||||
: 'Unknown';
|
||||
|
||||
const toAcct =
|
||||
transfer.toAccountType &&
|
||||
transfer.toAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
|
||||
? AccountType[transfer.toAccountType]
|
||||
: AccountType.ACCOUNT_TYPE_GENERAL;
|
||||
const toAccountTypeLabel = transfer.fromAccountType
|
||||
? AccountTypeMapping[toAcct]
|
||||
: 'Unknown';
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<li>
|
||||
<strong>{t('Starting epoch')}</strong>:{' '}
|
||||
<EpochOverview id={transfer.startEpoch} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>{t('Ending epoch')}</strong>:{' '}
|
||||
<EpochOverview id={transfer.endEpoch} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>{t('Factor')}</strong>: {transfer.factor}
|
||||
</li>
|
||||
{transfer.dispatchStrategy ? (
|
||||
<TransferRecurringStrategy strategy={transfer.dispatchStrategy} />
|
||||
<div className="flex gap-5 flex-wrap">
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Transfer')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center">
|
||||
<PartyLink id={from} truncate={true} />
|
||||
<Tooltip
|
||||
description={
|
||||
<p>{`${t('From account')}: ${fromAccountTypeLabel}`}</p>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Icon className="ml-3" name={'bank-account'} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<br />
|
||||
|
||||
<div className="bg-zinc-200 dark:bg-zinc-800 flex items-center justify-center my-4 relative">
|
||||
<div className="bg-zinc-200 dark:bg-zinc-800 border w-full pt-5 pb-3 px-3 boder-zinc-200 dark:border-zinc-800 relative">
|
||||
<div className="text-xs z-20 relative leading-none">
|
||||
{transfer.asset ? (
|
||||
<SizeInAsset
|
||||
assetId={transfer.asset}
|
||||
size={transfer.amount}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="z-10 absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-45 w-4 h-4 dark:border-zinc-800 border-zinc-200 bg-white dark:bg-black border-r border-b"></div>
|
||||
<div className="z-10 absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 rotate-45 w-4 h-4 border-zing-200 dark:border-zinc-800 bg-zinc-200 dark:bg-zinc-800 border-r border-b"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransferRecurringRecipient to={transfer.to} />
|
||||
<Tooltip
|
||||
description={<p>{`${t('To account')}: ${toAccountTypeLabel}`}</p>}
|
||||
>
|
||||
<span>
|
||||
<Icon className="ml-3" name={'bank-account'} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
{recurring ? (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Active epochs')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center p-6">
|
||||
<p>
|
||||
<EpochOverview id={recurring.startEpoch} />
|
||||
</p>
|
||||
<p className="leading-10 my-2">
|
||||
<IconForEpoch
|
||||
start={recurring.startEpoch}
|
||||
end={recurring.endEpoch}
|
||||
current={data?.epoch.id}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{recurring.endEpoch ? (
|
||||
<EpochOverview id={recurring.endEpoch} />
|
||||
) : (
|
||||
<span>{t('Forever')}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</ul>
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
|
||||
<ul className="relative block rounded-lg py-6 text-center p-6">
|
||||
{recurring.dispatchStrategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Asset')}</strong>:{' '}
|
||||
<AssetLink
|
||||
assetId={recurring.dispatchStrategy.assetForMetric}
|
||||
/>
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
|
||||
</li>
|
||||
{recurring.dispatchStrategy.markets &&
|
||||
recurring.dispatchStrategy.markets.length > 0 ? (
|
||||
<li>
|
||||
<strong>{t('Markets in scope')}</strong>:
|
||||
<ul>
|
||||
{recurring.dispatchStrategy.markets.map((m) => (
|
||||
<li key={m}>
|
||||
<MarketLink id={m} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Factor')}</strong>: {recurring.factor}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const metricLabels: Record<Metric, string> = {
|
||||
DISPATCH_METRIC_LP_FEES_RECEIVED: 'Liquidity Provision fees received',
|
||||
DISPATCH_METRIC_MAKER_FEES_PAID: 'Price maker fees paid',
|
||||
DISPATCH_METRIC_MAKER_FEES_RECEIVED: 'Price maker fees earned',
|
||||
DISPATCH_METRIC_MARKET_VALUE: 'Total market Value',
|
||||
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
|
||||
};
|
||||
|
||||
interface TransferRecurringStrategyProps {
|
||||
strategy: components['schemas']['vegaDispatchStrategy'];
|
||||
}
|
||||
@@ -61,40 +198,56 @@ export function TransferRecurringStrategy({
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {strategy.metric}
|
||||
</li>
|
||||
{strategy.markets ? (
|
||||
<li>
|
||||
<strong>{t('Markets')}</strong>:{' '}
|
||||
<TransferRecurringMarkets markets={strategy.markets} />
|
||||
</li>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface TransferRecurringMarketsProps {
|
||||
markets: readonly string[];
|
||||
interface TransferRecurringRecipientProps {
|
||||
to?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple render for a list of Market IDs used in a Recurring Transger
|
||||
* Simple render for a list of Market IDs used in a Recurring Transfer
|
||||
* dispatch strategy.
|
||||
*
|
||||
* @param markets String[] IDs of markets for this dispatch strategy
|
||||
*/
|
||||
export function TransferRecurringMarkets({
|
||||
markets,
|
||||
}: TransferRecurringMarketsProps) {
|
||||
if (!markets) {
|
||||
export function TransferRecurringRecipient({
|
||||
to,
|
||||
}: TransferRecurringRecipientProps) {
|
||||
if (to === SPECIAL_CASE_NETWORK || to === SPECIAL_CASE_NETWORK_ID) {
|
||||
return <span>{t('Rewards pool')}</span>;
|
||||
} else if (to) {
|
||||
return <PartyLink id={to} truncate={true} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="ml-10">
|
||||
{markets.map((m) => (
|
||||
<li key={m}>
|
||||
<MarketLink id={m} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
export type IconForTransferProps = {
|
||||
current?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
};
|
||||
|
||||
function IconForEpoch({ start, end, current }: IconForTransferProps) {
|
||||
let i: IconProps['name'] = 'repeat';
|
||||
|
||||
if (current && start && end) {
|
||||
const startEpoch = parseInt(start);
|
||||
const endEpoch = parseInt(end);
|
||||
const currentEpoch = parseInt(current);
|
||||
|
||||
if (currentEpoch > endEpoch) {
|
||||
// If we've finished
|
||||
i = 'updated';
|
||||
} else if (startEpoch > currentEpoch) {
|
||||
// If we haven't yet started
|
||||
i = 'time';
|
||||
} else if (startEpoch < currentEpoch && endEpoch > currentEpoch) {
|
||||
i = 'repeat';
|
||||
}
|
||||
}
|
||||
|
||||
return <Icon name={i} className="mr-2" />;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const TxDetailsTransfer = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
@@ -75,9 +75,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
{transfer.recurring ? (
|
||||
<TransferRecurring transfer={transfer.recurring} />
|
||||
) : null}
|
||||
<TransferRecurring from={from} transfer={transfer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user