diff --git a/apps/explorer/src/app/components/epoch-overview/epoch-missing.tsx b/apps/explorer/src/app/components/epoch-overview/epoch-missing.tsx index b39ee3aba..5454b6fdf 100644 --- a/apps/explorer/src/app/components/epoch-overview/epoch-missing.tsx +++ b/apps/explorer/src/app/components/epoch-overview/epoch-missing.tsx @@ -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 -; + } if (!data || loading || error) { return {missingEpochId}; } 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 =

{label}

; + return ( -
- {missingEpochId} -
-

{label}

-
-
+ +

+ {isInFuture ? ( + + ) : ( + + )} + {missingEpochId} +

+
); }; diff --git a/apps/explorer/src/app/components/epoch-overview/epoch.tsx b/apps/explorer/src/app/components/epoch-overview/epoch.tsx index 974e8649b..332bc81ff 100644 --- a/apps/explorer/src/app/components/epoch-overview/epoch.tsx +++ b/apps/explorer/src/app/components/epoch-overview/epoch.tsx @@ -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 {id}; } + const description = ( + + + + + + + + + + + + + + + + + + + + +
{t('Block')}{t('Time')}
{t('Start')} + {ti.firstBlock ? : '-'} + +
{t('End')} + {ti.lastBlock ? ( + + ) : ( + t('In progress') + )} + + {ti.end ? ( + <> +
+ ); + return ( -
- {id} - - - - - - - - - - - - - - - - - - - - -
{t('Block')}{t('Time')}
{t('Epoch start')} - {ti.firstBlock ? : '-'} - -
{t('Epoch end')} - {ti.lastBlock ? ( - - ) : ( - t('In progress') - )} - - {ti.end ? ( - <> -
-
+ +

+ + {id} +

+
); }; +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 ; +} + export default EpochOverview; diff --git a/apps/explorer/src/app/components/links/party-link/party-link.tsx b/apps/explorer/src/app/components/links/party-link/party-link.tsx index 69bd88d66..726b323a9 100644 --- a/apps/explorer/src/app/components/links/party-link/party-link.tsx +++ b/apps/explorer/src/app/components/links/party-link/party-link.tsx @@ -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> & { 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}`} > - + ); }; diff --git a/apps/explorer/src/app/components/size-in-asset/size-in-asset.tsx b/apps/explorer/src/app/components/size-in-asset/size-in-asset.tsx index 2414d9038..152386e75 100644 --- a/apps/explorer/src/app/components/size-in-asset/size-in-asset.tsx +++ b/apps/explorer/src/app/components/size-in-asset/size-in-asset.tsx @@ -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'; diff --git a/apps/explorer/src/app/components/txs/details/transfer/transfer-recurring.tsx b/apps/explorer/src/app/components/txs/details/transfer/transfer-recurring.tsx index c6650e600..5f9f94273 100644 --- a/apps/explorer/src/app/components/txs/details/transfer/transfer-recurring.tsx +++ b/apps/explorer/src/app/components/txs/details/transfer/transfer-recurring.tsx @@ -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 ( -
    -
  • - {t('Starting epoch')}:{' '} - -
  • -
  • - {t('Ending epoch')}:{' '} - -
  • -
  • - {t('Factor')}: {transfer.factor} -
  • - {transfer.dispatchStrategy ? ( - +
    +
    +

    {t('Transfer')}

    +
    + + {`${t('From account')}: ${fromAccountTypeLabel}`}

    + } + > + + + +
    +
    + +
    +
    +
    + {transfer.asset ? ( + + ) : null} +
    + +
    +
    +
    +
    + + + {`${t('To account')}: ${toAccountTypeLabel}`}

    } + > + + + +
    +
    +
    +
    + {recurring ? ( +
    +

    {t('Active epochs')}

    +
    +

    + +

    +

    + +

    +

    + {recurring.endEpoch ? ( + + ) : ( + {t('Forever')} + )} +

    +
    +
    ) : null} -
+ {recurring && recurring.dispatchStrategy ? ( +
+

{t('Reward metrics')}

+
    + {recurring.dispatchStrategy.assetForMetric ? ( +
  • + {t('Asset')}:{' '} + +
  • + ) : null} +
  • + {t('Metric')}: {metricLabels[metric]} +
  • + {recurring.dispatchStrategy.markets && + recurring.dispatchStrategy.markets.length > 0 ? ( +
  • + {t('Markets in scope')}: +
      + {recurring.dispatchStrategy.markets.map((m) => ( +
    • + +
    • + ))} +
    +
  • + ) : null} +
  • + {t('Factor')}: {recurring.factor} +
  • +
+
+ ) : null} + ); } +const metricLabels: Record = { + 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({
  • {t('Metric')}: {strategy.metric}
  • - {strategy.markets ? ( -
  • - {t('Markets')}:{' '} - -
  • - ) : 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 {t('Rewards pool')}; + } else if (to) { + return ; + } else { return null; } +} - return ( -
      - {markets.map((m) => ( -
    • - -
    • - ))} -
    - ); +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 ; } diff --git a/apps/explorer/src/app/components/txs/details/tx-transfer.tsx b/apps/explorer/src/app/components/txs/details/tx-transfer.tsx index 49dd8fe58..fb269b872 100644 --- a/apps/explorer/src/app/components/txs/details/tx-transfer.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-transfer.tsx @@ -44,7 +44,7 @@ export const TxDetailsTransfer = ({ return ( <> - + ) : null} - {transfer.recurring ? ( - - ) : null} + ); };