feat(explorer): extend transfers view
This commit is contained in:
parent
532ad3a4b9
commit
496b0b5a90
@ -64,7 +64,9 @@ export const ProposalSummary = ({
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
|
||||
{id && <ProposalStatusIcon id={id} />}
|
||||
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
|
||||
{rationale?.title && (
|
||||
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
|
||||
)}
|
||||
{rationale?.description && (
|
||||
<div className="pt-2 text-sm leading-tight">
|
||||
<ReactMarkdown
|
||||
|
@ -0,0 +1,16 @@
|
||||
query ExplorerTransferVote($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
reference
|
||||
timestamp
|
||||
status
|
||||
reason
|
||||
fromAccountType
|
||||
from
|
||||
to
|
||||
toAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
amount
|
||||
}
|
||||
}
|
59
apps/explorer/src/app/components/txs/details/transfer/__generated__/Transfer.ts
generated
Normal file
59
apps/explorer/src/app/components/txs/details/transfer/__generated__/Transfer.ts
generated
Normal file
@ -0,0 +1,59 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTransferVoteQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerTransferVoteQuery = { __typename?: 'Query', transfer?: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } | null };
|
||||
|
||||
|
||||
export const ExplorerTransferVoteDocument = gql`
|
||||
query ExplorerTransferVote($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
reference
|
||||
timestamp
|
||||
status
|
||||
reason
|
||||
fromAccountType
|
||||
from
|
||||
to
|
||||
toAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
amount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTransferVoteQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTransferVoteQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTransferVoteQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerTransferVoteQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTransferVoteQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferVoteQuery, ExplorerTransferVoteQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTransferVoteQuery, ExplorerTransferVoteQueryVariables>(ExplorerTransferVoteDocument, options);
|
||||
}
|
||||
export function useExplorerTransferVoteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferVoteQuery, ExplorerTransferVoteQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTransferVoteQuery, ExplorerTransferVoteQueryVariables>(ExplorerTransferVoteDocument, options);
|
||||
}
|
||||
export type ExplorerTransferVoteQueryHookResult = ReturnType<typeof useExplorerTransferVoteQuery>;
|
||||
export type ExplorerTransferVoteLazyQueryHookResult = ReturnType<typeof useExplorerTransferVoteLazyQuery>;
|
||||
export type ExplorerTransferVoteQueryResult = Apollo.QueryResult<ExplorerTransferVoteQuery, ExplorerTransferVoteQueryVariables>;
|
@ -111,7 +111,7 @@ export function TransferParticipants({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-vega-light-100 dark:fill-black"
|
||||
className="fill-white dark:fill-black"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
@ -120,7 +120,7 @@ export function TransferParticipants({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-vega-light-100 dark:fill-vega-dark-200"
|
||||
className="fill-vega-light-200 dark:fill-vega-dark-200"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
|
@ -4,6 +4,7 @@ import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import type { components } from '../../../../../../types/explorer';
|
||||
import type { Recurring } from '../transfer-details';
|
||||
import { DispatchMetricLabels } from '@vegaprotocol/types';
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
export type Strategy = components['schemas']['vegaDispatchStrategy'];
|
||||
@ -13,6 +14,15 @@ const metricLabels: Record<Metric, string> = {
|
||||
...DispatchMetricLabels,
|
||||
};
|
||||
|
||||
// Maps the two (non-null) values of entityScope to the icon that represents it
|
||||
const entityScopeIcons: Record<
|
||||
string,
|
||||
typeof VegaIconNames[keyof typeof VegaIconNames]
|
||||
> = {
|
||||
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
|
||||
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
|
||||
};
|
||||
|
||||
interface TransferRewardsProps {
|
||||
recurring: Recurring;
|
||||
}
|
||||
@ -34,7 +44,7 @@ export function TransferRewards({ recurring }: TransferRewardsProps) {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
|
||||
<ul className="relative block rounded-lg py-6 text-center p-6">
|
||||
<ul className="relative block rounded-lg py-6 text-left p-6">
|
||||
{recurring.dispatchStrategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Asset')}</strong>:{' '}
|
||||
@ -44,6 +54,27 @@ export function TransferRewards({ recurring }: TransferRewardsProps) {
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
|
||||
</li>
|
||||
{recurring.dispatchStrategy.entityScope &&
|
||||
entityScopeIcons[recurring.dispatchStrategy.entityScope] ? (
|
||||
<li>
|
||||
<strong>{t('Scope')}</strong>:{' '}
|
||||
<VegaIcon
|
||||
name={entityScopeIcons[recurring.dispatchStrategy.entityScope]}
|
||||
/>
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{recurring.dispatchStrategy.individualScope}
|
||||
{recurring.dispatchStrategy.teamScope}
|
||||
|
||||
{recurring.dispatchStrategy.lockPeriod &&
|
||||
recurring.dispatchStrategy.lockPeriod !== '0' ? (
|
||||
<li>
|
||||
<strong>{t('Lock')}</strong>:{' '}
|
||||
{recurring.dispatchStrategy.lockPeriod}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{recurring.dispatchStrategy.markets &&
|
||||
recurring.dispatchStrategy.markets.length > 0 ? (
|
||||
<li>
|
||||
@ -57,9 +88,30 @@ export function TransferRewards({ recurring }: TransferRewardsProps) {
|
||||
</ul>
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{recurring.dispatchStrategy.stakingRequirement &&
|
||||
recurring.dispatchStrategy.stakingRequirement !== '0' ? (
|
||||
<li>
|
||||
<strong>{t('Factor')}</strong>: {recurring.factor}
|
||||
<strong>{t('Staking requirement')}</strong>:{' '}
|
||||
{recurring.dispatchStrategy.stakingRequirement}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{recurring.dispatchStrategy.windowLength &&
|
||||
recurring.dispatchStrategy.windowLength !== '0' ? (
|
||||
<li>
|
||||
<strong>{t('Window length')}</strong>:{' '}
|
||||
{recurring.dispatchStrategy.windowLength}
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{recurring.dispatchStrategy.rankTable &&
|
||||
recurring.dispatchStrategy.rankTable.length > 0 ? (
|
||||
<li>
|
||||
<strong>{t('Ranks')}</strong>:{' '}
|
||||
{recurring.dispatchStrategy.rankTable.toString()}
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
|
||||
interface TransferStatusProps {
|
||||
status: string;
|
||||
error: ApolloError | undefined;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Status')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center p-6">
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
<p className="leading-10 my-2">
|
||||
<Icon name="tick" className="text-green-500" />
|
||||
</p>
|
||||
<p className="leading-10 my-2">{status}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -2,6 +2,9 @@ import type { components } from '../../../../../types/explorer';
|
||||
import { TransferRepeat } from './blocks/transfer-repeat';
|
||||
import { TransferRewards } from './blocks/transfer-rewards';
|
||||
import { TransferParticipants } from './blocks/transfer-participants';
|
||||
import { useExplorerTransferVoteQuery } from './__generated__/Transfer';
|
||||
import { TransferStatusView } from './blocks/transfer-status';
|
||||
import { TransferStatus } from '@vegaprotocol/types';
|
||||
|
||||
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
@ -16,6 +19,9 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
interface TransferDetailsProps {
|
||||
transfer: Transfer;
|
||||
from: string;
|
||||
id: string;
|
||||
// If set, all blocks except the status one are hidden
|
||||
statusOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,16 +30,38 @@ interface TransferDetailsProps {
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
|
||||
export function TransferDetails({
|
||||
transfer,
|
||||
from,
|
||||
id,
|
||||
statusOnly = false,
|
||||
}: TransferDetailsProps) {
|
||||
const recurring = transfer.recurring;
|
||||
|
||||
// Currently all this is passed in to TransferStatus, but the extra details
|
||||
// may be useful in the future.
|
||||
const { data, error, loading } = useExplorerTransferVoteQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
const status = error
|
||||
? TransferStatus.STATUS_REJECTED
|
||||
: data?.transfer?.status;
|
||||
|
||||
return (
|
||||
<div className="flex gap-5 flex-wrap">
|
||||
{statusOnly ? null : (
|
||||
<>
|
||||
<TransferParticipants from={from} transfer={transfer} />
|
||||
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
<TransferRewards recurring={transfer.recurring} />
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{status ? (
|
||||
<TransferStatusView status={status} error={error} loading={loading} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
|
||||
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
|
||||
import { MarketLink } from '../../links';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { TransferDetails } from './transfer/transfer-details';
|
||||
import { proposalToTransfer } from '../lib/proposal-to-transfer';
|
||||
|
||||
export type Proposal = components['schemas']['v1ProposalSubmission'];
|
||||
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||
@ -104,6 +106,12 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
? ProposalSignatureBundleNewAsset
|
||||
: ProposalSignatureBundleUpdateAsset;
|
||||
|
||||
let transfer, from;
|
||||
if (proposal.terms?.newTransfer?.changes) {
|
||||
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
|
||||
from = proposal.terms.newTransfer.changes.source;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
@ -149,14 +157,27 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
</>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
<ProposalSummary
|
||||
id={deterministicId}
|
||||
rationale={proposal.rationale}
|
||||
terms={proposal?.terms}
|
||||
/>
|
||||
|
||||
{proposalRequiresSignatureBundle(proposal) && (
|
||||
<SignatureBundleComponent id={deterministicId} tx={tx} />
|
||||
)}
|
||||
|
||||
{transfer && (
|
||||
<div className="mt-8">
|
||||
<TransferDetails
|
||||
statusOnly={false}
|
||||
transfer={transfer}
|
||||
from={from || ''}
|
||||
id={deterministicId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -60,7 +60,7 @@ export const TxDetailsTransfer = ({
|
||||
}
|
||||
|
||||
const from = txData.submitter;
|
||||
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
@ -70,9 +70,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered" data-testid="id">
|
||||
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
|
||||
<TableCell>
|
||||
{txSignatureToDeterministicId(txData.signature.value)}
|
||||
</TableCell>
|
||||
<TableCell>{id}</TableCell>
|
||||
</TableRow>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
@ -105,7 +103,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
<TransferDetails from={from} transfer={transfer} />
|
||||
<TransferDetails from={from} transfer={transfer} id={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,29 @@
|
||||
import type { components } from '../../../../types/explorer';
|
||||
|
||||
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
|
||||
type ActualTransfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
/**
|
||||
* Converts a governance proposal for a transfer in to a transfer command that the
|
||||
* TransferDetails component can then render. The types are very similar, but do not
|
||||
* map precisely to each other due to some missing fields and some different field
|
||||
* names.
|
||||
*
|
||||
* @param proposal Governance proposal for a transfer
|
||||
* @returns transfer a Transfer object as if it had been submitted
|
||||
*/
|
||||
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
|
||||
return {
|
||||
amount: proposal.amount,
|
||||
asset: proposal.asset,
|
||||
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
|
||||
// fromAccountType does exist and is just named differently on the proposal
|
||||
fromAccountType: proposal.sourceType,
|
||||
oneOff: proposal.oneOff,
|
||||
recurring: proposal.recurring,
|
||||
// There is no reference applied on governance initiated transfers
|
||||
reference: '',
|
||||
to: proposal.destination,
|
||||
toAccountType: proposal.destinationType,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user