Compare commits

..
Author SHA1 Message Date
Matthew Russell 885dc0b52b fix: change prop name to be clearer that its fallback ui 2024-02-07 17:32:39 -08:00
Matthew Russell 58df46687b feat: improve styles for market header 2024-02-07 17:30:05 -08:00
Matthew Russell 96a07b6379 chore: adjust styles for alignment and borders 2024-02-07 17:15:11 -08:00
Matthew Russell 619b73212f fix: remove additional h1 2024-02-07 16:46:50 -08:00
Matthew Russell 3e2449fdea fix: remove router that doesnt render anything 2024-02-07 16:45:08 -08:00
Matthew Russell 0351d3f478 fix: breakpoint for mobile header 2024-02-07 16:44:53 -08:00
Madalina Raicu 5ab455159c feat: update styling 2024-02-07 15:28:36 +00:00
Madalina Raicu 5742759780 chore(explorer, governance): update manifest.json 2024-02-07 14:43:09 +00:00
Madalina Raicu 94c34a1d76 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/mobile-layout 2024-02-07 14:39:48 +00:00
Madalina Raicu 2c1a3d106c chore(trading): refactor last price change render prop 2024-02-07 14:39:25 +00:00
Madalina Raicu d41e9456bc chore(trading): rename mobile view vars 2024-02-07 14:23:58 +00:00
Madalina Raicu 0bad7e3317 chore: update manifest 2024-02-06 14:09:04 +00:00
Madalina Raicu b2d52f6f5c test: link rel manifest to index.page 2024-02-06 13:35:55 +00:00
Madalina Raicu 3ae2fe3e1c fix: remove semi-colon 2024-02-05 23:39:06 +00:00
Madalina Raicu 8eed5f1eb4 chore: update manifest 2024-02-05 22:44:57 +00:00
Madalina Raicu 84bb32bdd7 chore(trading): update manifest.json 2024-02-05 19:38:38 +00:00
Madalina Raicu 87579c0c5f fix(trading): update price change 2024-02-02 15:55:44 +00:00
Madalina Raicu 4bb50c0dab fix(trading): use mobile market header in layout-with-sidebar 2024-02-02 15:33:55 +00:00
Madalina Raicu 96b46d1533 fix(trading): revert adding header to layout with sidebar for liquidity 2024-02-02 15:25:29 +00:00
Madalina Raicu 19732d90e0 feat(trading): market stats grid 2024-02-02 14:45:13 +00:00
Madalina Raicu 853d0654d7 fix(trading): mobile header justify between 2024-02-02 14:27:57 +00:00
Madalina Raicu b24b630f5f feat(trading): mobile header 2024-02-02 13:04:02 +00:00
Madalina Raicu 7777420f1f feat(trading): update navbar 2024-02-01 19:59:39 +00:00
Madalina Raicu f5406941e7 feat(trading): update layout on mobile 2024-02-01 16:11:04 +00:00
Madalina Raicu 18a3786c98 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/mobile-layout 2024-01-31 14:41:10 +00:00
29 changed files with 334 additions and 822 deletions
@@ -64,9 +64,7 @@ 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 break-all">{rationale.title}</h1>
)}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
{rationale?.description && (
<div className="pt-2 text-sm leading-tight">
<ReactMarkdown
@@ -1,18 +0,0 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
@@ -1,61 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', 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 ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
`;
/**
* __useExplorerTransferStatusQuery__
*
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerTransferStatusQuery` 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 } = useExplorerTransferStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
@@ -111,7 +111,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-white dark:fill-black"
className="fill-vega-light-100 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-200 dark:fill-vega-dark-200"
className="fill-vega-light-100 dark:fill-vega-dark-200"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -1,223 +1,97 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink } from '../../../../links';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer';
import type { Recurring } from '../transfer-details';
import {
DispatchMetricLabels,
DistributionStrategy,
} from '@vegaprotocol/types';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
import { DispatchMetricLabels } from '@vegaprotocol/types';
export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy'];
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
const metricLabels: Record<Metric, string> = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...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,
};
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
};
interface TransferRewardsProps {
recurring: Recurring;
}
/**
* Renders recurring transfers/game details in a way that is, perhaps, easy to understand
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferRewards({ recurring }: TransferRewardsProps) {
const metric =
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
if (!recurring || !recurring.dispatchStrategy) {
return null;
}
// Destructure to make things a bit more readable
const {
entityScope,
individualScope,
teamScope,
distributionStrategy,
lockPeriod,
markets,
stakingRequirement,
windowLength,
notionalTimeWeightedAveragePositionRequirement,
rankTable,
nTopPerformers,
} = recurring.dispatchStrategy;
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
<ul className="relative block rounded-lg py-6 text-left p-6">
{entityScope && entityScopeIcons[entityScope] ? (
<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('Scope')}</strong>:{' '}
<VegaIcon name={entityScopeIcons[entityScope]} />
&nbsp;
{individualScope ? individualScopeLabels[individualScope] : null}
{getScopeLabel(entityScope, teamScope)}
<strong>{t('Asset')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
) : null}
{recurring.dispatchStrategy &&
recurring.dispatchStrategy.assetForMetric && (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
)}
{recurring.dispatchStrategy.metric &&
metricLabels[recurring.dispatchStrategy.metric] && (
<li>
<strong>{t('Metric')}</strong>:{' '}
{metricLabels[recurring.dispatchStrategy.metric]}
</li>
)}
{lockPeriod && (
<li>
<strong>{t('Reward lock')}</strong>:&nbsp;
{recurring.dispatchStrategy.lockPeriod}{' '}
{recurring.dispatchStrategy.lockPeriod === '1'
? t('epoch')
: t('epochs')}
</li>
)}
{markets && markets.length > 0 ? (
<li>
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
</li>
{recurring.dispatchStrategy.markets &&
recurring.dispatchStrategy.markets.length > 0 ? (
<li>
<strong>{t('Markets in scope')}</strong>:
<ul className="inline-block ml-1">
{markets.map((m) => (
<li key={m} className="inline-block mr-2">
<ul>
{recurring.dispatchStrategy.markets.map((m) => (
<li key={m}>
<MarketLink id={m} />
</li>
))}
</ul>
</li>
) : null}
{stakingRequirement && stakingRequirement !== '0' ? (
<li>
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
</li>
) : null}
{windowLength && windowLength !== '0' ? (
<li>
<strong>{t('Window length')}</strong>:{' '}
{recurring.dispatchStrategy.windowLength}{' '}
{recurring.dispatchStrategy.windowLength === '1'
? t('epoch')
: t('epochs')}
</li>
) : null}
{notionalTimeWeightedAveragePositionRequirement &&
notionalTimeWeightedAveragePositionRequirement !== '' ? (
<li>
<strong>{t('Notional TWAP')}</strong>:{' '}
{notionalTimeWeightedAveragePositionRequirement}
</li>
) : null}
{nTopPerformers && (
<li>
<strong>{t('Elligible team members:')}</strong> top{' '}
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
</li>
)}
{distributionStrategy &&
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
<li>
<strong>{t('Distribution strategy')}</strong>:{' '}
{distributionStrategyLabel[distributionStrategy]}
</li>
)}
<li>
<strong>{t('Factor')}</strong>: {recurring.factor}
</li>
</ul>
<div className="px-6 pt-1 pb-5">
{rankTable && rankTable.length > 0 ? (
<table className="border-collapse border border-gray-400 ">
<thead>
<tr>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Start rank')}</strong>
</th>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Share of reward pool')}</strong>
</th>
</tr>
</thead>
<tbody>
{rankTable.map((row, i) => {
return (
<tr key={`rank-${i}`}>
<td className="border border-slate-300 text-center">
{row.startRank}
</td>
<td className="border border-slate-300 text-center">
{row.shareRatio}
</td>
</tr>
);
})}
</tbody>
</table>
) : null}
</div>
</div>
);
}
export function getScopeLabel(
scope: components['schemas']['vegaEntityScope'] | undefined,
teamScope: readonly string[] | undefined
): string {
if (scope === 'ENTITY_SCOPE_TEAMS') {
if (teamScope && teamScope.length !== 0) {
return ` ${teamScope.length} teams`;
} else {
return t('All teams');
}
} else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
return t('Individuals');
} else {
return '';
}
}
export function getRewardTitle(
scope?: components['schemas']['vegaEntityScope']
) {
if (scope === 'ENTITY_SCOPE_TEAMS') {
return t('Game');
}
return t('Reward metrics');
interface TransferRecurringStrategyProps {
strategy: Strategy;
}
const individualScopeLabels: Record<
components['schemas']['vegaIndividualScope'],
string
> = {
// Unspecified and All are not rendered
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
INDIVIDUAL_SCOPE_ALL: '',
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
};
/**
* Simple renderer for a dispatch strategy in a recurring transfer
*
* @param strategy Dispatch strategy object
*/
export function TransferRecurringStrategy({
strategy,
}: TransferRecurringStrategyProps) {
if (!strategy) {
return null;
}
return (
<>
{strategy.assetForMetric ? (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={strategy.assetForMetric} />
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {strategy.metric}
</li>
</>
);
}
@@ -1,84 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { headerClasses, wrapperClasses } from '../transfer-details';
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import type { ApolloError } from '@apollo/client';
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
import { IconNames } from '@blueprintjs/icons';
interface TransferStatusProps {
status: TransferStatus | undefined;
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) {
status = TransferStatus.STATUS_PENDING;
}
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Status')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
{loading ? (
<div className="leading-10 mt-12">
<Loader size={'small'} />
</div>
) : (
<>
<p className="leading-10 my-2">
<Icon
name={getIconForStatus(status)}
className={getColourForStatus(status)}
/>
</p>
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
</>
)}
</div>
</div>
);
}
/**
* Simple mapping from status to icon name
* @param status TransferStatus
* @returns IconName
*/
export function getIconForStatus(status: TransferStatus): IconName {
switch (status) {
case TransferStatus.STATUS_PENDING:
return IconNames.TIME;
case TransferStatus.STATUS_DONE:
return IconNames.TICK;
case TransferStatus.STATUS_REJECTED:
return IconNames.CROSS;
default:
return IconNames.TIME;
}
}
/**
* Simple mapping from status to colour
* @param status TransferStatus
* @returns string Tailwind classname
*/
export function getColourForStatus(status: TransferStatus): string {
switch (status) {
case TransferStatus.STATUS_PENDING:
return 'text-yellow-500';
case TransferStatus.STATUS_DONE:
return 'text-green-500';
case TransferStatus.STATUS_REJECTED:
return 'text-red-500';
default:
return 'text-yellow-500';
}
}
@@ -2,15 +2,12 @@ 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 { useExplorerTransferStatusQuery } 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'];
export const wrapperClasses =
'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
@@ -19,7 +16,6 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
interface TransferDetailsProps {
transfer: Transfer;
from: string;
id: string;
}
/**
@@ -28,24 +24,13 @@ interface TransferDetailsProps {
*
* @param transfer A recurring transfer object
*/
export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
export function TransferDetails({ transfer, from }: 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 } = useExplorerTransferStatusQuery({
variables: { id },
});
const status = error
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
return (
<div className="flex flex-wrap">
<div className="flex gap-5 flex-wrap">
<TransferParticipants from={from} transfer={transfer} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? (
<TransferRewards recurring={transfer.recurring} />
) : null}
@@ -1,172 +0,0 @@
import {
getScopeLabel,
getRewardTitle,
TransferRewards,
} from './blocks/transfer-rewards';
import { render } from '@testing-library/react';
import type { components } from '../../../../../types/explorer';
import type { Recurring } from './transfer-details';
import {
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getScopeLabel', () => {
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = ['team1', 'team2', 'team3'];
const expectedLabel = ' 3 teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = undefined;
const expectedLabel = 'All teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const teamScope = undefined;
const expectedLabel = 'Individuals';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return an empty string for unknown scope', () => {
const scope = 'UNKNOWN_SCOPE';
const teamScope = undefined;
const expectedLabel = '';
const result = getScopeLabel(
scope as unknown as components['schemas']['vegaEntityScope'],
teamScope
);
expect(result).toEqual(expectedLabel);
});
});
describe('getRewardTitle', () => {
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const expectedTitle = 'Game';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
it('should return the correct title for other scopes', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const expectedTitle = 'Reward metrics';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
});
describe('TransferRewards', () => {
it('should render nothing if recurring dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={null as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={{} as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render the reward details correctly', () => {
const recurring = {
dispatchStrategy: {
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
assetForMetric: '123',
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
teamScope: [],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: '1',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
rankTable: [
{ startRank: 1, shareRatio: 0.2 },
{ startRank: 2, shareRatio: 0.3 },
],
nTopPerformers: 'nTopPerformers',
},
};
const { getByText } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(getByText('Game')).toBeInTheDocument();
expect(getByText('Scope')).toBeInTheDocument();
expect(getByText('Asset for metric')).toBeInTheDocument();
expect(getByText('Metric')).toBeInTheDocument();
expect(getByText('Reward lock')).toBeInTheDocument();
expect(getByText('Markets in scope')).toBeInTheDocument();
expect(getByText('Staking requirement')).toBeInTheDocument();
expect(getByText('Window length')).toBeInTheDocument();
expect(getByText('Notional TWAP')).toBeInTheDocument();
expect(getByText('Elligible team members:')).toBeInTheDocument();
expect(getByText('Distribution strategy')).toBeInTheDocument();
expect(getByText('Start rank')).toBeInTheDocument();
expect(getByText('Share of reward pool')).toBeInTheDocument();
});
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
const recurring = {
dispatchStrategy: {
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: ['team1', 'team2', 'team3'],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: 'stakingRequirement',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
nTopPerformers: 'nTopPerformers',
},
};
const { container } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(container.querySelector('table')).toBeNull();
});
});
@@ -12,8 +12,6 @@ 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'];
@@ -106,12 +104,6 @@ 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}>
@@ -157,26 +149,14 @@ 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
transfer={transfer}
from={from || ''}
id={deterministicId}
/>
</div>
)}
</>
);
};
@@ -13,7 +13,6 @@ import {
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import Hash from '../../links/hash';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -61,7 +60,7 @@ export const TxDetailsTransfer = ({
}
const from = txData.submitter;
const id = txSignatureToDeterministicId(txData.signature.value);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -72,7 +71,7 @@ export const TxDetailsTransfer = ({
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
<Hash text={id} />
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
@@ -106,7 +105,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
</TableWithTbody>
<TransferDetails from={from} transfer={transfer} id={id} />
<TransferDetails from={from} transfer={transfer} />
</>
);
};
@@ -1,29 +0,0 @@
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,
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"short_name": "Explorer VEGA",
"name": "Vega Protocol - Explorer",
"icons": [
{
"src": "favicon.ico",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"short_name": "Governance VEGA",
"name": "Vega Protocol - Governance",
"icons": [
{
"src": "favicon.ico",
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vega Protocol static asseets</title>
<title>Vega Protocol static assets</title>
<link rel="stylesheet" href="fonts.css" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
+9 -7
View File
@@ -1,6 +1,12 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"name": "Vega Protocol - Trading",
"short_name": "Console",
"description": "Vega Protocol - Trading dApp",
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"theme_color": "#000000",
"background_color": "#ffffff",
"icons": [
{
"src": "favicon.ico",
@@ -12,9 +18,5 @@
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
]
}
@@ -56,6 +56,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
<Last24hPriceChange
marketId={market.id}
decimalPlaces={market.decimalPlaces}
fallback={<span>-</span>}
/>
</HeaderStat>
<HeaderStat heading={t('Volume (24h)')} testId="market-volume">
@@ -112,7 +113,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
heading={`${t('Funding Rate')} / ${t('Countdown')}`}
testId="market-funding"
>
<div className="flex justify-between gap-2">
<div className="flex gap-2">
<FundingRate marketId={market.id} />
<FundingCountdown marketId={market.id} />
</div>
@@ -3,7 +3,6 @@ import { type Market } from '@vegaprotocol/markets';
// TODO: handle oracle banner
// import { OracleBanner } from '@vegaprotocol/markets';
import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import classNames from 'classnames';
import {
Popover,
@@ -12,21 +11,21 @@ import {
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { MarketBanner } from '../../components/market-banner';
import { ErrorBoundary } from '../../components/error-boundary';
import { type TradingView } from './trade-views';
import { TradingViews } from './trade-views';
interface TradePanelsProps {
market: Market;
pinnedAsset?: PinnedAsset;
}
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('chart');
const viewCfg = TradingViews[view];
const [topView, setTopView] = useState<TradingView>('chart');
const topViewCfg = TradingViews[topView];
const [bottomView, setBottomView] = useState<TradingView>('positions');
const bottomViewCfg = TradingViews[bottomView];
const renderView = () => {
const renderView = (view: TradingView) => {
const Component = TradingViews[view].component;
if (!Component) {
@@ -39,12 +38,13 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
// so watch out for clashes in props
return (
<ErrorBoundary feature={view}>
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />
</ErrorBoundary>
);
};
const renderMenu = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderMenu = (viewCfg: any) => {
if ('menu' in viewCfg || 'settings' in viewCfg) {
return (
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
@@ -69,55 +69,80 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
};
return (
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
<div>
<MarketBanner market={market} />
</div>
<div>{renderMenu()}</div>
<div className="h-full relative">
<AutoSizer>
{({ width, height }) => (
<div style={{ width, height }} className="overflow-auto">
{renderView()}
</div>
)}
</AutoSizer>
</div>
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
{Object.keys(TradingViews)
// filter to control available views for the current market
// eg only perps should get the funding views
.filter((_key) => {
const key = _key as TradingView;
const perpOnlyViews = ['funding', 'fundingPayments'];
<div className="h-full flex flex-col lg:grid grid-rows-[min-content_min-content_1fr_min-content]">
<div className="flex flex-col w-full overflow-hidden">
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
{['chart', 'orderbook', 'trades', 'liquidity', 'fundingPayments']
// filter to control available views for the current market
// e.g. only perpetuals should get the funding views
.filter((_key) => {
const key = _key as TradingView;
const perpOnlyViews = ['funding', 'fundingPayments'];
if (
market?.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
return true;
}
if (perpOnlyViews.includes(key)) {
return false;
}
if (
market?.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
return true;
}
})
.map((_key) => {
const key = _key as TradingView;
const isActive = topView === key;
return (
<ViewButton
key={key}
view={key}
isActive={isActive}
onClick={() => {
setTopView(key);
}}
/>
);
})}
</div>
<div className="h-[50vh] lg:h-full relative">
<div>{renderMenu(topViewCfg)}</div>
<div className="overflow-auto h-full">{renderView(topView)}</div>
</div>
</div>
if (perpOnlyViews.includes(key)) {
return false;
}
return true;
})
.map((_key) => {
<div className="flex flex-col w-full grow">
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
{[
'positions',
'activeOrders',
'closedOrders',
'rejectedOrders',
'orders',
'stopOrders',
'collateral',
'fills',
].map((_key) => {
const key = _key as TradingView;
const isActive = view === key;
const isActive = bottomView === key;
return (
<ViewButton
key={key}
view={key}
isActive={isActive}
onClick={() => {
setView(key);
setBottomView(key);
}}
/>
);
})}
</div>
<div className="relative grow">
<div className="flex flex-col">{renderMenu(bottomViewCfg)}</div>
<div className="overflow-auto h-full">{renderView(bottomView)}</div>
</div>
</div>
</div>
);
@@ -157,7 +182,7 @@ const useViewLabel = (view: TradingView) => {
depth: t('Depth'),
liquidity: t('Liquidity'),
funding: t('Funding'),
fundingPayments: t('Funding Payments'),
fundingPayments: t('Funding'),
orderbook: t('Orderbook'),
trades: t('Trades'),
positions: t('Positions'),
@@ -3,7 +3,6 @@ import { Outlet } from 'react-router-dom';
import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
import classNames from 'classnames';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const LayoutWithSidebar = ({
header,
sidebar,
@@ -27,10 +26,13 @@ export const LayoutWithSidebar = ({
<div className={gridClasses}>
<div className="col-span-full">{header}</div>
<main
className={classNames('col-start-1 col-end-1 overflow-y-auto', {
'lg:col-end-3': !sidebarOpen,
'hidden lg:block lg:col-end-2': sidebarOpen,
})}
className={classNames(
'col-start-1 col-end-1 overflow-hidden lg:overflow-y-auto grow lg:grow-0',
{
'lg:col-end-3': !sidebarOpen,
'hidden lg:block lg:col-end-2': sidebarOpen,
}
)}
>
<Outlet />
</main>
@@ -1 +1,2 @@
export * from './market-header';
export * from './mobile-market-header';
@@ -0,0 +1,133 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { MarketSelector } from '../market-selector';
import {
Last24hPriceChange,
useMarket,
useMarketList,
} from '@vegaprotocol/markets';
import { useParams } from 'react-router-dom';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { useState } from 'react';
import { useT } from '../../lib/use-t';
import classNames from 'classnames';
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
import { MarketMarkPrice } from '../market-mark-price';
/**
* This is only rendered for the mobile navigation
*/
export const MobileMarketHeader = () => {
const t = useT();
const { marketId } = useParams();
const { data } = useMarket(marketId);
const [openMarket, setOpenMarket] = useState(false);
const [openPrice, setOpenPrice] = useState(false);
// Ensure that markets are kept cached so opening the list
// shows all markets instantly
useMarketList();
if (!marketId) return null;
return (
<div className="pl-3 pr-2 flex justify-between gap-2 h-10 bg-vega-clight-700 dark:bg-vega-cdark-700">
<FullScreenPopover
open={openMarket}
onOpenChange={(x) => {
setOpenMarket(x);
}}
trigger={
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-base leading-3 md:text-lg whitespace-nowrap">
{data
? data.tradableInstrument.instrument.code
: t('Select market')}
<span
className={classNames(
'transition-transform ease-in-out duration-300 flex',
{
'rotate-180': openMarket,
}
)}
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={16} />
</span>
</h1>
}
>
<MarketSelector
currentMarketId={marketId}
onSelect={() => setOpenMarket(false)}
/>
</FullScreenPopover>
<FullScreenPopover
open={openPrice}
onOpenChange={(x) => {
setOpenPrice(x);
}}
trigger={
<span className="flex gap-2 items-end md:text-md whitespace-nowrap leading-3">
{data && (
<>
<span className="text-xs">
<Last24hPriceChange
marketId={data.id}
decimalPlaces={data.decimalPlaces}
/>
</span>
<span className="flex items-center gap-1">
<MarketMarkPrice
marketId={data.id}
decimalPlaces={data.decimalPlaces}
/>
<VegaIcon
name={VegaIconNames.CHEVRON_DOWN}
size={16}
className={classNames(
'transition-transform ease-in-out duration-300',
{
'rotate-180': openPrice,
}
)}
/>
</span>
</>
)}
</span>
}
>
{data && (
<div className="px-3 py-6 text-sm grid grid-cols-2 items-center gap-x-4 gap-y-6">
<MarketHeaderStats market={data} />
</div>
)}
</FullScreenPopover>
</div>
);
};
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
trigger: React.ReactNode | string;
}
export const FullScreenPopover = ({
trigger,
children,
open,
onOpenChange,
}: PopoverProps) => {
return (
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
<PopoverPrimitive.Trigger data-testid="popover-trigger">
{trigger}
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-testid="popover-content"
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 border-y border-default"
sideOffset={0}
>
{children}
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
);
};
-1
View File
@@ -1,2 +1 @@
export * from './navbar';
export * from './nav-header';
@@ -1,81 +0,0 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { MarketSelector } from '../market-selector';
import { useMarket, useMarketList } from '@vegaprotocol/markets';
import { useParams } from 'react-router-dom';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { useState } from 'react';
import { useT } from '../../lib/use-t';
import classNames from 'classnames';
/**
* This is only rendered for the mobile navigation
*/
export const NavHeader = () => {
const t = useT();
const { marketId } = useParams();
const { data } = useMarket(marketId);
const [open, setOpen] = useState(false);
// Ensure that markets are kept cached so opening the list
// shows all markets instantly
useMarketList();
if (!marketId) return null;
return (
<FullScreenPopover
open={open}
onOpenChange={(x) => {
setOpen(x);
}}
trigger={
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
{data ? data.tradableInstrument.instrument.code : t('Select market')}
<span
className={classNames(
'transition-transform ease-in-out duration-300',
{
'rotate-180': open,
}
)}
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
</span>
</h1>
}
>
<MarketSelector
currentMarketId={marketId}
onSelect={() => setOpen(false)}
/>
</FullScreenPopover>
);
};
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
trigger: React.ReactNode | string;
}
export const FullScreenPopover = ({
trigger,
children,
open,
onOpenChange,
}: PopoverProps) => {
return (
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
<PopoverPrimitive.Trigger data-testid="popover-trigger">
{trigger}
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-testid="popover-content"
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 text-default border border-default"
sideOffset={5}
>
{children}
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
);
};
+1 -9
View File
@@ -34,13 +34,7 @@ import { supportedLngs } from '../../lib/i18n';
type MenuState = 'wallet' | 'nav' | null;
type Theme = 'system' | 'yellow';
export const Navbar = ({
children,
theme = 'system',
}: {
children?: ReactNode;
theme?: Theme;
}) => {
export const Navbar = ({ theme = 'system' }: { theme?: Theme }) => {
const i18n = useI18n();
const t = useT();
// menu state for small screens
@@ -75,8 +69,6 @@ export const Navbar = ({
>
<VLogo className="w-4" />
</NavLink>
{/* Left section */}
<div className="flex items-center lg:hidden">{children}</div>
{/* Used to show header in nav on mobile */}
<div className="hidden lg:block">
<NavbarMenu onClick={() => setMenu(null)} />
+2 -13
View File
@@ -14,7 +14,7 @@ import './styles.css';
import { usePageTitleStore } from '../stores';
import DialogsContainer from './dialogs-container';
import ToastsManager from './toasts-manager';
import { HashRouter, useLocation, Route, Routes } from 'react-router-dom';
import { HashRouter, useLocation } from 'react-router-dom';
import { Bootstrapper } from '../components/bootstrapper';
import { AnnouncementBanner } from '../components/banner';
import { Navbar } from '../components/navbar';
@@ -25,9 +25,7 @@ import {
ProtocolUpgradeProposalNotification,
} from '@vegaprotocol/proposals';
import { ViewingBanner } from '../components/viewing-banner';
import { NavHeader } from '../components/navbar/nav-header';
import { Telemetry } from '../components/telemetry';
import { Routes as AppRoutes } from '../lib/links';
import { SSRLoader } from './ssr-loader';
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
@@ -73,16 +71,7 @@ function AppBody({ Component }: AppProps) {
<Title />
<div className={gridClasses}>
<AnnouncementBanner />
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'}>
<Routes>
<Route
path={AppRoutes.MARKETS}
// render nothing for markets/all, otherwise markets/:marketId will match with markets/all
element={null}
/>
<Route path={AppRoutes.MARKET} element={<NavHeader />} />
</Routes>
</Navbar>
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} />
<div data-testid="banners">
<ProtocolUpgradeProposalNotification
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
+5 -2
View File
@@ -24,11 +24,14 @@ export default function Document() {
{/* scripts */}
<script src="/theme-setter.js" type="text/javascript" async />
{/* manifest */}
<link rel="manifest" href="/apps/trading/public/manifest.json" />
</Head>
<Html>
<body
// Nextjs will set body to display none until js runs. Because the entire app is client rendered
// and delivered via ipfs we override this to show a server side render loading animation until the
// Next.js will set body to display none until js runs. Because the entire app is client rendered
// and delivered via IPFS we override this to show a server side render loading animation until the
// js is downloaded and react takes over rendering
style={{ display: 'block' }}
className="bg-white dark:bg-vega-cdark-900 text-default font-alpha"
+6 -5
View File
@@ -23,7 +23,7 @@ import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-bo
import { compact } from 'lodash';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { LiquidityHeader } from '../components/liquidity-header';
import { MarketHeader } from '../components/market-header';
import { MarketHeader, MobileMarketHeader } from '../components/market-header';
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
@@ -33,6 +33,7 @@ import { CompetitionsTeams } from '../client-pages/competitions/competitions-tea
import { CompetitionsTeam } from '../client-pages/competitions/competitions-team';
import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team';
import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
// Using dynamic imports is a workaround for this until pennant is published as ESM
@@ -50,6 +51,9 @@ const NotFound = () => {
export const useRouterConfig = (): RouteObject[] => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const marketHeader = largeScreen ? <MarketHeader /> : <MobileMarketHeader />;
const routeConfig = compact([
{
index: true,
@@ -151,10 +155,7 @@ export const useRouterConfig = (): RouteObject[] => {
{
path: 'markets/*',
element: (
<LayoutWithSidebar
header={<MarketHeader />}
sidebar={<MarketsSidebar />}
/>
<LayoutWithSidebar header={marketHeader} sidebar={<MarketsSidebar />} />
),
children: [
{
+22
View File
@@ -0,0 +1,22 @@
{
"name": "Vega Protocol - Trading",
"short_name": "Console",
"description": "Vega Protocol - Trading dApp",
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"theme_color": "#000000",
"background_color": "#ffffff",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "cover.png",
"type": "image/png",
"sizes": "192x192"
}
]
}
@@ -18,12 +18,15 @@ interface Props {
initialValue?: string[];
isHeader?: boolean;
noUpdate?: boolean;
// render prop for no price change
fallback?: React.ReactNode;
}
export const Last24hPriceChange = ({
marketId,
decimalPlaces,
initialValue,
fallback,
}: Props) => {
const t = useT();
const { oneDayCandles, error, fiveDaysCandles } = useCandles({
@@ -48,13 +51,13 @@ export const Last24hPriceChange = ({
</span>
}
>
<span>-</span>
<span>{fallback}</span>
</Tooltip>
);
}
if (error || !isNumeric(decimalPlaces)) {
return <span>-</span>;
return <span>{fallback}</span>;
}
const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];
+6 -58
View File
@@ -14,32 +14,6 @@ export type Scalars = {
Timestamp: any;
};
/** Margins for a hypothetical position not related to any existing party */
export type AbstractMarginLevels = {
__typename?: 'AbstractMarginLevels';
/** Asset for the current margins */
asset: Asset;
/**
* If the margin of the party is greater than this level, then collateral will be released from the margin account into
* the general account of the party for the given asset.
*/
collateralReleaseLevel: Scalars['String'];
/** This is the minimum margin required for a party to place a new order on the network, expressed as unsigned integer */
initialLevel: Scalars['String'];
/** Minimal margin for the position to be maintained in the network (unsigned integer) */
maintenanceLevel: Scalars['String'];
/** Margin factor, only relevant for isolated margin mode, else 0 */
marginFactor: Scalars['String'];
/** Margin mode of the party, cross margin or isolated margin */
marginMode: MarginMode;
/** Market in which the margin is required for this party */
market: Market;
/** When in isolated margin, the required order margin level, otherwise, 0 */
orderMarginLevel: Scalars['String'];
/** If the margin is between maintenance and search, the network will initiate a collateral search, expressed as unsigned integer */
searchLevel: Scalars['String'];
};
/** An account record */
export type AccountBalance = {
__typename?: 'AccountBalance';
@@ -385,8 +359,6 @@ export enum AuctionTrigger {
export type BatchProposal = {
__typename?: 'BatchProposal';
/** Terms of all the proposals in the batch */
batchTerms?: Maybe<BatchProposalTerms>;
/** RFC3339Nano time and date when the proposal reached the network */
datetime: Scalars['Timestamp'];
/** Details of the rejection reason */
@@ -417,10 +389,10 @@ export type BatchProposal = {
votes: ProposalVotes;
};
/** The terms for the batch proposal */
/** The rationale for the proposal */
export type BatchProposalTerms = {
__typename?: 'BatchProposalTerms';
/** Actual changes being introduced by the batch proposal - actions the proposal triggers if passed and enacted. */
/** Actual changes being introduced by the proposal - actions the proposal triggers if passed and enacted. */
changes: Array<Maybe<BatchProposalTermsChange>>;
/**
* RFC3339Nano time and date when voting closes for this proposal.
@@ -559,22 +531,6 @@ export type CompositePriceConfiguration = {
decayWeight: Scalars['String'];
};
export type CompositePriceSource = {
__typename?: 'CompositePriceSource';
/** The source of the price */
PriceSource: Scalars['String'];
/** The last time the price source was updated in RFC3339Nano */
lastUpdated: Scalars['Timestamp'];
/** The current value of the composite source price */
price: Scalars['String'];
};
export type CompositePriceState = {
__typename?: 'CompositePriceState';
/** Underlying state of the composite price */
priceSources?: Maybe<Array<CompositePriceSource>>;
};
export enum CompositePriceType {
/** Composite price is set to the last trade (legacy) */
COMPOSITE_PRICE_TYPE_LAST_TRADE = 'COMPOSITE_PRICE_TYPE_LAST_TRADE',
@@ -2209,9 +2165,9 @@ export type MarginEdge = {
export type MarginEstimate = {
__typename?: 'MarginEstimate';
/** Margin level estimate assuming no slippage */
bestCase: AbstractMarginLevels;
bestCase: MarginLevels;
/** Margin level estimate assuming slippage cap is applied */
worstCase: AbstractMarginLevels;
worstCase: MarginLevels;
};
/** Margins for a given a party */
@@ -2483,8 +2439,6 @@ export type MarketData = {
liquidityProviderSla?: Maybe<Array<LiquidityProviderSLA>>;
/** The mark price (an unsigned integer) */
markPrice: Scalars['String'];
/** State of the underlying internal composite price */
markPriceState?: Maybe<CompositePriceState>;
/** The methodology used for the calculation of the mark price */
markPriceType: CompositePriceType;
/** Market of the associated mark price */
@@ -3099,8 +3053,6 @@ export type ObservableMarketData = {
liquidityProviderSla?: Maybe<Array<ObservableLiquidityProviderSLA>>;
/** The mark price (an unsigned integer) */
markPrice: Scalars['String'];
/** State of the underlying internal composite price */
markPriceState?: Maybe<CompositePriceState>;
/** The methodology used to calculated mark price */
markPriceType: CompositePriceType;
/** The market growth factor for the last market time window */
@@ -4069,8 +4021,6 @@ export type PerpetualData = {
fundingRate?: Maybe<Scalars['String']>;
/** Internal composite price used as input to the internal VWAP */
internalCompositePrice: Scalars['String'];
/** The internal state of the underlying internal composite price */
internalCompositePriceState?: Maybe<CompositePriceState>;
/** The methodology used to calculated internal composite price for perpetual markets */
internalCompositePriceType: CompositePriceType;
/** Time-weighted average price calculated from data points for this period from the internal data source. */
@@ -4081,8 +4031,6 @@ export type PerpetualData = {
seqNum: Scalars['Int'];
/** Time at which the funding period started */
startTime: Scalars['Timestamp'];
/** The last value from the external oracle */
underlyingIndexPrice: Scalars['String'];
};
export type PerpetualProduct = {
@@ -4380,7 +4328,7 @@ export type ProposalDetail = {
__typename?: 'ProposalDetail';
/** Batch proposal ID that is provided by Vega once proposal reaches the network */
batchId?: Maybe<Scalars['ID']>;
/** Terms of all the proposals in the batch */
/** Terms of the proposal for a batch proposal */
batchTerms?: Maybe<BatchProposalTerms>;
/** RFC3339Nano time and date when the proposal reached the Vega network */
datetime: Scalars['Timestamp'];
@@ -4406,7 +4354,7 @@ export type ProposalDetail = {
requiredParticipation: Scalars['String'];
/** State of the proposal */
state: ProposalState;
/** Terms of the proposal */
/** Terms of the proposal for proposal */
terms?: Maybe<ProposalTerms>;
};