Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf391bd5ef | ||
|
|
d77a8792fc | ||
|
|
64adba4eec |
@@ -77,7 +77,6 @@
|
||||
"fixStyle": "inline-type-imports"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-useless-constructor": 0,
|
||||
"curly": ["error", "multi-line"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
* text eol=lf
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.woff2 binary
|
||||
|
||||
+2
-1
@@ -59,4 +59,5 @@ apps/trading/e2e/logs/
|
||||
apps/trading/e2e/.pytest_cache/
|
||||
apps/trading/e2e/traces/
|
||||
|
||||
.nx/
|
||||
.nx/cache
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Auto-format all files
|
||||
yarn nx format:write
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files - this brings more value as pre-commit
|
||||
# yarn nx format:check
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
# yarn nx affected -t test --exclude trading
|
||||
yarn nx affected -t test --exclude trading
|
||||
|
||||
@@ -12,8 +12,7 @@ import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-web
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { useRouterConfig } from './routes/router-config';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { router } from './routes/router-config';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@@ -39,10 +38,7 @@ function App() {
|
||||
}
|
||||
>
|
||||
<Suspense fallback={splashLoading}>
|
||||
<RouterProvider
|
||||
router={createBrowserRouter(useRouterConfig())}
|
||||
fallbackElement={splashLoading}
|
||||
/>
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
</Suspense>
|
||||
</NodeGuard>
|
||||
<NodeSwitcherDialog
|
||||
|
||||
@@ -12,8 +12,7 @@ import { type VegaICellRendererParams } from '@vegaprotocol/datagrid';
|
||||
import { useRef, useLayoutEffect } from 'react';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { type ColDef } from 'ag-grid-community';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import { type RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
|
||||
type AssetsTableProps = {
|
||||
data: AssetFieldsFragment[] | null;
|
||||
@@ -24,7 +23,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef<AgGridReact>(null);
|
||||
const showColumnsOnDesktop = () => {
|
||||
ref.current?.api.setColumnsVisible(
|
||||
ref.current?.columnApi.setColumnsVisible(
|
||||
['id', 'type', 'status'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import { isNavigable } from '../../routes/router-config';
|
||||
import { useRouterConfig } from '../../routes/router-config';
|
||||
import { routerConfig } from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
@@ -26,7 +26,6 @@ const routeToNavigationItem = (r: Navigable) => (
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const routerConfig = useRouterConfig();
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const pages = routerConfig[0].children || [];
|
||||
const mainItems = compact(
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
['id', 'state', 'asset'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Time } from '../time';
|
||||
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
||||
import SizeInMarket from '../size-in-market/size-in-market';
|
||||
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
|
||||
import { OrderTypeMapping } from '@vegaprotocol/types';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
@@ -70,7 +69,7 @@ const DeterministicOrderDetails = ({
|
||||
<span className="mx-5 text-base">@</span>
|
||||
<PriceInMarket price={o.price} marketId={o.market.id} />
|
||||
</h2>
|
||||
<p className="text-gray-400 dark:text-gray-600">
|
||||
<p className="text-gray-200">
|
||||
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
||||
</p>
|
||||
{o.peggedOrder ? (
|
||||
@@ -84,12 +83,13 @@ const DeterministicOrderDetails = ({
|
||||
/>
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{o.reference ? (
|
||||
<p className="text-gray-500 mt-4">
|
||||
<span>{t('Reference')}</span>: {o.reference}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
|
||||
<div className="grid md:grid-cols-4 gap-x-6 mt-4">
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Status')}
|
||||
@@ -114,16 +114,6 @@ const DeterministicOrderDetails = ({
|
||||
{o.version}
|
||||
</h5>
|
||||
</div>
|
||||
{o.type ? (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Type')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{OrderTypeMapping[o.type]}
|
||||
</h5>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
type VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { type ColDef } from 'ag-grid-community';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import { type RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -44,11 +43,11 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
['voting', 'cDate', 'eDate', 'type'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
gridRef.current?.api.setColumnWidth(
|
||||
gridRef.current?.columnApi.setColumnWidth(
|
||||
'actions',
|
||||
window.innerWidth > BREAKPOINT_MD ? 221 : 80
|
||||
);
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface SignatureProps {
|
||||
signature: BlockExplorerTransactionResult['signature'];
|
||||
}
|
||||
|
||||
const valueClass =
|
||||
'font-mono px-2.5 py-0.5 text-xs max-w-[200px] cursor-pointer';
|
||||
const valueClassClosed = 'text-ellipsis overflow-hidden';
|
||||
const valueClassOpen = 'break-words text-left';
|
||||
|
||||
/**
|
||||
* Viewer component for a vega signature. Featuers copy and pasting, truncation
|
||||
*
|
||||
* @param signature
|
||||
*/
|
||||
export const Signature = ({ signature }: SignatureProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
if (!signature || !signature.value || !signature.version || !signature.algo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-flex border rounded signature-component relative pr-[20px]">
|
||||
<span
|
||||
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
|
||||
title={`Version ${signature.version}`}
|
||||
>
|
||||
{signature.algo}
|
||||
</span>
|
||||
<div
|
||||
className={
|
||||
isOpen
|
||||
? `${valueClass} ${valueClassOpen}`
|
||||
: `${valueClass} ${valueClassClosed}`
|
||||
}
|
||||
>
|
||||
<CopyWithTooltip text={signature.value}>
|
||||
<span title={signature.value}>{signature.value}</span>
|
||||
</CopyWithTooltip>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="absolute top-[-3px] right-0 pr-2"
|
||||
title={t('Show full signature')}
|
||||
>
|
||||
<VegaIcon name={isOpen ? VegaIconNames.EYE_OFF : VegaIconNames.EYE} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -9,7 +9,6 @@ import { Time } from '../../../time';
|
||||
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
import { TxDataView } from '../../tx-data-view';
|
||||
import Hash from '../../../links/hash';
|
||||
import { Signature } from '../../../signature/signature';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -76,12 +75,6 @@ export const TxDetailsShared = ({
|
||||
<BlockLink height={height} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Signature')}</TableCell>
|
||||
<TableCell>
|
||||
<Signature signature={txData.signature} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -80,12 +80,6 @@ export function getLabelForOrderType(
|
||||
if (command.orderSubmission.icebergOpts) {
|
||||
return 'Iceberg';
|
||||
}
|
||||
if (command.orderSubmission.type === 'TYPE_MARKET') {
|
||||
return 'Market order';
|
||||
}
|
||||
if (command.orderSubmission.type === 'TYPE_LIMIT') {
|
||||
return 'Limit order';
|
||||
}
|
||||
}
|
||||
return 'Order';
|
||||
}
|
||||
|
||||
@@ -98,8 +98,6 @@ describe('TxDetailsTransfer', () => {
|
||||
},
|
||||
},
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value:
|
||||
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
|
||||
},
|
||||
|
||||
@@ -20,8 +20,6 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||
type: 'Submit Order',
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value: '123',
|
||||
},
|
||||
code: 0,
|
||||
|
||||
@@ -54,7 +54,7 @@ const Block = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{blockData && 'result' in blockData && (
|
||||
{blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -18,6 +18,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from './route-names';
|
||||
import { NetworkParameters } from './network-parameters';
|
||||
import type { Params, RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -28,7 +29,7 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||
import { Disclaimer } from './pages/disclaimer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
|
||||
export type Navigable = {
|
||||
@@ -59,315 +60,311 @@ type Route = RouteItem & {
|
||||
children?: RouteItem[];
|
||||
};
|
||||
|
||||
export const useRouterConfig = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
|
||||
const partiesRoutes: Route[] = featureFlags.EXPLORER_PARTIES
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||
const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartyAccountsByAsset />,
|
||||
handle: {
|
||||
breadcrumb: () => {
|
||||
return t('Assets');
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartyAccountsByAsset />,
|
||||
handle: {
|
||||
breadcrumb: () => {
|
||||
return t('Assets');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const assetsRoutes: Route[] = featureFlags.EXPLORER_ASSETS
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
handle: {
|
||||
name: t('Assets'),
|
||||
text: t('Assets'),
|
||||
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <AssetsPage />,
|
||||
},
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const genesisRoutes: Route[] = featureFlags.EXPLORER_GENESIS
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
handle: {
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
|
||||
),
|
||||
],
|
||||
},
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const governanceRoutes: Route[] = featureFlags.EXPLORER_GOVERNANCE
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
handle: {
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Proposals />,
|
||||
const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
handle: {
|
||||
name: t('Assets'),
|
||||
text: t('Assets'),
|
||||
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const marketsRoutes: Route[] = featureFlags.EXPLORER_MARKETS
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
handle: {
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketsPage />,
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes: Route[] =
|
||||
featureFlags.EXPLORER_NETWORK_PARAMETERS
|
||||
? [
|
||||
children: [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
index: true,
|
||||
element: <AssetsPage />,
|
||||
},
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
handle: {
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const genesisRoutes: Route[] = FLAGS.EXPLORER_GENESIS
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
handle: {
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const governanceRoutes: Route[] = FLAGS.EXPLORER_GOVERNANCE
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
handle: {
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Proposals />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const marketsRoutes: Route[] = FLAGS.EXPLORER_MARKETS
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
handle: {
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketsPage />,
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes: Route[] = FLAGS.EXPLORER_NETWORK_PARAMETERS
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
handle: {
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = FLAGS.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
},
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const linkTo = (...segments: (string | undefined)[]) =>
|
||||
compact(segments).join('/');
|
||||
|
||||
export const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
element: <Layout />,
|
||||
handle: {
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
handle: {
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
||||
{truncateMiddle(remove0x(params.txHash as string))}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const linkTo = (...segments: (string | undefined)[]) =>
|
||||
compact(segments).join('/');
|
||||
|
||||
const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
element: <Layout />,
|
||||
handle: {
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
|
||||
],
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
handle: {
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
||||
{truncateMiddle(remove0x(params.txHash as string))}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||
{params.block}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||
{params.block}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
handle: {
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
|
||||
},
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.ORACLES, params.id)}>
|
||||
{truncateMiddle(params.id as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.DISCLAIMER,
|
||||
element: <Disclaimer />,
|
||||
handle: {
|
||||
name: t('Disclaimer'),
|
||||
text: t('Disclaimer'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.RESTRICTED,
|
||||
element: <RestrictedPage />,
|
||||
handle: {
|
||||
name: t('Restricted'),
|
||||
text: t('Restricted'),
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
handle: {
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
|
||||
},
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.ORACLES, params.id)}>
|
||||
{truncateMiddle(params.id as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.DISCLAIMER,
|
||||
element: <Disclaimer />,
|
||||
handle: {
|
||||
name: t('Disclaimer'),
|
||||
text: t('Disclaimer'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.RESTRICTED,
|
||||
element: <RestrictedPage />,
|
||||
handle: {
|
||||
name: t('Restricted'),
|
||||
text: t('Restricted'),
|
||||
},
|
||||
];
|
||||
return routerConfig;
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter(routerConfig);
|
||||
|
||||
@@ -23,8 +23,6 @@ const txData: BlockExplorerTransactionResult = {
|
||||
type: 'type',
|
||||
command: {} as ValidatorHeartbeat,
|
||||
signature: {
|
||||
version: '1',
|
||||
algo: 'vega/ed25519',
|
||||
value: '123',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,8 +11,6 @@ export interface BlockExplorerTransactionResult {
|
||||
cursor: string;
|
||||
command: components['schemas']['blockexplorerv1transaction'];
|
||||
signature: {
|
||||
version: string;
|
||||
algo: string;
|
||||
value: string;
|
||||
};
|
||||
error?: string;
|
||||
|
||||
@@ -32,7 +32,7 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -23,7 +23,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -22,7 +22,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -21,7 +21,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React, { Suspense } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,23 +15,21 @@ import {
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { useConnectors } from './lib/vega-connectors';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const useVegaWalletEagerConnect = () => {
|
||||
const connectors = useConnectors();
|
||||
const vegaConnecting = useEagerConnect(connectors);
|
||||
const vegaConnecting = useEagerConnect(Connectors);
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = React.useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(connectors.view);
|
||||
connect(Connectors['view']);
|
||||
}
|
||||
return vegaConnecting;
|
||||
};
|
||||
|
||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWeb3React();
|
||||
const { VEGA_URL } = useEnvironment();
|
||||
@@ -81,16 +79,10 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
run();
|
||||
}
|
||||
}, [
|
||||
token,
|
||||
appDispatch,
|
||||
staking,
|
||||
vesting,
|
||||
featureFlags.GOVERNANCE_NETWORK_DOWN,
|
||||
]);
|
||||
}, [token, appDispatch, staking, vesting]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (account && pubKey) {
|
||||
@@ -155,16 +147,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
};
|
||||
|
||||
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
|
||||
if (featureFlags.GOVERNANCE_NETWORK_LIMITS) {
|
||||
if (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
|
||||
getNetworkLimits();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopPoll();
|
||||
};
|
||||
}, [appDispatch, VEGA_URL, t, featureFlags.GOVERNANCE_NETWORK_LIMITS]);
|
||||
}, [appDispatch, VEGA_URL, t]);
|
||||
|
||||
if (featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashError />
|
||||
|
||||
@@ -23,13 +23,10 @@ export const Heading = ({
|
||||
})}
|
||||
>
|
||||
<h1
|
||||
className={classNames(
|
||||
'font-alpha calt text-5xl [word-break:break-word]',
|
||||
{
|
||||
'mt-0': !marginTop,
|
||||
'mb-0': !marginBottom,
|
||||
}
|
||||
)}
|
||||
className={classNames('font-alpha calt text-5xl break-words', {
|
||||
'mt-0': !marginTop,
|
||||
'mb-0': !marginBottom,
|
||||
})}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
@@ -7,16 +7,16 @@ import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useConnectors } from '../../lib/vega-connectors';
|
||||
import { Connectors } from '../../lib/vega-connectors';
|
||||
import { RiskMessage } from './risk-message';
|
||||
|
||||
export const VegaWalletDialogs = () => {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
const connectors = useConnectors();
|
||||
|
||||
return (
|
||||
<>
|
||||
<VegaConnectDialog
|
||||
connectors={connectors}
|
||||
connectors={Connectors}
|
||||
riskMessage={<RiskMessage />}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const VegaWalletDialogs = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
<ViewAsDialog connector={connectors.view} />
|
||||
<ViewAsDialog connector={Connectors.view} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,8 +49,12 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
? activeProvider
|
||||
: defaultProvider;
|
||||
|
||||
if (account && provider && typeof provider.getSigner === 'function') {
|
||||
signer = provider.getSigner(account);
|
||||
if (
|
||||
account &&
|
||||
activeProvider &&
|
||||
typeof activeProvider.getSigner === 'function'
|
||||
) {
|
||||
signer = provider.getSigner();
|
||||
}
|
||||
|
||||
const tokenVestingAddress =
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useMemo } from 'react';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
@@ -14,17 +13,13 @@ export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const useConnectors = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
return useMemo(
|
||||
() => ({
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
||||
}),
|
||||
[featureFlags.METAMASK_SNAPS]
|
||||
);
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
|
||||
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
|
||||
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||
import Routes from '../routes';
|
||||
import { ExternalLinks, useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { ExternalLinks, FLAGS } from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
@@ -175,7 +175,6 @@ export const ValidatorDetailsLink = ({
|
||||
};
|
||||
|
||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -187,9 +186,9 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
-3
@@ -7,10 +7,8 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
|
||||
export const ProposalAssetDetails = ({
|
||||
asset,
|
||||
originalAsset,
|
||||
}: {
|
||||
asset: AssetFieldsFragment;
|
||||
originalAsset?: AssetFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
||||
@@ -29,7 +27,6 @@ export const ProposalAssetDetails = ({
|
||||
<div className="mb-10 pb-4">
|
||||
<AssetDetailsTable
|
||||
asset={asset}
|
||||
originalAsset={originalAsset}
|
||||
omitRows={[
|
||||
AssetDetail.STATUS,
|
||||
AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
|
||||
|
||||
+3
-2
@@ -19,7 +19,7 @@ import {
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||
@@ -62,7 +62,8 @@ describe('Proposal header', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('Renders New market proposal', () => {
|
||||
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = true;
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
rationale: {
|
||||
|
||||
+7
-9
@@ -12,7 +12,7 @@ import {
|
||||
useNewTransferProposalDetails,
|
||||
useSuccessorMarketProposalDetails,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { VoteState } from '../vote-details/use-user-vote';
|
||||
@@ -28,7 +28,6 @@ export const ProposalHeader = ({
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const change = proposal?.terms.change;
|
||||
|
||||
@@ -55,14 +54,13 @@ export const ProposalHeader = ({
|
||||
switch (change?.__typename) {
|
||||
case 'NewMarket': {
|
||||
proposalType =
|
||||
featureFlags.PRODUCT_PERPETUALS &&
|
||||
change?.instrument?.product?.__typename
|
||||
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
|
||||
? `NewMarket${change?.instrument?.product?.__typename}`
|
||||
: 'NewMarket';
|
||||
fallbackTitle = t('NewMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
{featureFlags.SUCCESSOR_MARKETS && (
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<SuccessorCode proposalId={proposal?.id} />
|
||||
)}
|
||||
<span>
|
||||
@@ -84,13 +82,13 @@ export const ProposalHeader = ({
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
proposalType =
|
||||
featureFlags.UPDATE_MARKET_STATE && change?.updateType
|
||||
FLAGS.UPDATE_MARKET_STATE && change?.updateType
|
||||
? t(change.updateType)
|
||||
: 'UpdateMarketState';
|
||||
fallbackTitle = t('UpdateMarketStateProposal');
|
||||
details = (
|
||||
<span>
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
{FLAGS.UPDATE_MARKET_STATE &&
|
||||
change?.market?.id &&
|
||||
change.updateType ? (
|
||||
<>
|
||||
@@ -179,14 +177,14 @@ export const ProposalHeader = ({
|
||||
case 'NewTransfer':
|
||||
proposalType = 'NewTransfer';
|
||||
fallbackTitle = t('NewTransferProposal');
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
<NewTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
proposalType = 'CancelTransfer';
|
||||
fallbackTitle = t('CancelTransferProposal');
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
<CancelTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
|
||||
+2
-2
@@ -54,8 +54,8 @@ export const ProposalReferralProgramDetails = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers.slice();
|
||||
const stakingTiers = proposal?.terms?.change?.stakingTiers.slice();
|
||||
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||
const stakingTiers = proposal?.terms?.change?.stakingTiers;
|
||||
const windowLength = proposal?.terms?.change?.windowLength;
|
||||
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
ProposalCancelTransferDetails,
|
||||
ProposalTransferDetails,
|
||||
} from '../proposal-transfer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
|
||||
export interface ProposalProps {
|
||||
@@ -53,7 +53,6 @@ export const Proposal = ({
|
||||
originalMarketProposalRestData,
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
}: ProposalProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||
@@ -66,13 +65,10 @@ export const Proposal = ({
|
||||
? removePaginationWrapper(assetData.assetsConnection?.edges)[0]
|
||||
: undefined;
|
||||
|
||||
const originalAsset = asset;
|
||||
|
||||
if (proposal.terms.change.__typename === 'UpdateAsset' && asset) {
|
||||
asset = {
|
||||
...asset,
|
||||
quantum: proposal.terms.change.quantum,
|
||||
source: { ...asset.source },
|
||||
};
|
||||
|
||||
if (asset.source.__typename === 'ERC20') {
|
||||
@@ -133,7 +129,7 @@ export const Proposal = ({
|
||||
}
|
||||
|
||||
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
||||
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
|
||||
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
|
||||
<>
|
||||
{proposal.terms.change.__typename === 'NewTransfer' && (
|
||||
/** Governance New Transfer Details */
|
||||
@@ -232,7 +228,7 @@ export const Proposal = ({
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} originalAsset={originalAsset} />
|
||||
<ProposalAssetDetails asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
+2
-2
@@ -281,8 +281,8 @@ describe('VoteBreakdown', () => {
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP majority', () => {
|
||||
const yesVotesLP = 0.8;
|
||||
const noVotesLP = 0.2;
|
||||
const yesVotesLP = 800;
|
||||
const noVotesLP = 200;
|
||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||
|
||||
renderComponent(
|
||||
|
||||
@@ -105,6 +105,8 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
noEquityLikeShareWeight,
|
||||
totalEquityLikeShareWeight,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
@@ -133,7 +135,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
.multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
);
|
||||
|
||||
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
@@ -201,24 +202,50 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
description={formatNumber(
|
||||
yesEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>{yesLPPercentage.toFixed(1)}%</button>
|
||||
<button>
|
||||
<CompactVotes number={yesEquityLikeShareWeight} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{yesLPPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
noEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={noEquityLikeShareWeight} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
||||
<button>{noLPPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,8 +282,13 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
||||
<button>
|
||||
<CompactVotes number={totalEquityLikeShareWeight} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -54,8 +54,8 @@ describe('use-vote-information', () => {
|
||||
it('returns all required vote information', () => {
|
||||
const yesVotes = 40;
|
||||
const noVotes = 60;
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.70';
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '70';
|
||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
@@ -195,10 +195,10 @@ describe('use-vote-information', () => {
|
||||
});
|
||||
|
||||
it('correctly shows whether an update market proposal will pass by token or LP vote - both failing', () => {
|
||||
const yesVotes = 0.2;
|
||||
const noVotes = 0.7;
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.60';
|
||||
const yesVotes = 20;
|
||||
const noVotes = 70;
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '60';
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const proposal = generateProposal({
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useVoteInformation = ({
|
||||
const noEquityLikeShareWeight = !proposal?.votes.no
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight).times(100);
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight);
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(proposal?.votes.yes.totalTokens ?? 0, decimals)
|
||||
@@ -70,7 +70,7 @@ export const useVoteInformation = ({
|
||||
const yesEquityLikeShareWeight = !proposal?.votes.yes
|
||||
.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight);
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
@@ -81,7 +81,12 @@ export const useVoteInformation = ({
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
||||
|
||||
const yesLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalEquityLikeShareWeight);
|
||||
|
||||
const noPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
@@ -98,7 +103,9 @@ export const useVoteInformation = ({
|
||||
);
|
||||
|
||||
const participationLPMet = requiredParticipationLP
|
||||
? totalEquityLikeShareWeight.isGreaterThan(requiredParticipationLP)
|
||||
? totalEquityLikeShareWeight.isGreaterThan(
|
||||
totalSupply.multipliedBy(requiredParticipationLP)
|
||||
)
|
||||
: false;
|
||||
|
||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||
@@ -113,7 +120,9 @@ export const useVoteInformation = ({
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const willPassByTokenVote =
|
||||
participationMet &&
|
||||
|
||||
@@ -15,11 +15,10 @@ import {
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||
|
||||
export const ProposalContainer = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const [
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
setMostRecentlyEnactedAssociatedMarketProposal,
|
||||
@@ -60,9 +59,9 @@ export const ProposalContainer = () => {
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
proposalId: params.proposalId || '',
|
||||
includeNewMarketProductField: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!featureFlags.REFERRALS,
|
||||
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||
},
|
||||
skip: !params.proposalId,
|
||||
});
|
||||
@@ -121,7 +120,7 @@ export const ProposalContainer = () => {
|
||||
variables: {
|
||||
marketId: marketData?.id || '',
|
||||
},
|
||||
skip: !featureFlags.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
||||
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -134,7 +133,7 @@ export const ProposalContainer = () => {
|
||||
variables: {
|
||||
marketId: parentMarketId?.market?.parentMarketID || '',
|
||||
skip:
|
||||
!featureFlags.SUCCESSOR_MARKETS ||
|
||||
!FLAGS.SUCCESSOR_MARKETS ||
|
||||
!isSuccessor ||
|
||||
!parentMarketId?.market?.parentMarketID,
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from './__generated__/Proposals';
|
||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
|
||||
return flow([
|
||||
@@ -43,16 +43,15 @@ export function getNotRejectedProtocolUpgradeProposals<
|
||||
}
|
||||
|
||||
export const ProposalsContainer = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { data, loading, error } = useProposalsQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ export const ProtocolUpgradeProposalContainer = () => {
|
||||
time={
|
||||
pending && time ? (
|
||||
convertToCountdownString(time, '0:00:00:00')
|
||||
) : blockInfo && 'result' in blockInfo && blockInfo?.result ? (
|
||||
) : blockInfo?.result ? (
|
||||
<span title={blockInfo.result.block.header.time}>
|
||||
{formatDateWithLocalTimezone(
|
||||
new Date(blockInfo.result.block.header.time)
|
||||
|
||||
@@ -10,7 +10,7 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import flow from 'lodash/flow';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
||||
orderBy(
|
||||
@@ -33,16 +33,15 @@ export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
|
||||
}
|
||||
|
||||
export const RejectedProposalsContainer = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const { data, loading, error } = useProposalsQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -116,8 +116,7 @@ export const generateYesVotes = (
|
||||
fixedTokenValue?: number,
|
||||
totalEquityLikeShareWeight?: string
|
||||
): Votes => {
|
||||
const votes = [];
|
||||
for (let i = 0; i < numberOfVotes; i++) {
|
||||
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||
const vote: Vote = {
|
||||
__typename: 'Vote',
|
||||
value: Schema.VoteValue.VALUE_YES,
|
||||
@@ -153,9 +152,8 @@ export const generateYesVotes = (
|
||||
datetime: faker.date.past().toISOString(),
|
||||
};
|
||||
|
||||
votes.push(vote);
|
||||
}
|
||||
|
||||
return vote;
|
||||
});
|
||||
return {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: votes.length.toString(),
|
||||
@@ -174,8 +172,7 @@ export const generateNoVotes = (
|
||||
fixedTokenValue?: number,
|
||||
totalEquityLikeShareWeight?: string
|
||||
): Votes => {
|
||||
const votes = [];
|
||||
for (let i = 0; i < numberOfVotes; i++) {
|
||||
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||
const vote: Vote = {
|
||||
__typename: 'Vote',
|
||||
value: Schema.VoteValue.VALUE_NO,
|
||||
@@ -210,9 +207,8 @@ export const generateNoVotes = (
|
||||
},
|
||||
datetime: faker.date.past().toISOString(),
|
||||
};
|
||||
votes.push(vote);
|
||||
}
|
||||
|
||||
return vote;
|
||||
});
|
||||
return {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: votes.length.toString(),
|
||||
|
||||
@@ -104,10 +104,6 @@
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.react-markdown-container a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jsondiffpatch-delta,
|
||||
.jsondiffpatch-delta pre {
|
||||
font-family: 'Roboto Mono', monospace !important;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const expirtyTooltip = 'expiry-tooltip';
|
||||
const externalLink = 'external-link';
|
||||
const itemHeader = 'item-header';
|
||||
const itemValue = 'item-value';
|
||||
const link = 'link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const liquiditySupplied = 'liquidity-supplied';
|
||||
const liquiditySuppliedTooltip = 'liquidity-supplied-tooltip';
|
||||
const marketChange = 'market-change';
|
||||
const marketExpiry = 'market-expiry';
|
||||
const marketMode = 'market-trading-mode';
|
||||
const marketName = 'header-title';
|
||||
const marketPrice = 'market-price';
|
||||
const marketSettlement = 'market-settlement-asset';
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const marketVolume = 'market-volume';
|
||||
const percentageValue = 'price-change-percentage';
|
||||
const priceChangeValue = 'price-change';
|
||||
const tradingModeTooltip = 'trading-mode-tooltip';
|
||||
|
||||
describe('Market trading page', () => {
|
||||
before(() => {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@MarketData');
|
||||
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Market summary', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-001
|
||||
// 7002-SORD-002
|
||||
it('must display market name', () => {
|
||||
// 6002-MDET-001
|
||||
cy.getByTestId(marketName).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('must see market expiry', () => {
|
||||
// 6002-MDET-002
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketExpiry).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market price', () => {
|
||||
// 6002-MDET-003
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketPrice).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Mark Price');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market change', () => {
|
||||
// 6002-MDET-004
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketChange).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
|
||||
cy.getByTestId(percentageValue).should('not.be.empty');
|
||||
cy.getByTestId(priceChangeValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market volume', () => {
|
||||
// 6002-MDET-005
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketVolume).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market settlement', () => {
|
||||
// 6002-MDET-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketSettlement).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Market tooltips', { tags: '@smoke' }, () => {
|
||||
it('should see expiry tooltip', () => {
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketExpiry).within(() => {
|
||||
cy.getByTestId(itemValue)
|
||||
.should('have.text', 'Not time-based')
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(expirtyTooltip)
|
||||
.eq(0)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market expires when triggered by its oracle, not on a set date.'
|
||||
)
|
||||
.within(() => {
|
||||
cy.getByTestId(link)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('EXPLORER_URL'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should see trading conditions tooltip', () => {
|
||||
const toolTipLabel = 'tooltip-label';
|
||||
const toolTipValue = 'tooltip-value';
|
||||
const auctionToolTipLabels = [
|
||||
'Auction start',
|
||||
'Est. auction end',
|
||||
'Target liquidity',
|
||||
'Current liquidity',
|
||||
'Est. uncrossing price',
|
||||
'Est. uncrossing vol',
|
||||
];
|
||||
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketMode).within(() => {
|
||||
cy.getByTestId(itemValue)
|
||||
.should('contain.text', 'Monitoring auction')
|
||||
.and('contain.text', 'liquidity')
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(tradingModeTooltip)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market is in auction until it reaches sufficient liquidity.'
|
||||
)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.getByTestId(externalLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('TRADING_MODE_LINK'));
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
cy.getByTestId(toolTipLabel)
|
||||
.eq(i)
|
||||
.should('have.text', auctionToolTipLabels[i]);
|
||||
cy.getByTestId(toolTipValue).eq(i).should('not.be.empty');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should see liquidity supplied tooltip', () => {
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(liquiditySupplied).within(() => {
|
||||
cy.getByTestId(itemValue).realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquiditySuppliedTooltip)
|
||||
.should('contain.text', 'Supplied stake')
|
||||
.and('contain.text', 'Target stake')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
cy.getByTestId(externalLink).should(
|
||||
'have.text',
|
||||
'Learn about providing liquidity'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
|
||||
describe(
|
||||
'vega wallet - prompt',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
describe('must submit order', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-039
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('must see a prompt to check connected vega wallet to approve transaction', () => {
|
||||
// 0003-WTXN-002
|
||||
cy.mockVegaWalletTransaction(1000);
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
cy.getByTestId('toast-content').should(
|
||||
'contain.text',
|
||||
'Please go to your Vega wallet application and approve or reject the transaction.'
|
||||
);
|
||||
});
|
||||
|
||||
it('must show error returned by wallet ', () => {
|
||||
// 0003-WTXN-009
|
||||
// 0003-WTXN-011
|
||||
// 0002-WCON-016
|
||||
// 0003-WTXN-008
|
||||
|
||||
//trigger error from the wallet
|
||||
cy.intercept('POST', 'http://localhost:1789/api/v2/requests', (req) => {
|
||||
req.on('response', (res) => {
|
||||
res.send({
|
||||
jsonrpc: '2.0',
|
||||
id: '1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
cy.getByTestId('toast-content').should(
|
||||
'contain.text',
|
||||
'The connection to your Vega Wallet has been lost.'
|
||||
);
|
||||
cy.getByTestId('connect-vega-wallet').click();
|
||||
cy.getByTestId('dialog-content').should('be.visible');
|
||||
});
|
||||
|
||||
it('must see that the order was rejected by the connected wallet', () => {
|
||||
// 0003-WTXN-007
|
||||
|
||||
//trigger rejection error from the wallet
|
||||
cy.intercept('POST', 'http://localhost:1789/api/v2/requests', (req) => {
|
||||
req.alias = 'client.send_transaction';
|
||||
req.reply({
|
||||
statusCode: 400,
|
||||
body: {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: 3001,
|
||||
data: 'the user rejected the wallet connection',
|
||||
message: 'User error',
|
||||
},
|
||||
id: '0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
cy.getByTestId('toast-content').should(
|
||||
'contain.text',
|
||||
'Error occurredthe user rejected the wallet connection'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -26,6 +26,18 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('tab-deposits').should('not.be.empty');
|
||||
});
|
||||
|
||||
it.skip('should see QR code modal for WalletConnect', () => {
|
||||
// 0004-EWAL-003
|
||||
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||
cy.getByTestId('web3-connector-list').should('exist');
|
||||
cy.getByTestId('web3-connector-WalletConnect').click();
|
||||
// testing if exists rather than visible because of the long loading time
|
||||
cy.get('#w3m-modal').should('exist');
|
||||
});
|
||||
|
||||
it('able to disconnect eth wallet', () => {
|
||||
// 0004-EWAL-004
|
||||
// 0004-EWAL-005
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
mockConnectWallet,
|
||||
mockConnectWalletWithUserError,
|
||||
} from '@vegaprotocol/cypress';
|
||||
|
||||
const connectVegaBtn = 'connect-vega-wallet';
|
||||
const manageVegaBtn = 'manage-vega-wallet';
|
||||
const dialogContent = 'dialog-content';
|
||||
|
||||
describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
// Using portfolio page as it requires vega wallet connection
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
it('can connect', () => {
|
||||
// 0002-WCON-002
|
||||
// 0002-WCON-005
|
||||
// 0002-WCON-007
|
||||
// 0002-WCON-009
|
||||
|
||||
mockConnectWallet();
|
||||
cy.getByTestId(connectVegaBtn).click();
|
||||
cy.getByTestId('connectors-list')
|
||||
.find('[data-testid="connector-jsonRpc"]')
|
||||
.click();
|
||||
cy.wait('@walletReq');
|
||||
cy.getByTestId(dialogContent).should(
|
||||
'contain.text',
|
||||
'Approve the connection from your Vega wallet app.'
|
||||
);
|
||||
cy.getByTestId(dialogContent).should('not.exist');
|
||||
cy.getByTestId(manageVegaBtn).should('exist');
|
||||
});
|
||||
|
||||
it('can not connect', () => {
|
||||
// 0002-WCON-002
|
||||
// 0002-WCON-005
|
||||
// 0002-WCON-007
|
||||
// 0002-WCON-015
|
||||
|
||||
mockConnectWalletWithUserError();
|
||||
cy.getByTestId(connectVegaBtn).click();
|
||||
cy.getByTestId('connectors-list')
|
||||
.find('[data-testid="connector-jsonRpc"]')
|
||||
.click();
|
||||
cy.getByTestId('dialog-content')
|
||||
.should('contain.text', 'User error')
|
||||
.and('contain.text', 'the user rejected the wallet connection');
|
||||
});
|
||||
|
||||
it('can change selected public key and disconnect', () => {
|
||||
// 0002-WCON-022
|
||||
// 0002-WCON-023
|
||||
// 0002-WCON-025
|
||||
// 0002-WCON-026
|
||||
// 0002-WCON-021
|
||||
// 0002-WCON-027
|
||||
// 0002-WCON-030
|
||||
// 0002-WCON-029
|
||||
// 0002-WCON-008
|
||||
// 0002-WCON-035
|
||||
// 0002-WCON-014
|
||||
// 0002-WCON-010
|
||||
// 0003-WTXN-004
|
||||
|
||||
mockConnectWallet();
|
||||
const key2 = Cypress.env('VEGA_PUBLIC_KEY2');
|
||||
const truncatedKey2 = Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2');
|
||||
cy.connectVegaWallet();
|
||||
cy.getByTestId('manage-vega-wallet').click();
|
||||
cy.getByTestId('keypair-list').should('exist');
|
||||
cy.getByTestId(`key-${key2}`).should('contain.text', truncatedKey2);
|
||||
cy.getByTestId(`key-${key2}`)
|
||||
.find('[data-testid="copy-vega-public-key"]')
|
||||
.should('be.visible');
|
||||
cy.get(`[data-testid="key-${key2}"] > .mr-2`).click();
|
||||
cy.getByTestId('keypair-list')
|
||||
.find('[data-state="checked"]')
|
||||
.should('be.visible');
|
||||
cy.getByTestId('disconnect').click();
|
||||
cy.getByTestId('connect-vega-wallet').should('exist');
|
||||
cy.getByTestId('manage-vega-wallet').should('not.exist');
|
||||
cy.getByTestId('connect-vega-wallet').click();
|
||||
cy.contains('Enter a custom wallet location');
|
||||
});
|
||||
});
|
||||
@@ -25,4 +25,3 @@ NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
NX_TEAM_COMPETITION=true
|
||||
|
||||
@@ -22,9 +22,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
# NX_DISABLE_CLOSE_POSITION=false
|
||||
NX_REFERRALS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -24,7 +24,4 @@ NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
NX_REFERRALS=true
|
||||
@@ -23,11 +23,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { FeesContainer } from '../../components/fees-container';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Fees = () => {
|
||||
const t = useT();
|
||||
@@ -11,17 +10,13 @@ export const Fees = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
updateTitle(titlefy([title]));
|
||||
}, [updateTitle, title]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary feature="fees">
|
||||
<div className="container p-4 mx-auto">
|
||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||
<FeesContainer />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div className="container p-4 mx-auto">
|
||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||
<FeesContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigateToLastMarket } from '../../lib/hooks/use-navigate-to-last-market';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
import { Links } from '../../lib/links';
|
||||
|
||||
// The home pages only purpose is to redirect to the users last market,
|
||||
// the top traded if they are new, or fall back to the list of markets.
|
||||
// Thats why we just render a loader here
|
||||
export const Home = () => {
|
||||
useNavigateToLastMarket();
|
||||
const navigate = useNavigate();
|
||||
const { data } = useTopTradedMarkets();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (marketId) {
|
||||
navigate(Links.MARKET(marketId), {
|
||||
replace: true,
|
||||
});
|
||||
} else if (data) {
|
||||
const marketDataId = data[0]?.id;
|
||||
if (marketDataId) {
|
||||
navigate(Links.MARKET(marketDataId), {
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
navigate(Links.MARKETS());
|
||||
}
|
||||
}
|
||||
}, [marketId, data, navigate]);
|
||||
|
||||
return (
|
||||
<Splash>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
|
||||
const enum LiquidityTabs {
|
||||
Active = 'active',
|
||||
@@ -59,28 +58,19 @@ export const LiquidityViewContainer = ({
|
||||
name={t('My liquidity provision')}
|
||||
hidden={!pubKey}
|
||||
>
|
||||
<ErrorBoundary feature="liquidity-party">
|
||||
<LiquidityContainer
|
||||
marketId={marketId}
|
||||
filter={{ partyId: pubKey || undefined }}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<LiquidityContainer
|
||||
marketId={marketId}
|
||||
filter={{ partyId: pubKey || undefined }}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id={LiquidityTabs.Active} name={t('Active')}>
|
||||
<ErrorBoundary feature="liquidity-active">
|
||||
<LiquidityContainer
|
||||
marketId={marketId}
|
||||
filter={{ active: true }}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
|
||||
</Tab>
|
||||
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
|
||||
<ErrorBoundary feature="liquidity-inactive">
|
||||
<LiquidityContainer
|
||||
marketId={marketId}
|
||||
filter={{ active: false }}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<LiquidityContainer
|
||||
marketId={marketId}
|
||||
filter={{ active: false }}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { DocsLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
@@ -144,6 +145,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
/>
|
||||
</HeaderStat>
|
||||
)}
|
||||
<MarketProposalNotification marketId={market.id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -188,11 +190,12 @@ const useNow = () => {
|
||||
return now;
|
||||
};
|
||||
|
||||
const useEvery = (marketId: string, skip: boolean) => {
|
||||
const useEvery = (marketId: string) => {
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const { data: marketInfo } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
variables: { marketId },
|
||||
skip,
|
||||
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||
});
|
||||
let every: number | undefined = undefined;
|
||||
const sourceType =
|
||||
@@ -210,10 +213,8 @@ const useEvery = (marketId: string, skip: boolean) => {
|
||||
return every;
|
||||
};
|
||||
|
||||
const useStartTime = (marketId: string, skip: boolean) => {
|
||||
const useStartTime = (marketId: string) => {
|
||||
const { data: fundingPeriods } = useFundingPeriodsQuery({
|
||||
pollInterval: 5000,
|
||||
skip,
|
||||
variables: {
|
||||
marketId: marketId,
|
||||
pagination: { first: 1 },
|
||||
@@ -247,10 +248,8 @@ const useFormatCountdown = (
|
||||
|
||||
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
|
||||
const now = useNow();
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const skip = !marketTradingMode || isMarketInAuction(marketTradingMode);
|
||||
const startTime = useStartTime(marketId, skip);
|
||||
const every = useEvery(marketId, skip);
|
||||
const startTime = useStartTime(marketId);
|
||||
const every = useEvery(marketId);
|
||||
|
||||
return (
|
||||
<div data-testid="funding-countdown">
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { Link, Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { ExternalLink, Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { getAsset, marketDataProvider, useMarket } from '@vegaprotocol/markets';
|
||||
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||
import { TradeGrid } from './trade-grid';
|
||||
import { TradePanels } from './trade-panels';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links } from '../../lib/links';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
@@ -59,6 +61,9 @@ export const MarketPage = () => {
|
||||
const t = useT();
|
||||
const { marketId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
@@ -67,11 +72,31 @@ export const MarketPage = () => {
|
||||
const { data, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||
update({ marketId: data.id });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews(
|
||||
{ type: closed ? ViewType.Info : ViewType.Order },
|
||||
currentRouteId
|
||||
);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
|
||||
const pinnedAsset = data && getAsset(data);
|
||||
|
||||
const tradeView = useMemo(() => {
|
||||
if (pinnedAsset) {
|
||||
if (largeScreen) {
|
||||
return <TradeGrid market={data} pinnedAsset={pinnedAsset} />;
|
||||
}
|
||||
return <TradePanels market={data} pinnedAsset={pinnedAsset} />;
|
||||
}
|
||||
}, [largeScreen, data, pinnedAsset]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Splash>
|
||||
@@ -92,13 +117,12 @@ export const MarketPage = () => {
|
||||
defaults="Please choose another market from the <0>market list</0>"
|
||||
ns={ns}
|
||||
components={[
|
||||
<Link
|
||||
className="underline underline-offset-4 "
|
||||
<ExternalLink
|
||||
onClick={() => navigate(Links.MARKETS())}
|
||||
key="link"
|
||||
>
|
||||
market list
|
||||
</Link>,
|
||||
</ExternalLink>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
@@ -107,8 +131,6 @@ export const MarketPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const pinnedAsset = data && getAsset(data);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleUpdater
|
||||
@@ -116,11 +138,7 @@ export const MarketPage = () => {
|
||||
marketName={data?.tradableInstrument.instrument.name}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
{largeScreen ? (
|
||||
<TradeGrid market={data} pinnedAsset={pinnedAsset} />
|
||||
) : (
|
||||
<TradePanels market={data} pinnedAsset={pinnedAsset} />
|
||||
)}
|
||||
{tradeView}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { memo, type ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import classNames from 'classnames';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { type Market } from '@vegaprotocol/markets';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { OracleBanner, useMarket } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ResizableGrid,
|
||||
@@ -12,19 +13,29 @@ import {
|
||||
usePaneLayout,
|
||||
} from '../../components/resizable-grid';
|
||||
import { TradingViews } from './trade-views';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { MarketBanner } from '../../components/market-banner';
|
||||
|
||||
interface TradeGridProps {
|
||||
market: Market;
|
||||
pinnedAsset?: PinnedAsset | undefined;
|
||||
market: Market | null;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
const MainGrid = memo(
|
||||
({ market, pinnedAsset }: { market: Market; pinnedAsset?: PinnedAsset }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
({
|
||||
marketId,
|
||||
pinnedAsset,
|
||||
}: {
|
||||
marketId: string;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: market } = useMarket(marketId);
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
|
||||
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
|
||||
id: 'middle-1',
|
||||
@@ -49,44 +60,30 @@ const MainGrid = memo(
|
||||
id="chart"
|
||||
overflowHidden
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.chart.menu />}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
>
|
||||
<ErrorBoundary feature="chart">
|
||||
<TradingViews.chart.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
<ErrorBoundary feature="depth">
|
||||
<TradingViews.depth.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.depth.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="liquidity" name={t('Liquidity')}>
|
||||
<ErrorBoundary feature="liquidity">
|
||||
<TradingViews.liquidity.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.liquidity.component marketId={marketId} />
|
||||
</Tab>
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding-history" name={t('Funding history')}>
|
||||
<ErrorBoundary feature="funding-history">
|
||||
<TradingViews.funding.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.funding.component marketId={marketId} />
|
||||
</Tab>
|
||||
) : null}
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab
|
||||
id="funding-payments"
|
||||
name={t('Funding payments')}
|
||||
settings={<TradingViews.fundingPayments.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="funding-payments">
|
||||
<TradingViews.fundingPayments.component
|
||||
marketId={market.id}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<TradingViews.fundingPayments.component
|
||||
marketId={marketId}
|
||||
/>
|
||||
</Tab>
|
||||
) : null}
|
||||
</Tabs>
|
||||
@@ -99,18 +96,10 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-right">
|
||||
<Tab id="orderbook" name={t('Orderbook')}>
|
||||
<ErrorBoundary feature="orderbook">
|
||||
<TradingViews.orderbook.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.orderbook.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="trades"
|
||||
name={t('Trades')}
|
||||
settings={<TradingViews.trades.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="trades">
|
||||
<TradingViews.trades.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
<Tab id="trades" name={t('Trades')}>
|
||||
<TradingViews.trades.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -128,79 +117,43 @@ const MainGrid = memo(
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<TradingViews.positions.menu />}
|
||||
settings={<TradingViews.positions.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="positions">
|
||||
<TradingViews.positions.component />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.positions.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="open-orders"
|
||||
name={t('Open')}
|
||||
menu={<TradingViews.activeOrders.menu />}
|
||||
settings={<TradingViews.activeOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="activeOrders">
|
||||
<TradingViews.activeOrders.component />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.activeOrders.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="closed-orders"
|
||||
name={t('Closed')}
|
||||
settings={<TradingViews.closedOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="closedOrders">
|
||||
<TradingViews.closedOrders.component />
|
||||
</ErrorBoundary>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<TradingViews.closedOrders.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="rejected-orders"
|
||||
name={t('Rejected')}
|
||||
settings={<TradingViews.rejectedOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="rejectedOrders">
|
||||
<TradingViews.rejectedOrders.component />
|
||||
</ErrorBoundary>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<TradingViews.rejectedOrders.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="orders"
|
||||
name={t('All')}
|
||||
menu={<TradingViews.orders.menu />}
|
||||
settings={<TradingViews.orders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="orders">
|
||||
<TradingViews.orders.component />
|
||||
</ErrorBoundary>
|
||||
<TradingViews.orders.component />
|
||||
</Tab>
|
||||
{featureFlags.STOP_ORDERS ? (
|
||||
<Tab
|
||||
id="stop-orders"
|
||||
name={t('Stop orders')}
|
||||
settings={<TradingViews.stopOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="stop-orders">
|
||||
<TradingViews.stopOrders.component />
|
||||
</ErrorBoundary>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
<TradingViews.stopOrders.component />
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab
|
||||
id="fills"
|
||||
name={t('Fills')}
|
||||
settings={<TradingViews.fills.settings />}
|
||||
>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<TradingViews.fills.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="accounts"
|
||||
name={t('Collateral')}
|
||||
menu={<TradingViews.collateral.menu />}
|
||||
settings={<TradingViews.collateral.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="collateral">
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -220,10 +173,17 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div>
|
||||
<MarketBanner market={market} />
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div className="min-h-0 p-0.5">
|
||||
<MainGrid market={market} pinnedAsset={pinnedAsset} />
|
||||
<MainGrid marketId={market?.id || ''} pinnedAsset={pinnedAsset} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { type Market } from '@vegaprotocol/markets';
|
||||
// TODO: handle oracle banner
|
||||
// import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import type { TradingView } from './trade-views';
|
||||
import { TradingViews } from './trade-views';
|
||||
import { useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Popover,
|
||||
Splash,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
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';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface TradePanelsProps {
|
||||
market: Market;
|
||||
market: Market | null;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
const [view, setView] = useState<TradingView>('chart');
|
||||
const viewCfg = TradingViews[view];
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
|
||||
const renderView = () => {
|
||||
const Component = TradingViews[view].component;
|
||||
@@ -37,44 +34,39 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
|
||||
// Watch out here, we don't know what component is being rendered
|
||||
// so watch out for clashes in props
|
||||
return (
|
||||
<ErrorBoundary feature={view}>
|
||||
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
||||
</ErrorBoundary>
|
||||
);
|
||||
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
if ('menu' in viewCfg || 'settings' in viewCfg) {
|
||||
const viewCfg = TradingViews[view];
|
||||
|
||||
if ('menu' in viewCfg) {
|
||||
const Menu = viewCfg.menu;
|
||||
|
||||
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">
|
||||
{'menu' in viewCfg ? <viewCfg.menu /> : null}
|
||||
{'settings' in viewCfg ? (
|
||||
<Popover
|
||||
align="end"
|
||||
trigger={
|
||||
<span className="ml-1 flex items-center justify-center h-6 w-6">
|
||||
<VegaIcon name={VegaIconNames.COG} size={16} />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="p-4 flex justify-end">
|
||||
<viewCfg.settings />
|
||||
</div>
|
||||
</Popover>
|
||||
) : null}
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div>
|
||||
<MarketBanner market={market} />
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
<div className="h-full relative">
|
||||
<div className="h-full">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<div style={{ width, height }} className="overflow-auto">
|
||||
@@ -112,9 +104,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
key={key}
|
||||
view={key}
|
||||
isActive={isActive}
|
||||
onClick={() => {
|
||||
setView(key);
|
||||
}}
|
||||
onClick={() => setView(key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -153,7 +143,7 @@ const useViewLabel = (view: TradingView) => {
|
||||
const t = useT();
|
||||
|
||||
const labels = {
|
||||
chart: t('Chart'),
|
||||
candles: t('Candles'),
|
||||
depth: t('Depth'),
|
||||
liquidity: t('Liquidity'),
|
||||
funding: t('Funding'),
|
||||
|
||||
@@ -1,46 +1,28 @@
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
||||
import {
|
||||
TradesContainer,
|
||||
TradesSettings,
|
||||
} from '../../components/trades-container';
|
||||
import { TradesContainer } from '../../components/trades-container';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
import {
|
||||
FillsContainer,
|
||||
FillsSettings,
|
||||
} from '../../components/fills-container';
|
||||
import {
|
||||
PositionsContainer,
|
||||
PositionsSettings,
|
||||
} from '../../components/positions-container';
|
||||
import {
|
||||
AccountsContainer,
|
||||
AccountsSettings,
|
||||
} from '../../components/accounts-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import { FundingContainer } from '../../components/funding-container';
|
||||
import {
|
||||
FundingPaymentsContainer,
|
||||
FundingPaymentsSettings,
|
||||
} from '../../components/funding-payments-container';
|
||||
import {
|
||||
OrdersContainer,
|
||||
OrdersSettings,
|
||||
} from '../../components/orders-container';
|
||||
import {
|
||||
StopOrdersContainer,
|
||||
StopOrdersSettings,
|
||||
} from '../../components/stop-orders-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
import { AccountsMenu } from '../../components/accounts-menu';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { ChartContainer, ChartMenu } from '../../components/chart-container';
|
||||
|
||||
export type TradingView = keyof typeof TradingViews;
|
||||
|
||||
export const TradingViews = {
|
||||
chart: {
|
||||
component: ChartContainer,
|
||||
menu: ChartMenu,
|
||||
candles: {
|
||||
component: CandlesChartContainer,
|
||||
menu: CandlesMenu,
|
||||
},
|
||||
depth: {
|
||||
component: DepthChartContainer,
|
||||
@@ -53,46 +35,37 @@ export const TradingViews = {
|
||||
},
|
||||
fundingPayments: {
|
||||
component: FundingPaymentsContainer,
|
||||
settings: FundingPaymentsSettings,
|
||||
},
|
||||
orderbook: {
|
||||
component: OrderbookContainer,
|
||||
},
|
||||
trades: {
|
||||
component: TradesContainer,
|
||||
settings: TradesSettings,
|
||||
},
|
||||
positions: {
|
||||
component: PositionsContainer,
|
||||
menu: PositionsMenu,
|
||||
settings: PositionsSettings,
|
||||
},
|
||||
activeOrders: {
|
||||
component: () => <OrdersContainer filter={Filter.Open} />,
|
||||
menu: OpenOrdersMenu,
|
||||
settings: () => <OrdersSettings filter={Filter.Open} />,
|
||||
},
|
||||
closedOrders: {
|
||||
component: () => <OrdersContainer filter={Filter.Closed} />,
|
||||
settings: () => <OrdersSettings filter={Filter.Closed} />,
|
||||
},
|
||||
rejectedOrders: {
|
||||
component: () => <OrdersContainer filter={Filter.Rejected} />,
|
||||
settings: () => <OrdersSettings filter={Filter.Rejected} />,
|
||||
},
|
||||
orders: {
|
||||
component: OrdersContainer,
|
||||
menu: OpenOrdersMenu,
|
||||
settings: OrdersSettings,
|
||||
},
|
||||
stopOrders: {
|
||||
component: StopOrdersContainer,
|
||||
settings: StopOrdersSettings,
|
||||
},
|
||||
collateral: {
|
||||
component: AccountsContainer,
|
||||
menu: AccountsMenu,
|
||||
settings: AccountsSettings,
|
||||
},
|
||||
fills: { component: FillsContainer, settings: FillsSettings },
|
||||
fills: { component: FillsContainer },
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { act, render, screen, waitFor, within } from '@testing-library/react';
|
||||
// import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Closed } from './closed';
|
||||
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
|
||||
@@ -27,7 +26,6 @@ import {
|
||||
marketsDataQuery,
|
||||
createMarketsDataFragment,
|
||||
} from '@vegaprotocol/mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('Closed', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
@@ -170,11 +168,14 @@ describe('Closed', () => {
|
||||
Date.now = originalNow;
|
||||
});
|
||||
|
||||
const renderComponent = async (mocks: MockedResponse[]) => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('renders correctly formatted and filtered rows', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MockedProvider
|
||||
mocks={[marketsMock, marketsDataMock, oracleDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
@@ -184,10 +185,6 @@ describe('Closed', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
it('renders correct headers', async () => {
|
||||
await renderComponent([marketsMock, marketsDataMock, oracleDataMock]);
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = [
|
||||
@@ -203,10 +200,6 @@ describe('Closed', () => {
|
||||
];
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||
});
|
||||
|
||||
it('renders correctly formatted and filtered rows', async () => {
|
||||
await renderComponent([marketsMock, marketsDataMock, oracleDataMock]);
|
||||
|
||||
const assetSymbol = getAsset(market).symbol;
|
||||
|
||||
@@ -280,8 +273,21 @@ describe('Closed', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await renderComponent([mixedMarketsMock, marketsDataMock, oracleDataMock]);
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[mixedMarketsMock, marketsDataMock, oracleDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Closed />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
|
||||
// check that the number of rows in datagrid is 2
|
||||
const container = within(
|
||||
@@ -313,67 +319,8 @@ describe('Closed', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('display market actions', async () => {
|
||||
// Use market with a succcessor Id as the actions dropdown will optionally
|
||||
// show a link to the successor market
|
||||
const marketsWithSuccessorAndParent = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
successorMarketID: 'successor',
|
||||
parentMarketID: 'parent',
|
||||
}),
|
||||
},
|
||||
];
|
||||
const mockWithSuccessorAndParent: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: marketsWithSuccessorAndParent,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await renderComponent([
|
||||
mockWithSuccessorAndParent,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
]);
|
||||
|
||||
const actionCell = screen
|
||||
.getAllByRole('gridcell')
|
||||
.find((el) => el.getAttribute('col-id') === 'market-actions');
|
||||
|
||||
await userEvent.click(
|
||||
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
|
||||
);
|
||||
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'Copy Market ID' })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'View on Explorer' })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'View settlement asset details' })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'View parent market' })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'View successor market' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('successor market should be visible', async () => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('successor marked should be visible', async () => {
|
||||
const marketsWithSuccessorID = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
@@ -398,11 +345,21 @@ describe('Closed', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await renderComponent([
|
||||
mockWithSuccessors,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
]);
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[mockWithSuccessors, marketsDataMock, oracleDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Closed />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
|
||||
const container = within(
|
||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||
|
||||
@@ -42,7 +42,7 @@ export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const useMarketsStore = create<DataGridSlice>()(
|
||||
const useMarketsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_market_list_store',
|
||||
})
|
||||
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { MarketsSettings } from './markets-settings';
|
||||
|
||||
export const MarketsPage = () => {
|
||||
const t = useT();
|
||||
@@ -35,19 +33,12 @@ export const MarketsPage = () => {
|
||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||
<div className="h-full my-1 border rounded-sm border-default">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab
|
||||
id="open-markets"
|
||||
name={t('Open markets')}
|
||||
settings={<MarketsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="markets-open">
|
||||
<OpenMarkets />
|
||||
</ErrorBoundary>
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<OpenMarkets />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
name={t('Proposed markets')}
|
||||
settings={<MarketsSettings />}
|
||||
menu={
|
||||
<TradingAnchorButton
|
||||
size="extra-small"
|
||||
@@ -58,18 +49,10 @@ export const MarketsPage = () => {
|
||||
</TradingAnchorButton>
|
||||
}
|
||||
>
|
||||
<ErrorBoundary feature="markets-proposed">
|
||||
<Proposed />
|
||||
</ErrorBoundary>
|
||||
<Proposed />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="closed-markets"
|
||||
name={t('Closed markets')}
|
||||
settings={<MarketsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="markets-closed">
|
||||
<Closed />
|
||||
</ErrorBoundary>
|
||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||
<Closed />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { GridSettings } from '../../components/grid-settings/grid-settings';
|
||||
import { useMarketsStore } from './market-list-table';
|
||||
|
||||
export const MarketsSettings = () => (
|
||||
<GridSettings
|
||||
updateGridStore={useMarketsStore((store) => store.updateGridStore)}
|
||||
/>
|
||||
);
|
||||
@@ -4,26 +4,9 @@ import {
|
||||
SidebarButton,
|
||||
SidebarDivider,
|
||||
ViewType,
|
||||
useSidebar,
|
||||
} from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const ViewInitializer = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews({ type: ViewType.Order }, currentRouteId);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const MarketsSidebar = () => {
|
||||
const t = useT();
|
||||
@@ -54,7 +37,6 @@ export const MarketsSidebar = () => {
|
||||
path=":marketId"
|
||||
element={
|
||||
<>
|
||||
<ViewInitializer />
|
||||
<SidebarDivider />
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import { act, render, screen, within } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { OpenMarkets } from './open-markets';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type {
|
||||
MarketsDataQuery,
|
||||
MarketsQuery,
|
||||
MarketCandlesQuery,
|
||||
MarketFieldsFragment,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
MarketsDataDocument,
|
||||
MarketsDocument,
|
||||
MarketsCandlesDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
marketsQuery,
|
||||
marketsDataQuery,
|
||||
marketsCandlesQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('Open', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
const mockNowTimestamp = 1672531200000;
|
||||
const pubKey = 'pubKey';
|
||||
|
||||
const marketsQueryData = marketsQuery();
|
||||
const marketsMock: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: marketsQueryData,
|
||||
},
|
||||
};
|
||||
|
||||
const marketsCandlesQueryData = marketsCandlesQuery();
|
||||
const marketsCandlesMock: MockedResponse<MarketCandlesQuery> = {
|
||||
request: {
|
||||
query: MarketsCandlesDocument,
|
||||
variables: {
|
||||
interval: Interval.INTERVAL_I1H,
|
||||
since: '2022-12-31T00:00:00.000Z',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: marketsCandlesQueryData,
|
||||
},
|
||||
};
|
||||
|
||||
const marketsDataQueryData = marketsDataQuery();
|
||||
|
||||
const marketsDataMock: MockedResponse<MarketsDataQuery> = {
|
||||
request: {
|
||||
query: MarketsDataDocument,
|
||||
},
|
||||
result: {
|
||||
data: marketsDataQueryData,
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
originalNow = Date.now;
|
||||
Date.now = jest.fn().mockReturnValue(mockNowTimestamp);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Date.now = originalNow;
|
||||
});
|
||||
|
||||
const renderComponent = async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[marketsMock, marketsCandlesMock, marketsDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<OpenMarkets />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
it('renders correct headers', async () => {
|
||||
await renderComponent();
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Settlement asset',
|
||||
'Trading mode',
|
||||
'Status',
|
||||
'Mark price',
|
||||
'24h volume',
|
||||
'Open Interest',
|
||||
'Spread',
|
||||
'', // Action row
|
||||
];
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||
});
|
||||
|
||||
it('sort columns', async () => {
|
||||
await renderComponent();
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const marketHeader = headers.find(
|
||||
(h) => h.getAttribute('col-id') === 'tradableInstrument.instrument.code'
|
||||
);
|
||||
if (!marketHeader) {
|
||||
throw new Error('No market header found');
|
||||
}
|
||||
expect(marketHeader).toHaveAttribute('aria-sort', 'none');
|
||||
await userEvent.click(within(marketHeader).getByText(/market/i));
|
||||
// 6001-MARK-064
|
||||
expect(marketHeader).toHaveAttribute('aria-sort', 'ascending');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests, jest/expect-expect
|
||||
it('renders row', async () => {
|
||||
await renderComponent();
|
||||
|
||||
const container = within(
|
||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||
);
|
||||
|
||||
const markets = marketsQueryData.marketsConnection?.edges.map(
|
||||
(e) => e.node
|
||||
) as MarketFieldsFragment[];
|
||||
|
||||
const rows = container.getAllByRole('row');
|
||||
expect(rows).toHaveLength(markets.length);
|
||||
});
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export const OpenMarkets = () => {
|
||||
if (!data) return;
|
||||
|
||||
// prevent navigating to the market page if any of the below cells are clicked
|
||||
// event.preventDefault or event.stopPropagation do not seem to apply for ag-grid
|
||||
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
|
||||
const colId = column.getColId();
|
||||
|
||||
if (
|
||||
|
||||
@@ -5,29 +5,14 @@ import { titlefy } from '@vegaprotocol/utils';
|
||||
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import {
|
||||
AccountsContainer,
|
||||
AccountsSettings,
|
||||
} from '../../components/accounts-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { DepositsContainer } from '../../components/deposits-container';
|
||||
import {
|
||||
FillsContainer,
|
||||
FillsSettings,
|
||||
} from '../../components/fills-container';
|
||||
import {
|
||||
FundingPaymentsContainer,
|
||||
FundingPaymentsSettings,
|
||||
} from '../../components/funding-payments-container';
|
||||
import {
|
||||
PositionsContainer,
|
||||
PositionsSettings,
|
||||
} from '../../components/positions-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||
import {
|
||||
OrdersContainer,
|
||||
OrdersSettings,
|
||||
} from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import {
|
||||
ResizableGrid,
|
||||
@@ -40,7 +25,6 @@ import { DepositsMenu } from '../../components/deposits-menu';
|
||||
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
@@ -54,21 +38,11 @@ const WithdrawalsIndicator = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarViewInitializer = () => {
|
||||
export const Portfolio = () => {
|
||||
const t = useT();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { getView, setViews } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
// Make transfer sidebar open by default
|
||||
useEffect(() => {
|
||||
if (view === undefined) {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
}
|
||||
}, [view, setViews, currentRouteId]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const t = useT();
|
||||
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
@@ -78,11 +52,17 @@ export const Portfolio = () => {
|
||||
updateTitle(titlefy([t('Portfolio')]));
|
||||
}, [updateTitle, t]);
|
||||
|
||||
// Make transfer sidebar open by default
|
||||
useEffect(() => {
|
||||
if (view === undefined) {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
}
|
||||
}, [view, setViews, currentRouteId]);
|
||||
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<SidebarViewInitializer />
|
||||
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||
<ResizableGridPanel minSize={75}>
|
||||
<PortfolioGridChild>
|
||||
@@ -91,35 +71,20 @@ export const Portfolio = () => {
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<PositionsMenu />}
|
||||
settings={<PositionsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-positions">
|
||||
<PositionsContainer allKeys />
|
||||
</ErrorBoundary>
|
||||
<PositionsContainer allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')} settings={<OrdersSettings />}>
|
||||
<ErrorBoundary feature="portfolio-orders">
|
||||
<OrdersContainer />
|
||||
</ErrorBoundary>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<OrdersContainer />
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')} settings={<FillsSettings />}>
|
||||
<ErrorBoundary feature="portfolio-fills">
|
||||
<FillsContainer />
|
||||
</ErrorBoundary>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<FillsContainer />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="funding-payments"
|
||||
name={t('Funding payments')}
|
||||
settings={<FundingPaymentsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-funding-payments">
|
||||
<FundingPaymentsContainer />
|
||||
</ErrorBoundary>
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<FundingPaymentsContainer />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<ErrorBoundary feature="portfolio-ledger">
|
||||
<LedgerContainer />
|
||||
</ErrorBoundary>
|
||||
<LedgerContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</PortfolioGridChild>
|
||||
@@ -134,17 +99,12 @@ export const Portfolio = () => {
|
||||
<Tab
|
||||
id="collateral"
|
||||
name={t('Collateral')}
|
||||
settings={<AccountsSettings />}
|
||||
menu={<AccountsMenu />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-accounts">
|
||||
<AccountsContainer />
|
||||
</ErrorBoundary>
|
||||
<AccountsContainer />
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
|
||||
<ErrorBoundary feature="portfolio-deposit">
|
||||
<DepositsContainer />
|
||||
</ErrorBoundary>
|
||||
<DepositsContainer />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
@@ -152,9 +112,7 @@ export const Portfolio = () => {
|
||||
indicator={<WithdrawalsIndicator />}
|
||||
menu={<WithdrawalsMenu />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-deposit">
|
||||
<WithdrawalsContainer />
|
||||
</ErrorBoundary>
|
||||
<WithdrawalsContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</PortfolioGridChild>
|
||||
|
||||
@@ -10,43 +10,18 @@ import { useForm } from 'react-hook-form';
|
||||
import classNames from 'classnames';
|
||||
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { RainbowButton } from './buttons';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
||||
import { Statistics, useStats } from './referral-statistics';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { ns, useT } from '../../lib/use-t';
|
||||
import { useFundsAvailable } from './hooks/use-funds-available';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
const RELOAD_DELAY = 3000;
|
||||
|
||||
const SPAM_PROTECTION_ERR = 'SPAM_PROTECTION_ERR';
|
||||
const SpamProtectionErr = ({
|
||||
requiredFunds,
|
||||
}: {
|
||||
requiredFunds?: string | number | bigint;
|
||||
}) => {
|
||||
if (!requiredFunds) return null;
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
return (
|
||||
<Trans
|
||||
defaults="To protect the network from spam, you must have at least {{requiredFunds}} <0>qUSD</0> of any asset on the network to proceed."
|
||||
values={{
|
||||
requiredFunds,
|
||||
}}
|
||||
components={[<QUSDTooltip key="qusd" />]}
|
||||
ns={ns}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||
const number = +`0x${value}`;
|
||||
if (!value || value.length !== 64) {
|
||||
@@ -57,23 +32,7 @@ const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const ApplyCodeFormContainer = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const isInReferralSet = useIsInReferralSet(pubKey);
|
||||
|
||||
// Navigate to the index page when already in the referral set.
|
||||
if (isInReferralSet) {
|
||||
return <Navigate to={Routes.REFERRALS} />;
|
||||
}
|
||||
|
||||
return <ApplyCodeForm onSuccess={onSuccess} />;
|
||||
};
|
||||
|
||||
export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
export const ApplyCodeForm = () => {
|
||||
const t = useT();
|
||||
const program = useReferralProgram();
|
||||
const navigate = useNavigate();
|
||||
@@ -82,15 +41,10 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
);
|
||||
|
||||
const [status, setStatus] = useState<
|
||||
'requested' | 'no-funds' | 'successful' | null
|
||||
'requested' | 'failed' | 'successful' | null
|
||||
>(null);
|
||||
const txHash = useRef<string | null>(null);
|
||||
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
|
||||
const { isEligible, requiredFunds } = useFundsAvailable();
|
||||
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((s) => s.setViews);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -101,54 +55,19 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
} = useForm();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const { data: referee } = useReferral({ pubKey, role: 'referee' });
|
||||
const { data: referrer } = useReferral({ pubKey, role: 'referrer' });
|
||||
|
||||
const codeField = watch('code');
|
||||
const { data: previewData, loading: previewLoading } = useReferral({
|
||||
code: validateCode(codeField, t) ? codeField : undefined,
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates if a connected party can apply a code (min funds span protection)
|
||||
*/
|
||||
const validateFundsAvailable = useCallback(() => {
|
||||
if (requiredFunds && !isEligible) {
|
||||
const err = SPAM_PROTECTION_ERR;
|
||||
return err;
|
||||
}
|
||||
return true;
|
||||
}, [isEligible, requiredFunds]);
|
||||
|
||||
/**
|
||||
* Validates the set a user tries to apply to.
|
||||
*/
|
||||
const validateSet = useCallback(() => {
|
||||
if (
|
||||
codeField &&
|
||||
!previewLoading &&
|
||||
previewData &&
|
||||
!previewData.isEligible
|
||||
) {
|
||||
return t('The code is no longer valid.');
|
||||
}
|
||||
if (codeField && !previewLoading && !previewData) {
|
||||
return t('The code is invalid');
|
||||
}
|
||||
return true;
|
||||
}, [codeField, previewData, previewLoading, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const code = params.get('code');
|
||||
if (code) setValue('code', code);
|
||||
}, [params, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const err = validateFundsAvailable();
|
||||
if (err !== true) {
|
||||
setStatus('no-funds');
|
||||
} else {
|
||||
setStatus(null);
|
||||
}
|
||||
}, [isEligible, validateFundsAvailable]);
|
||||
|
||||
const onSubmit = ({ code }: FieldValues) => {
|
||||
if (isReadOnly || !pubKey || !code || code.length === 0) {
|
||||
return;
|
||||
@@ -220,11 +139,15 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
useEffect(() => {
|
||||
if (status === 'successful') {
|
||||
setTimeout(() => {
|
||||
if (onSuccess) onSuccess();
|
||||
navigate(Routes.REFERRALS);
|
||||
}, RELOAD_DELAY);
|
||||
}
|
||||
}, [navigate, onSuccess, status]);
|
||||
}, [navigate, status]);
|
||||
|
||||
// go to main page if the current pubkey is already a referrer or referee
|
||||
if (referee || referrer) {
|
||||
return <Navigate to={Routes.REFERRALS} />;
|
||||
}
|
||||
|
||||
// show "code applied" message when successfully applied
|
||||
if (status === 'successful') {
|
||||
@@ -261,18 +184,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (status === 'no-funds') {
|
||||
return {
|
||||
disabled: false,
|
||||
children: t('Deposit funds'),
|
||||
type: 'button' as ButtonHTMLAttributes<HTMLButtonElement>['type'],
|
||||
onClick: ((event) => {
|
||||
event.preventDefault();
|
||||
setViews({ type: ViewType.Deposit }, currentRouteId);
|
||||
}) as MouseEventHandler,
|
||||
};
|
||||
}
|
||||
|
||||
if (status === 'requested') {
|
||||
return {
|
||||
disabled: true,
|
||||
@@ -294,17 +205,12 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
data-testid="referral-apply-code-form"
|
||||
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto md:w-2/3 max-w-md rounded-lg p-8"
|
||||
>
|
||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto w-2/3 max-w-md rounded-lg p-8">
|
||||
<h3 className="calt mb-4 text-center text-2xl">
|
||||
{t('Apply a referral code')}
|
||||
</h3>
|
||||
<p className="mb-4 text-center text-base">
|
||||
{t(
|
||||
'Apply a referral code to access the discount benefits of the current program.'
|
||||
)}
|
||||
{t('Enter a referral code to get trading discounts.')}
|
||||
</p>
|
||||
<form
|
||||
className={classNames('flex w-full flex-col gap-4', {
|
||||
@@ -318,13 +224,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
hasError={Boolean(errors.code)}
|
||||
{...register('code', {
|
||||
required: t('You have to provide a code to apply it.'),
|
||||
validate: (value) => {
|
||||
const codeErr = validateCode(value, t);
|
||||
if (codeErr !== true) return codeErr;
|
||||
const fundsErr = validateFundsAvailable();
|
||||
if (fundsErr !== true) return fundsErr;
|
||||
return validateSet();
|
||||
},
|
||||
validate: (value) => validateCode(value, t),
|
||||
})}
|
||||
placeholder="Enter a code"
|
||||
className="bg-vega-clight-900 dark:bg-vega-cdark-700 mb-2"
|
||||
@@ -332,39 +232,21 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
</label>
|
||||
<RainbowButton variant="border" {...getButtonProps()} />
|
||||
</form>
|
||||
{status === 'no-funds' ? (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
<span>
|
||||
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
|
||||
</span>
|
||||
{errors.code && (
|
||||
<InputError className="overflow-auto break-words">
|
||||
{errors.code.message?.toString()}
|
||||
</InputError>
|
||||
) : (
|
||||
errors.code && (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
{errors.code.message === SPAM_PROTECTION_ERR ? (
|
||||
<span>
|
||||
<SpamProtectionErr
|
||||
requiredFunds={requiredFunds?.toString()}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
errors.code.message?.toString()
|
||||
)}
|
||||
</InputError>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
|
||||
{previewLoading && !previewData ? (
|
||||
<div className="mt-10">
|
||||
<Loader />
|
||||
</div>
|
||||
) : null}
|
||||
{/* TODO: Re-check plural forms once i18n is updated */}
|
||||
{previewData && previewData.isEligible ? (
|
||||
{previewData ? (
|
||||
<div className="mt-10">
|
||||
<h2 className="mb-5 text-2xl">
|
||||
{t(
|
||||
'youAreJoiningTheGroup',
|
||||
'You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.',
|
||||
{ count: nextBenefitTierEpochsValue }
|
||||
)}
|
||||
|
||||
@@ -3,11 +3,9 @@ export const GRADIENT =
|
||||
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
|
||||
|
||||
export const SKY_BACKGROUND =
|
||||
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[37%_0px] bg-[length:1440px] bg-no-repeat bg-local';
|
||||
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
|
||||
|
||||
// TODO: Update the links to use the correct referral related pages
|
||||
export const REFERRAL_DOCS_LINK =
|
||||
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
|
||||
export const ABOUT_REFERRAL_DOCS_LINK =
|
||||
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
|
||||
export const REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
export const ABOUT_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
export const DISCLAIMER_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||
|
||||
@@ -19,22 +19,14 @@ import {
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
|
||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||
import { ABOUT_REFERRAL_DOCS_LINK } from './constants';
|
||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
||||
import {
|
||||
ABOUT_REFERRAL_DOCS_LINK,
|
||||
DISCLAIMER_REFERRAL_DOCS_LINK,
|
||||
} from './constants';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
|
||||
export const CreateCodeContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const isInReferralSet = useIsInReferralSet(pubKey);
|
||||
|
||||
// Navigate to the index page when already in the referral set.
|
||||
if (isInReferralSet) {
|
||||
return <Navigate to={Routes.REFERRALS} />;
|
||||
}
|
||||
|
||||
return <CreateCodeForm />;
|
||||
};
|
||||
|
||||
@@ -47,16 +39,13 @@ export const CreateCodeForm = () => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="referral-create-code-form"
|
||||
className="md:w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
|
||||
>
|
||||
<div className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg">
|
||||
<h3 className="mb-4 text-2xl text-center calt">
|
||||
{t('Create a referral code')}
|
||||
</h3>
|
||||
<p className="mb-4 text-center text-base">
|
||||
{t(
|
||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
||||
'Generate a referral code to share with your friends and start earning commission.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
@@ -106,7 +95,10 @@ const CreateCodeDialog = ({
|
||||
const { stakeAvailable: currentStakeAvailable, requiredStake } =
|
||||
useStakeAvailable();
|
||||
|
||||
const { details: programDetails } = useReferralProgram();
|
||||
const { data: referralSets } = useReferral({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
});
|
||||
|
||||
const onSubmit = () => {
|
||||
if (isReadOnly || !pubKey) {
|
||||
@@ -206,7 +198,7 @@ const CreateCodeDialog = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!programDetails) {
|
||||
if (!referralSets) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||
@@ -242,9 +234,7 @@ const CreateCodeDialog = ({
|
||||
intent={Intent.Primary}
|
||||
onClick={() => onSubmit()}
|
||||
{...getButtonProps()}
|
||||
>
|
||||
{t('Yes')}
|
||||
</TradingButton>
|
||||
></TradingButton>
|
||||
{status === 'idle' && (
|
||||
<TradingButton
|
||||
fill={true}
|
||||
@@ -262,6 +252,9 @@ const CreateCodeDialog = ({
|
||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||
{t('About the referral program')}
|
||||
</ExternalLink>
|
||||
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||
{t('Disclaimer')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -272,7 +265,7 @@ const CreateCodeDialog = ({
|
||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||
<p>
|
||||
{t(
|
||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
||||
'Generate a referral code to share with your friends and start earning commission.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
@@ -303,6 +296,9 @@ const CreateCodeDialog = ({
|
||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||
{t('About the referral program')}
|
||||
</ExternalLink>
|
||||
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||
{t('Disclaimer')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ export const NotFound = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<LayoutWithSky className="pt-32">
|
||||
<div className="pt-32">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
|
||||
@@ -75,6 +75,6 @@ export const NotFound = () => {
|
||||
{t('Go back and try again')}
|
||||
</RainbowButton>
|
||||
</p>
|
||||
</LayoutWithSky>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
query FundsAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
symbol
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
networkParameter(key: "spam.protection.applyReferral.min.funds") {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ query ReferralSetStats($code: ID!, $epoch: Int) {
|
||||
rewardsMultiplier
|
||||
rewardsFactorMultiplier
|
||||
referrerTakerVolume
|
||||
wasEligible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
query StakeAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
}
|
||||
networkParameter(key: "referralProgram.minStakedVegaTokens") {
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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 FundsAvailableQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FundsAvailableQuery = { __typename?: 'Query', party?: { __typename?: 'Party', accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, asset: { __typename?: 'Asset', decimals: number, symbol: string, id: string } } } | null> | null } | null } | null, networkParameter?: { __typename?: 'NetworkParameter', key: string, value: string } | null };
|
||||
|
||||
|
||||
export const FundsAvailableDocument = gql`
|
||||
query FundsAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
symbol
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
networkParameter(key: "spam.protection.applyReferral.min.funds") {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useFundsAvailableQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFundsAvailableQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFundsAvailableQuery` 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 } = useFundsAvailableQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFundsAvailableQuery(baseOptions: Apollo.QueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
|
||||
}
|
||||
export function useFundsAvailableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FundsAvailableQuery, FundsAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FundsAvailableQuery, FundsAvailableQueryVariables>(FundsAvailableDocument, options);
|
||||
}
|
||||
export type FundsAvailableQueryHookResult = ReturnType<typeof useFundsAvailableQuery>;
|
||||
export type FundsAvailableLazyQueryHookResult = ReturnType<typeof useFundsAvailableLazyQuery>;
|
||||
export type FundsAvailableQueryResult = Apollo.QueryResult<FundsAvailableQuery, FundsAvailableQueryVariables>;
|
||||
+1
-2
@@ -9,7 +9,7 @@ export type ReferralSetStatsQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string, wasEligible: boolean } } | null> } };
|
||||
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string } } | null> } };
|
||||
|
||||
|
||||
export const ReferralSetStatsDocument = gql`
|
||||
@@ -26,7 +26,6 @@ export const ReferralSetStatsDocument = gql`
|
||||
rewardsMultiplier
|
||||
rewardsFactorMultiplier
|
||||
referrerTakerVolume
|
||||
wasEligible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +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 StakeAvailableQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type StakeAvailableQuery = { __typename?: 'Query', party?: { __typename?: 'Party', stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } | null, networkParameter?: { __typename?: 'NetworkParameter', value: string } | null };
|
||||
|
||||
|
||||
export const StakeAvailableDocument = gql`
|
||||
query StakeAvailable($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
}
|
||||
networkParameter(key: "referralProgram.minStakedVegaTokens") {
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useStakeAvailableQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useStakeAvailableQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useStakeAvailableQuery` 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 } = useStakeAvailableQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useStakeAvailableQuery(baseOptions: Apollo.QueryHookOptions<StakeAvailableQuery, StakeAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<StakeAvailableQuery, StakeAvailableQueryVariables>(StakeAvailableDocument, options);
|
||||
}
|
||||
export function useStakeAvailableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<StakeAvailableQuery, StakeAvailableQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<StakeAvailableQuery, StakeAvailableQueryVariables>(StakeAvailableDocument, options);
|
||||
}
|
||||
export type StakeAvailableQueryHookResult = ReturnType<typeof useStakeAvailableQuery>;
|
||||
export type StakeAvailableLazyQueryHookResult = ReturnType<typeof useStakeAvailableLazyQuery>;
|
||||
export type StakeAvailableQueryResult = Apollo.QueryResult<StakeAvailableQuery, StakeAvailableQueryVariables>;
|
||||
@@ -1,48 +0,0 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
|
||||
import compact from 'lodash/compact';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
/**
|
||||
* Gets the funds for given public key and required min for
|
||||
* the referral program.
|
||||
*
|
||||
* (Uses currently connected public key if left empty)
|
||||
*/
|
||||
export const useFundsAvailable = (pubKey?: string) => {
|
||||
const { pubKey: currentPubKey } = useVegaWallet();
|
||||
const partyId = pubKey || currentPubKey;
|
||||
const { data, stopPolling } = useFundsAvailableQuery({
|
||||
variables: { partyId: partyId || '' },
|
||||
skip: !partyId,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
pollInterval: 5000,
|
||||
});
|
||||
|
||||
const fundsAvailable = data
|
||||
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
|
||||
: undefined;
|
||||
const requiredFunds = data
|
||||
? BigNumber(data.networkParameter?.value || '0')
|
||||
: undefined;
|
||||
|
||||
const sumOfFunds =
|
||||
fundsAvailable
|
||||
?.filter((fa) => fa.balance)
|
||||
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
|
||||
BigNumber(0);
|
||||
|
||||
if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
return {
|
||||
fundsAvailable,
|
||||
requiredFunds,
|
||||
isEligible:
|
||||
fundsAvailable != null &&
|
||||
requiredFunds != null &&
|
||||
sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
|
||||
};
|
||||
};
|
||||
@@ -1,8 +1,14 @@
|
||||
import { getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { addDays } from 'date-fns';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import omit from 'lodash/omit';
|
||||
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
const STAKING_TIERS_MAPPING: Record<number, string> = {
|
||||
1: 'Tradestarter',
|
||||
2: 'Mid level degen',
|
||||
3: 'Reward hoarder',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const MOCK = {
|
||||
@@ -10,76 +16,46 @@ const MOCK = {
|
||||
currentReferralProgram: {
|
||||
id: 'abc',
|
||||
version: 1,
|
||||
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
|
||||
windowLength: 10,
|
||||
benefitTiers: [
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '100000',
|
||||
referralDiscountFactor: '0.1',
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '30000',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '20000',
|
||||
referralDiscountFactor: '0.05',
|
||||
referralRewardFactor: '0.05',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '1000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.075',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '5000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.1',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '25000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.125',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '75000000',
|
||||
referralDiscountFactor: '0.1',
|
||||
referralRewardFactor: '0.15',
|
||||
},
|
||||
{
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '150000000',
|
||||
referralDiscountFactor: '0.07',
|
||||
referralRewardFactor: '0.175',
|
||||
minimumEpochs: 5,
|
||||
minimumRunningNotionalTakerVolume: '10000',
|
||||
referralDiscountFactor: '0.001',
|
||||
referralRewardFactor: '0.001',
|
||||
},
|
||||
],
|
||||
stakingTiers: [
|
||||
{
|
||||
minimumStakedTokens: '100000000000000000000',
|
||||
referralRewardMultiplier: '1.025',
|
||||
minimumStakedTokens: '10000',
|
||||
referralRewardMultiplier: '1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '1000000000000000000000',
|
||||
referralRewardMultiplier: '1.05',
|
||||
minimumStakedTokens: '20000',
|
||||
referralRewardMultiplier: '2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '5000000000000000000000',
|
||||
referralRewardMultiplier: '1.1',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '50000000000000000000000',
|
||||
referralRewardMultiplier: '1.2',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '250000000000000000000000',
|
||||
referralRewardMultiplier: '1.25',
|
||||
},
|
||||
{
|
||||
minimumStakedTokens: '500000000000000000000000',
|
||||
referralRewardMultiplier: '1.3',
|
||||
minimumStakedTokens: '30000',
|
||||
referralRewardMultiplier: '3',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '2024-12-31T01:00:00Z',
|
||||
windowLength: 30,
|
||||
},
|
||||
loading: false,
|
||||
error: undefined,
|
||||
},
|
||||
loading: false,
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
export const useReferralProgram = () => {
|
||||
@@ -99,26 +75,30 @@ export const useReferralProgram = () => {
|
||||
|
||||
const benefitTiers = sortBy(data.currentReferralProgram.benefitTiers, (t) =>
|
||||
Number(t.referralRewardFactor)
|
||||
).map((t, i) => {
|
||||
return {
|
||||
tier: i + 1, // sorted in asc order, hence first is the lowest tier
|
||||
rewardFactor: Number(t.referralRewardFactor),
|
||||
commission: BigNumber(t.referralRewardFactor).times(100).toFixed(2) + '%',
|
||||
discountFactor: Number(t.referralDiscountFactor),
|
||||
discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%',
|
||||
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
||||
volume: getNumberFormat(0).format(
|
||||
Number(t.minimumRunningNotionalTakerVolume)
|
||||
),
|
||||
epochs: Number(t.minimumEpochs),
|
||||
};
|
||||
});
|
||||
)
|
||||
.reverse()
|
||||
.map((t, i) => {
|
||||
return {
|
||||
tier: i + 1,
|
||||
rewardFactor: Number(t.referralRewardFactor),
|
||||
commission: Number(t.referralRewardFactor) * 100 + '%',
|
||||
discountFactor: Number(t.referralDiscountFactor),
|
||||
discount: Number(t.referralDiscountFactor) * 100 + '%',
|
||||
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
||||
volume: getNumberFormat(0).format(
|
||||
Number(t.minimumRunningNotionalTakerVolume)
|
||||
),
|
||||
epochs: Number(t.minimumEpochs),
|
||||
};
|
||||
});
|
||||
|
||||
const stakingTiers = sortBy(data.currentReferralProgram.stakingTiers, (t) =>
|
||||
parseFloat(t.referralRewardMultiplier)
|
||||
const stakingTiers = sortBy(
|
||||
data.currentReferralProgram.stakingTiers,
|
||||
(t) => t.referralRewardMultiplier
|
||||
).map((t, i) => {
|
||||
return {
|
||||
tier: i + 1,
|
||||
label: STAKING_TIERS_MAPPING[i + 1],
|
||||
...t,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import {
|
||||
Intent,
|
||||
type Toast,
|
||||
useToasts,
|
||||
ToastHeading,
|
||||
Button,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useReferral } from './use-referral';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useEffect } from 'react';
|
||||
import { useT } from '../../../lib/use-t';
|
||||
import { matchPath, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Routes } from '../../../lib/links';
|
||||
import { useCurrentEpochInfoQuery } from './__generated__/Epoch';
|
||||
|
||||
const REFETCH_INTERVAL = 60 * 60 * 1000; // 1h
|
||||
const NON_ELIGIBLE_REFERRAL_SET_TOAST_ID = 'non-eligible-referral-set';
|
||||
|
||||
const useNonEligibleReferralSet = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, loading, refetch } = useReferral({ pubKey, role: 'referee' });
|
||||
const {
|
||||
data: epochData,
|
||||
loading: epochLoading,
|
||||
refetch: epochRefetch,
|
||||
} = useCurrentEpochInfoQuery();
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
refetch();
|
||||
epochRefetch();
|
||||
}, REFETCH_INTERVAL);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [epochRefetch, refetch]);
|
||||
|
||||
return { data, epoch: epochData?.epoch.id, loading: loading || epochLoading };
|
||||
};
|
||||
|
||||
export const useReferralToasts = () => {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const t = useT();
|
||||
const [setToast, hasToast, updateToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.update,
|
||||
]);
|
||||
|
||||
const { data, epoch, loading } = useNonEligibleReferralSet();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
data &&
|
||||
epoch &&
|
||||
!loading &&
|
||||
!data.isEligible &&
|
||||
!hasToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch)
|
||||
) {
|
||||
const nonEligibleReferralToast: Toast = {
|
||||
id: NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch,
|
||||
intent: Intent.Warning,
|
||||
content: (
|
||||
<>
|
||||
<ToastHeading>{t('Referral code no longer valid')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Your referral code is no longer valid as the referrer no longer meets the minimum requirements.'
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
<Button
|
||||
data-testid="toast-apply-code"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
const matched = matchPath(Routes.REFERRALS, pathname);
|
||||
if (!matched) navigate(Routes.REFERRALS_APPLY_CODE);
|
||||
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
|
||||
hidden: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('Apply a new code')}
|
||||
</Button>
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
onClose: () =>
|
||||
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
|
||||
hidden: true,
|
||||
}),
|
||||
};
|
||||
setToast(nonEligibleReferralToast);
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
epoch,
|
||||
hasToast,
|
||||
loading,
|
||||
navigate,
|
||||
pathname,
|
||||
setToast,
|
||||
t,
|
||||
updateToast,
|
||||
]);
|
||||
};
|
||||
@@ -2,13 +2,8 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { useRefereesQuery } from './__generated__/Referees';
|
||||
import compact from 'lodash/compact';
|
||||
import pick from 'lodash/pick';
|
||||
import type {
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables,
|
||||
} from './__generated__/ReferralSets';
|
||||
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
|
||||
import { useReferralSetsQuery } from './__generated__/ReferralSets';
|
||||
import { useStakeAvailable } from './use-stake-available';
|
||||
|
||||
export const DEFAULT_AGGREGATION_DAYS = 30;
|
||||
|
||||
@@ -67,8 +62,6 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
? referralData.referralSets.edges[0]?.node
|
||||
: undefined;
|
||||
|
||||
const { isEligible } = useStakeAvailable(referralSet?.referrer);
|
||||
|
||||
const {
|
||||
data: refereesData,
|
||||
loading: refereesLoading,
|
||||
@@ -110,7 +103,6 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
referee: referee,
|
||||
referrerId: referralSet.referrer,
|
||||
createdAt: referralSet.createdAt,
|
||||
isEligible,
|
||||
referees,
|
||||
}
|
||||
: undefined;
|
||||
@@ -122,96 +114,3 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
type Referee = NonNullable<
|
||||
NonNullable<ReturnType<typeof useReferral>['data']>['referee']
|
||||
>;
|
||||
|
||||
type RefereeProperties = (keyof Referee)[];
|
||||
|
||||
const findReferee = (referee: Referee, referees: Referee[]) =>
|
||||
referees.find((r) => r.refereeId === referee?.refereeId) || referee;
|
||||
|
||||
const updateReferee = (
|
||||
referee: Referee,
|
||||
referees: Referee[],
|
||||
properties: RefereeProperties
|
||||
) => ({
|
||||
...referee,
|
||||
...pick(findReferee(referee, referees), properties),
|
||||
});
|
||||
|
||||
export const useUpdateReferees = (
|
||||
referral: ReturnType<typeof useReferral>,
|
||||
aggregationEpochs: number,
|
||||
properties: RefereeProperties,
|
||||
skip?: boolean
|
||||
): ReturnType<typeof useReferral> => {
|
||||
const { data, loading, error, refetch } = useRefereesQuery({
|
||||
variables: {
|
||||
code: referral?.data?.code as string,
|
||||
aggregationEpochs,
|
||||
},
|
||||
skip: skip || !referral?.data?.code,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
context: { isEnlargedTimeout: true },
|
||||
});
|
||||
const refetchAll = useCallback(() => {
|
||||
refetch();
|
||||
referral.refetch();
|
||||
}, [refetch, referral]);
|
||||
if (!referral.data || skip) {
|
||||
return referral;
|
||||
}
|
||||
const referees = compact(
|
||||
removePaginationWrapper(data?.referralSetReferees.edges)
|
||||
);
|
||||
|
||||
return {
|
||||
data: data && {
|
||||
...referral.data,
|
||||
referees: referral.data.referees.map((referee) =>
|
||||
updateReferee(referee, referees, properties)
|
||||
),
|
||||
referee:
|
||||
referral.data.referee &&
|
||||
updateReferee(referral.data.referee, referees, properties),
|
||||
},
|
||||
loading: loading || referral.loading,
|
||||
error: error || referral.error,
|
||||
refetch: refetchAll,
|
||||
};
|
||||
};
|
||||
|
||||
const retrieveReferralSetData = (data: ReferralSetsQuery | undefined) =>
|
||||
data?.referralSets.edges && data.referralSets.edges.length > 0
|
||||
? data.referralSets.edges[0]?.node
|
||||
: undefined;
|
||||
|
||||
export const useIsInReferralSet = (pubKey: string | null) => {
|
||||
const [asRefereeVariables, asRefereeSkip] = prepareVariables({
|
||||
pubKey,
|
||||
role: 'referee',
|
||||
});
|
||||
const [asReferrerVariables, asReferrerSkip] = prepareVariables({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
});
|
||||
|
||||
const { data: asRefereeData } = useReferralSetsQuery({
|
||||
variables: asRefereeVariables,
|
||||
skip: asRefereeSkip,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
const { data: asReferrerData } = useReferralSetsQuery({
|
||||
variables: asReferrerVariables,
|
||||
skip: asReferrerSkip,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
return Boolean(
|
||||
retrieveReferralSetData(asRefereeData) ||
|
||||
retrieveReferralSetData(asReferrerData)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useStakeAvailableQuery } from './__generated__/StakeAvailable';
|
||||
|
||||
/**
|
||||
* Gets the current stake available for given public key and required stake for
|
||||
* the referral program.
|
||||
*
|
||||
* (Uses currently connected public key if left empty)
|
||||
*/
|
||||
export const useStakeAvailable = (pubKey?: string) => {
|
||||
const { pubKey: currentPubKey } = useVegaWallet();
|
||||
const partyId = pubKey || currentPubKey;
|
||||
const { data } = useStakeAvailableQuery({
|
||||
variables: { partyId: partyId || '' },
|
||||
skip: !partyId,
|
||||
const STAKE_QUERY = gql`
|
||||
query CreateCode($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
}
|
||||
networkParameter(key: "referralProgram.minStakedVegaTokens") {
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useStakeAvailable = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data } = useQuery(STAKE_QUERY, {
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
// TODO: remove when network params available
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
const stakeAvailable = data
|
||||
? BigInt(data.party?.stakingSummary.currentStakeAvailable || '0')
|
||||
: undefined;
|
||||
const requiredStake = data
|
||||
? BigInt(data.networkParameter?.value || '0')
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
stakeAvailable,
|
||||
requiredStake,
|
||||
isEligible:
|
||||
stakeAvailable != null &&
|
||||
requiredStake != null &&
|
||||
stakeAvailable >= requiredStake,
|
||||
stakeAvailable: data
|
||||
? BigInt(data.party?.stakingSummary.currentStakeAvailable || '0')
|
||||
: undefined,
|
||||
requiredStake: data
|
||||
? BigInt(data.networkParameter?.value || '0')
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Table } from '../../components/table';
|
||||
import { Table } from './table';
|
||||
|
||||
export const HowItWorksTable = () => {
|
||||
const t = useT();
|
||||
|
||||
@@ -5,26 +5,21 @@ import { useT } from '../../lib/use-t';
|
||||
export const LandingBanner = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<div className={classNames('relative mb-10 lg:mb-20')}>
|
||||
<div className={classNames('relative mb-20')}>
|
||||
<div className="">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute top-20 right-[220px] md:right-[240px] max-sm:hidden"
|
||||
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden"
|
||||
>
|
||||
<AnimatedDudeWithWire />
|
||||
</div>
|
||||
<div className="pt-10 lg:pt-20 sm:w-[50%]">
|
||||
<h1 className="text-3xl _text-[6vw] lg:!text-6xl leading-[1em] font-alpha calt mb-10">
|
||||
{t('Vega community referrals')}
|
||||
<div className="pt-20 sm:w-[50%]">
|
||||
<h1 className="text-6xl font-alpha calt mb-10">
|
||||
{t('Earn commission & stake rewards')}
|
||||
</h1>
|
||||
<p className="text-lg mb-1">
|
||||
<p className="text-lg mb-10">
|
||||
{t(
|
||||
'Referral programs can be proposed and created via community governance.'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-lg mb-1">
|
||||
{t(
|
||||
'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.'
|
||||
'Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Layout = ({
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[1440px]',
|
||||
'mx-auto px-4 lg:px-32 pb-32',
|
||||
'mx-auto px-16 md:px-32 pb-32',
|
||||
'relative z-0',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,417 +0,0 @@
|
||||
import { MockedProvider, type MockedResponse } from '@apollo/react-testing';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { type VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { ReferralStatistics } from './referral-statistics';
|
||||
import {
|
||||
ReferralProgramDocument,
|
||||
type ReferralProgramQuery,
|
||||
} from './hooks/__generated__/CurrentReferralProgram';
|
||||
import {
|
||||
ReferralSetsDocument,
|
||||
type ReferralSetsQueryVariables,
|
||||
type ReferralSetsQuery,
|
||||
} from './hooks/__generated__/ReferralSets';
|
||||
import {
|
||||
StakeAvailableDocument,
|
||||
type StakeAvailableQueryVariables,
|
||||
type StakeAvailableQuery,
|
||||
} from './hooks/__generated__/StakeAvailable';
|
||||
import {
|
||||
RefereesDocument,
|
||||
type RefereesQueryVariables,
|
||||
type RefereesQuery,
|
||||
} from './hooks/__generated__/Referees';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const MOCK_PUBKEY =
|
||||
'1234567890123456789012345678901234567890123456789012345678901234';
|
||||
|
||||
const MOCK_STAKE_AVAILABLE: StakeAvailableQuery = {
|
||||
networkParameter: {
|
||||
__typename: 'NetworkParameter',
|
||||
value: '1',
|
||||
},
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
stakingSummary: {
|
||||
__typename: 'StakingSummary',
|
||||
currentStakeAvailable: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_NON_ELIGIBILE_STAKE_AVAILABLE: StakeAvailableQuery = {
|
||||
networkParameter: {
|
||||
__typename: 'NetworkParameter',
|
||||
value: '1',
|
||||
},
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
stakingSummary: {
|
||||
__typename: 'StakingSummary',
|
||||
currentStakeAvailable: '0',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_REFERRAL_PROGRAM: ReferralProgramQuery = {
|
||||
currentReferralProgram: {
|
||||
__typename: 'CurrentReferralProgram',
|
||||
benefitTiers: [
|
||||
{
|
||||
__typename: 'BenefitTier',
|
||||
minimumEpochs: 1,
|
||||
minimumRunningNotionalTakerVolume: '0',
|
||||
referralDiscountFactor: '0.01',
|
||||
referralRewardFactor: '0.01',
|
||||
},
|
||||
{
|
||||
__typename: 'BenefitTier',
|
||||
minimumEpochs: 2,
|
||||
minimumRunningNotionalTakerVolume: '10',
|
||||
referralDiscountFactor: '0.02',
|
||||
referralRewardFactor: '0.02',
|
||||
},
|
||||
],
|
||||
endOfProgramTimestamp: '202411012023-11-26T05:58:24.045158Z',
|
||||
id: '123',
|
||||
stakingTiers: [
|
||||
{
|
||||
__typename: 'StakingTier',
|
||||
minimumStakedTokens: '100',
|
||||
referralRewardMultiplier: '1',
|
||||
},
|
||||
{
|
||||
__typename: 'StakingTier',
|
||||
minimumStakedTokens: '1000',
|
||||
referralRewardMultiplier: '2',
|
||||
},
|
||||
],
|
||||
version: 2,
|
||||
windowLength: 3,
|
||||
endedAt: null,
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_REFERRER_SET: ReferralSetsQuery = {
|
||||
referralSets: {
|
||||
__typename: 'ReferralSetConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ReferralSetEdge',
|
||||
node: {
|
||||
__typename: 'ReferralSet',
|
||||
createdAt: '2023-11-26T05:58:24.045158Z',
|
||||
id: '3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa',
|
||||
referrer: MOCK_PUBKEY,
|
||||
updatedAt: '2023-11-26T05:58:24.045158Z',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_REFERREE_SET: ReferralSetsQuery = {
|
||||
referralSets: {
|
||||
__typename: 'ReferralSetConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ReferralSetEdge',
|
||||
node: {
|
||||
__typename: 'ReferralSet',
|
||||
createdAt: '2023-11-26T05:58:24.045158Z',
|
||||
id: '3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa',
|
||||
referrer:
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
updatedAt: '2023-11-26T05:58:24.045158Z',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_REFEREES: RefereesQuery = {
|
||||
referralSetReferees: {
|
||||
__typename: 'ReferralSetRefereeConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
atEpoch: 1,
|
||||
joinedAt: '2023-11-21T14:17:09.257235Z',
|
||||
refereeId:
|
||||
'0987654321098765432109876543210987654321098765432109876543219876',
|
||||
referralSetId:
|
||||
'3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa',
|
||||
totalRefereeGeneratedRewards: '1234',
|
||||
totalRefereeNotionalTakerVolume: '5678',
|
||||
__typename: 'ReferralSetReferee',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const MOCK_REFEREES_30: RefereesQuery = {
|
||||
referralSetReferees: {
|
||||
__typename: 'ReferralSetRefereeConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
atEpoch: 1,
|
||||
joinedAt: '2023-11-21T14:17:09.257235Z',
|
||||
refereeId:
|
||||
'0987654321098765432109876543210987654321098765432109876543219876',
|
||||
referralSetId:
|
||||
'3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa',
|
||||
totalRefereeGeneratedRewards: '12340',
|
||||
totalRefereeNotionalTakerVolume: '56780',
|
||||
__typename: 'ReferralSetReferee',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const programMock: MockedResponse<ReferralProgramQuery> = {
|
||||
request: {
|
||||
query: ReferralProgramDocument,
|
||||
},
|
||||
result: { data: MOCK_REFERRAL_PROGRAM },
|
||||
};
|
||||
|
||||
const referralSetAsReferrerMock: MockedResponse<
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ReferralSetsDocument,
|
||||
variables: {
|
||||
referrer: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_REFERRER_SET,
|
||||
},
|
||||
};
|
||||
|
||||
const noReferralSetAsReferrerMock: MockedResponse<
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ReferralSetsDocument,
|
||||
variables: {
|
||||
referrer: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: { referralSets: { edges: [] } },
|
||||
},
|
||||
};
|
||||
|
||||
const referralSetAsRefereeMock: MockedResponse<
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ReferralSetsDocument,
|
||||
variables: {
|
||||
referee: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_REFERREE_SET,
|
||||
},
|
||||
};
|
||||
|
||||
const noReferralSetAsRefereeMock: MockedResponse<
|
||||
ReferralSetsQuery,
|
||||
ReferralSetsQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: ReferralSetsDocument,
|
||||
variables: {
|
||||
referee: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: { referralSets: { edges: [] } },
|
||||
},
|
||||
};
|
||||
|
||||
const stakeAvailableMock: MockedResponse<
|
||||
StakeAvailableQuery,
|
||||
StakeAvailableQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: StakeAvailableDocument,
|
||||
variables: {
|
||||
partyId: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_STAKE_AVAILABLE,
|
||||
},
|
||||
};
|
||||
|
||||
const nonEligibleStakeAvailableMock: MockedResponse<
|
||||
StakeAvailableQuery,
|
||||
StakeAvailableQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: StakeAvailableDocument,
|
||||
variables: {
|
||||
partyId: MOCK_PUBKEY,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_NON_ELIGIBILE_STAKE_AVAILABLE,
|
||||
},
|
||||
};
|
||||
|
||||
const refereesMock: MockedResponse<RefereesQuery, RefereesQueryVariables> = {
|
||||
request: {
|
||||
query: RefereesDocument,
|
||||
variables: {
|
||||
code: MOCK_REFERRER_SET.referralSets.edges[0]?.node.id as string,
|
||||
aggregationEpochs:
|
||||
MOCK_REFERRAL_PROGRAM.currentReferralProgram?.windowLength,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_REFEREES,
|
||||
},
|
||||
};
|
||||
|
||||
const refereesMock30: MockedResponse<RefereesQuery, RefereesQueryVariables> = {
|
||||
request: {
|
||||
query: RefereesDocument,
|
||||
variables: {
|
||||
code: MOCK_REFERRER_SET.referralSets.edges[0]?.node.id as string,
|
||||
aggregationEpochs: 30,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: MOCK_REFEREES_30,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@vegaprotocol/wallet', () => {
|
||||
return {
|
||||
...jest.requireActual('@vegaprotocol/wallet'),
|
||||
useVegaWallet: () => {
|
||||
const ctx: Partial<VegaWalletContextShape> = {
|
||||
pubKey: MOCK_PUBKEY,
|
||||
};
|
||||
return ctx;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('ReferralStatistics', () => {
|
||||
it('displays apply code when no data has been found for given pubkey', () => {
|
||||
const { queryByTestId } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={[]} showWarnings={false}>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(queryByTestId('referral-apply-code-form')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays referrer stats when given pubkey is a referrer', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
programMock,
|
||||
referralSetAsReferrerMock,
|
||||
noReferralSetAsRefereeMock,
|
||||
stakeAvailableMock,
|
||||
refereesMock,
|
||||
refereesMock30,
|
||||
]}
|
||||
showWarnings={false}
|
||||
>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
queryByTestId('referral-create-code-form')
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')).toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')?.dataset.as).toEqual(
|
||||
'referrer'
|
||||
);
|
||||
// gets commision from 30 epochs query
|
||||
expect(queryByTestId('total-commission-value')).toHaveTextContent(
|
||||
'12,340'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays referee stats when given pubkey is a referee', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
programMock,
|
||||
noReferralSetAsReferrerMock,
|
||||
referralSetAsRefereeMock,
|
||||
stakeAvailableMock,
|
||||
refereesMock,
|
||||
]}
|
||||
showWarnings={false}
|
||||
>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
queryByTestId('referral-create-code-form')
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')).toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')?.dataset.as).toEqual(
|
||||
'referee'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays eligibility warning when the set is no longer valid due to the referrers stake', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
programMock,
|
||||
noReferralSetAsReferrerMock,
|
||||
referralSetAsRefereeMock,
|
||||
nonEligibleStakeAvailableMock,
|
||||
refereesMock,
|
||||
]}
|
||||
showWarnings={false}
|
||||
>
|
||||
<ReferralStatistics />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
queryByTestId('referral-create-code-form')
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')).toBeInTheDocument();
|
||||
expect(queryByTestId('referral-statistics')?.dataset.as).toEqual(
|
||||
'referee'
|
||||
);
|
||||
expect(queryByTestId('referral-eligibility-warning')).toBeInTheDocument();
|
||||
expect(queryByTestId('referral-apply-code-form')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,17 @@
|
||||
import minBy from 'lodash/minBy';
|
||||
import { CodeTile, StatTile } from './tile';
|
||||
import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
TextChildrenTooltip as Tooltip,
|
||||
ExternalLink,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
DEFAULT_AGGREGATION_DAYS,
|
||||
useReferral,
|
||||
useUpdateReferees,
|
||||
} from './hooks/use-referral';
|
||||
import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral';
|
||||
import { CreateCodeContainer } from './create-code-form';
|
||||
import classNames from 'classnames';
|
||||
import { Table } from '../../components/table';
|
||||
import { Table } from './table';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateFormat,
|
||||
@@ -28,73 +25,52 @@ import compact from 'lodash/compact';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import maxBy from 'lodash/maxBy';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const program = useReferralProgram();
|
||||
|
||||
const { data: referee, refetch: refereeRefetch } = useReferral({
|
||||
const { data: referee } = useReferral({
|
||||
pubKey,
|
||||
role: 'referee',
|
||||
aggregationEpochs: program.details?.windowLength,
|
||||
});
|
||||
|
||||
const { data: referrer, refetch: referrerRefetch } = useUpdateReferees(
|
||||
useReferral({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
aggregationEpochs: program.details?.windowLength,
|
||||
}),
|
||||
DEFAULT_AGGREGATION_DAYS,
|
||||
['totalRefereeGeneratedRewards'],
|
||||
DEFAULT_AGGREGATION_DAYS === program.details?.windowLength
|
||||
);
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
refereeRefetch();
|
||||
referrerRefetch();
|
||||
}, [refereeRefetch, referrerRefetch]);
|
||||
const { data: referrer } = useReferral({
|
||||
pubKey,
|
||||
role: 'referrer',
|
||||
aggregationEpochs: program.details?.windowLength,
|
||||
});
|
||||
|
||||
if (referee?.code) {
|
||||
return (
|
||||
<>
|
||||
<Statistics data={referee} program={program} as="referee" />
|
||||
{!referee.isEligible && <ApplyCodeForm />}
|
||||
</>
|
||||
);
|
||||
return <Statistics data={referee} program={program} as="referee" />;
|
||||
}
|
||||
|
||||
if (referrer?.code) {
|
||||
return (
|
||||
<>
|
||||
<Statistics data={referrer} program={program} as="referrer" />
|
||||
<RefereesTable data={referrer} program={program} />
|
||||
</>
|
||||
);
|
||||
return <Statistics data={referrer} program={program} as="referrer" />;
|
||||
}
|
||||
|
||||
return <ApplyCodeFormContainer onSuccess={refetch} />;
|
||||
return <CreateCodeContainer />;
|
||||
};
|
||||
|
||||
export const useStats = ({
|
||||
data,
|
||||
program,
|
||||
as,
|
||||
}: {
|
||||
data?: NonNullable<ReturnType<typeof useReferral>['data']>;
|
||||
program: ReturnType<typeof useReferralProgram>;
|
||||
as?: 'referrer' | 'referee';
|
||||
}) => {
|
||||
const { benefitTiers } = program;
|
||||
const { data: epochData } = useCurrentEpochInfoQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const { data: epochData } = useCurrentEpochInfoQuery();
|
||||
const { data: statsData } = useReferralSetStatsQuery({
|
||||
variables: {
|
||||
code: data?.code || '',
|
||||
@@ -128,7 +104,7 @@ export const useStats = ({
|
||||
: 1;
|
||||
const finalCommissionValue = isNaN(multiplier)
|
||||
? baseCommissionValue
|
||||
: new BigNumber(multiplier).times(baseCommissionValue).toNumber();
|
||||
: multiplier * baseCommissionValue;
|
||||
|
||||
const discountFactorValue = refereeStats?.discountFactor
|
||||
? Number(refereeStats.discountFactor)
|
||||
@@ -140,8 +116,8 @@ export const useStats = ({
|
||||
t.discountFactor === discountFactorValue
|
||||
);
|
||||
const nextBenefitTierValue = currentBenefitTierValue
|
||||
? benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier + 1)
|
||||
: minBy(benefitTiers, (bt) => bt.tier); // min tier number is lowest tier
|
||||
? benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier - 1)
|
||||
: maxBy(benefitTiers, (bt) => bt.tier); // max tier number is lowest tier
|
||||
const epochsValue =
|
||||
!isNaN(currentEpoch) && refereeInfo?.atEpoch
|
||||
? currentEpoch - refereeInfo?.atEpoch
|
||||
@@ -187,10 +163,9 @@ export const Statistics = ({
|
||||
discountFactorValue,
|
||||
currentBenefitTierValue,
|
||||
epochsValue,
|
||||
nextBenefitTierValue,
|
||||
nextBenefitTierVolumeValue,
|
||||
nextBenefitTierEpochsValue,
|
||||
} = useStats({ data, program });
|
||||
} = useStats({ data, program, as });
|
||||
|
||||
const isApplyCodePreview = useMemo(
|
||||
() => data.referee === null,
|
||||
@@ -199,7 +174,7 @@ export const Statistics = ({
|
||||
|
||||
const { benefitTiers } = useReferralProgram();
|
||||
|
||||
const { stakeAvailable, isEligible } = useStakeAvailable();
|
||||
const { stakeAvailable } = useStakeAvailable();
|
||||
const { details } = program;
|
||||
|
||||
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
|
||||
@@ -221,61 +196,37 @@ export const Statistics = ({
|
||||
).toString(),
|
||||
}
|
||||
)}
|
||||
testId="base-commission-rate"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{baseCommissionValue * 100}%
|
||||
</StatTile>
|
||||
);
|
||||
|
||||
const stakingMultiplierTile = (
|
||||
<StatTile
|
||||
title={t('Staking multiplier')}
|
||||
testId="staking-multiplier"
|
||||
description={
|
||||
<span
|
||||
className={classNames({
|
||||
'text-vega-red': !isEligible,
|
||||
})}
|
||||
>
|
||||
{t('{{amount}} $VEGA staked', {
|
||||
amount: addDecimalsFormatNumber(
|
||||
stakeAvailable?.toString() || 0,
|
||||
18
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
overrideWithNoProgram={!details}
|
||||
description={t('{{amount}} $VEGA staked', {
|
||||
amount: addDecimalsFormatNumber(stakeAvailable?.toString() || 0, 18),
|
||||
})}
|
||||
>
|
||||
{multiplier || t('None')}
|
||||
</StatTile>
|
||||
);
|
||||
const baseCommissionFormatted = BigNumber(baseCommissionValue)
|
||||
.times(100)
|
||||
.toString();
|
||||
const finalCommissionFormatted = new BigNumber(finalCommissionValue)
|
||||
.times(100)
|
||||
.toString();
|
||||
const finalCommissionTile = (
|
||||
<StatTile
|
||||
title={t('Final commission rate')}
|
||||
description={
|
||||
!isNaN(multiplier)
|
||||
? `(${baseCommissionFormatted}% ⨉ ${multiplier} = ${finalCommissionFormatted}%)`
|
||||
? `(${baseCommissionValue * 100}% ⨉ ${multiplier} = ${
|
||||
finalCommissionValue * 100
|
||||
}%)`
|
||||
: undefined
|
||||
}
|
||||
testId="final-commission-rate"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{finalCommissionFormatted}%
|
||||
{finalCommissionValue * 100}%
|
||||
</StatTile>
|
||||
);
|
||||
const numberOfTradersValue = data.referees.length;
|
||||
const numberOfTradersTile = (
|
||||
<StatTile title={t('Number of traders')} testId="number-of-traders">
|
||||
{numberOfTradersValue}
|
||||
</StatTile>
|
||||
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
|
||||
);
|
||||
|
||||
const codeTile = (
|
||||
@@ -287,11 +238,9 @@ export const Statistics = ({
|
||||
|
||||
const referrerVolumeTile = (
|
||||
<StatTile
|
||||
title={t('myVolume', 'My volume (last {{count}} epochs)', {
|
||||
title={t('My volume (last {{count}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
testId="my-volume"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{compactNumFormat.format(referrerVolumeValue)}
|
||||
</StatTile>
|
||||
@@ -302,26 +251,9 @@ export const Statistics = ({
|
||||
.reduce((all, r) => all.plus(r), new BigNumber(0));
|
||||
const totalCommissionTile = (
|
||||
<StatTile
|
||||
testId="total-commission"
|
||||
title={
|
||||
<Trans
|
||||
i18nKey="totalCommission"
|
||||
defaults="Total commission (<0>last {{count}} epochs</0>)"
|
||||
values={{
|
||||
count: DEFAULT_AGGREGATION_DAYS,
|
||||
}}
|
||||
components={[
|
||||
<Tooltip
|
||||
key="1"
|
||||
description={t(
|
||||
'Depending on data node retention you may not be able see the full 30 days'
|
||||
)}
|
||||
>
|
||||
last 30 epochs
|
||||
</Tooltip>,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
title={t('Total commission (last {{count}}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
description={<QUSDTooltip />}
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
@@ -346,30 +278,15 @@ export const Statistics = ({
|
||||
);
|
||||
|
||||
const currentBenefitTierTile = (
|
||||
<StatTile
|
||||
title={t('Current tier')}
|
||||
testId="current-tier"
|
||||
description={
|
||||
nextBenefitTierValue?.tier
|
||||
? t('(Next tier: {{nextTier}})', {
|
||||
nextTier: nextBenefitTierValue?.tier,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Current tier')}>
|
||||
{isApplyCodePreview
|
||||
? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None'
|
||||
: currentBenefitTierValue?.tier || 'None'}
|
||||
</StatTile>
|
||||
);
|
||||
const discountFactorTile = (
|
||||
<StatTile
|
||||
title={t('Discount')}
|
||||
testId="discount"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{isApplyCodePreview && benefitTiers.length >= 1
|
||||
<StatTile title={t('Discount')}>
|
||||
{isApplyCodePreview
|
||||
? benefitTiers[0].discountFactor * 100
|
||||
: discountFactorValue * 100}
|
||||
%
|
||||
@@ -377,41 +294,25 @@ export const Statistics = ({
|
||||
);
|
||||
const runningVolumeTile = (
|
||||
<StatTile
|
||||
title={t(
|
||||
'runningNotionalOverEpochs',
|
||||
'Combined volume (last {{count}} epochs)',
|
||||
{
|
||||
count: details?.windowLength,
|
||||
}
|
||||
)}
|
||||
testId="combined-volume"
|
||||
overrideWithNoProgram={!details}
|
||||
title={t('Combined volume (last {{count}} epochs)', {
|
||||
count: details?.windowLength,
|
||||
})}
|
||||
>
|
||||
{compactNumFormat.format(runningVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const epochsTile = (
|
||||
<StatTile title={t('Epochs in set')} testId="epochs-in-set">
|
||||
{epochsValue}
|
||||
</StatTile>
|
||||
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||
);
|
||||
const nextTierVolumeTile = (
|
||||
<StatTile
|
||||
title={t('Volume to next tier')}
|
||||
testId="vol-to-next-tier"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Volume to next tier')}>
|
||||
{nextBenefitTierVolumeValue <= 0
|
||||
? '0'
|
||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const nextTierEpochsTile = (
|
||||
<StatTile
|
||||
title={t('Epochs to next tier')}
|
||||
testId="epochs-to-next-tier"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Epochs to next tier')}>
|
||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -432,60 +333,28 @@ export const Statistics = ({
|
||||
</>
|
||||
);
|
||||
|
||||
const eligibilityWarning = as === 'referee' && !isEligible && (
|
||||
<div
|
||||
data-testid="referral-eligibility-warning"
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center w-1/2 lg:w-1/3"
|
||||
>
|
||||
<h2 className="text-2xl mb-2">{t('Referral code no longer valid')}</h2>
|
||||
<p>
|
||||
{t(
|
||||
'Your referral code is no longer valid as the referrer no longer meets the minimum requirements. Apply a new code to continue receiving discounts.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="referral-statistics"
|
||||
data-as={as}
|
||||
className="relative mx-auto mb-20"
|
||||
>
|
||||
<div
|
||||
className={classNames('grid grid-cols-1 grid-rows-1 gap-5', {
|
||||
'opacity-20 pointer-events-none': as === 'referee' && !isEligible,
|
||||
})}
|
||||
>
|
||||
{as === 'referrer' && referrerTiles}
|
||||
{as === 'referee' && refereeTiles}
|
||||
</div>
|
||||
|
||||
{eligibilityWarning}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const RefereesTable = ({
|
||||
data,
|
||||
program,
|
||||
}: {
|
||||
data: NonNullable<ReturnType<typeof useReferral>['data']>;
|
||||
program: ReturnType<typeof useReferralProgram>;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const tableRef = useRef<HTMLTableElement>(null);
|
||||
const { details } = program;
|
||||
useLayoutEffect(() => {
|
||||
if ((tableRef.current?.getBoundingClientRect().height || 0) > 384) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Stats tiles */}
|
||||
<div
|
||||
className={classNames(
|
||||
'grid grid-cols-1 grid-rows-1 gap-5 mx-auto mb-20'
|
||||
)}
|
||||
>
|
||||
{as === 'referrer' && referrerTiles}
|
||||
{as === 'referee' && refereeTiles}
|
||||
</div>
|
||||
|
||||
{/* Referees (only for referrer view) */}
|
||||
{data.referees.length > 0 && (
|
||||
{as === 'referrer' && data.referees.length > 0 && (
|
||||
<div className="mt-20 mb-20">
|
||||
<h2 className="mb-5 text-2xl">{t('Referees')}</h2>
|
||||
<div
|
||||
@@ -515,33 +384,19 @@ export const RefereesTable = ({
|
||||
{ name: 'joined', displayName: t('Date Joined') },
|
||||
{
|
||||
name: 'volume',
|
||||
displayName: t(
|
||||
'volumeLastEpochs',
|
||||
'Volume (last {{count}} epochs)',
|
||||
{
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
}
|
||||
),
|
||||
displayName: t('Volume (last {{count}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'commission',
|
||||
displayName: (
|
||||
<Trans
|
||||
i18nKey="referralStatisticsCommission"
|
||||
defaults="Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)"
|
||||
components={[
|
||||
<QUSDTooltip key="0" />,
|
||||
<Tooltip
|
||||
key="1"
|
||||
description={t(
|
||||
'Depending on data node retention you may not be able see the full 30 days'
|
||||
)}
|
||||
>
|
||||
last 30 epochs
|
||||
</Tooltip>,
|
||||
]}
|
||||
i18nKey="referral-statistics-commission"
|
||||
defaults="Commission earned in <0>qUSD</0> (last {{count}} epochs)"
|
||||
values={{
|
||||
count: DEFAULT_AGGREGATION_DAYS,
|
||||
count:
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
}}
|
||||
ns={ns}
|
||||
/>
|
||||
@@ -574,3 +429,28 @@ export const RefereesTable = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,10 +4,11 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { HowItWorksTable } from './how-it-works-table';
|
||||
import { LandingBanner } from './landing-banner';
|
||||
import { TiersContainer } from './tiers';
|
||||
import { TabLink } from './buttons';
|
||||
import { Outlet, useMatch } from 'react-router-dom';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useReferral } from './hooks/use-referral';
|
||||
@@ -17,17 +18,15 @@ import { usePageTitleStore } from '../../stores';
|
||||
import { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
|
||||
const Nav = () => {
|
||||
const t = useT();
|
||||
const match = useMatch(Routes.REFERRALS_APPLY_CODE);
|
||||
return (
|
||||
<div className="flex justify-center border-b border-vega-cdark-500">
|
||||
<TabLink end to={match ? Routes.REFERRALS_APPLY_CODE : Routes.REFERRALS}>
|
||||
{t('Apply code')}
|
||||
<TabLink end to={Routes.REFERRALS}>
|
||||
{t('I want a code')}
|
||||
</TabLink>
|
||||
<TabLink to={Routes.REFERRALS_CREATE_CODE}>{t('Create code')}</TabLink>
|
||||
<TabLink to={Routes.REFERRALS_APPLY_CODE}>{t('I have a code')}</TabLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -66,13 +65,13 @@ export const Referrals = () => {
|
||||
}, [updateTitle, t]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary feature="referrals">
|
||||
<>
|
||||
<LandingBanner />
|
||||
|
||||
{showNav && <Nav />}
|
||||
<div
|
||||
className={classNames({
|
||||
'py-8 lg:py-16': showNav,
|
||||
'py-16': showNav,
|
||||
'h-[300px] relative': loading || error,
|
||||
})}
|
||||
>
|
||||
@@ -96,16 +95,18 @@ export const Referrals = () => {
|
||||
<h2 className="text-2xl">{t('How it works')}</h2>
|
||||
</div>
|
||||
<div className="md:w-[60%] mx-auto">
|
||||
<HowItWorksTable />
|
||||
<div className="mt-5">
|
||||
<TradingAnchorButton
|
||||
className="mx-auto w-max"
|
||||
href={REFERRAL_DOCS_LINK}
|
||||
target="_blank"
|
||||
>
|
||||
{t('Read the docs')} <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
{t('Read the terms')}{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</TradingAnchorButton>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-10
@@ -1,17 +1,13 @@
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
|
||||
|
||||
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
|
||||
export const GRADIENT =
|
||||
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
|
||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||
|
||||
type TableColumnDefinition = {
|
||||
displayName?: ReactNode;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
className?: string;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
type TableProps = {
|
||||
@@ -46,7 +42,7 @@ export const Table = forwardRef<
|
||||
key={name}
|
||||
col-id={name}
|
||||
className={classNames(
|
||||
'px-5 py-3 text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
|
||||
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
|
||||
INNER_BORDER_STYLE
|
||||
)}
|
||||
>
|
||||
@@ -70,7 +66,7 @@ export const Table = forwardRef<
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'border-separate border rounded-md border-spacing-0 overflow-hidden',
|
||||
'border-separate border rounded-md border-spacing-0',
|
||||
BORDER_COLOR,
|
||||
GRADIENT,
|
||||
className
|
||||
@@ -86,10 +82,10 @@ export const Table = forwardRef<
|
||||
'max-md:flex flex-col w-full': !noCollapse,
|
||||
})}
|
||||
>
|
||||
{columns.map(({ name, displayName, className, testId }, j) => (
|
||||
{columns.map(({ name, displayName, className }, j) => (
|
||||
<td
|
||||
className={classNames(
|
||||
'px-5 py-3',
|
||||
'px-5 py-3 text-base',
|
||||
{
|
||||
'max-md:flex max-md:flex-col max-md:justify-between':
|
||||
!noCollapse,
|
||||
@@ -114,7 +110,7 @@ export const Table = forwardRef<
|
||||
{displayName}
|
||||
</span>
|
||||
)}
|
||||
<span data-testid={`${testId || name}-${i}`}>{d[name]}</span>
|
||||
<span>{d[name]}</span>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
+3
-13
@@ -2,15 +2,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type TagProps = {
|
||||
color?:
|
||||
| 'yellow'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'purple'
|
||||
| 'pink'
|
||||
| 'orange'
|
||||
| 'red'
|
||||
| 'none';
|
||||
color?: 'yellow' | 'green' | 'blue' | 'purple' | 'pink' | 'orange' | 'none';
|
||||
};
|
||||
export const Tag = ({
|
||||
color = 'none',
|
||||
@@ -22,10 +14,8 @@ export const Tag = ({
|
||||
className={classNames(
|
||||
'w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
|
||||
{
|
||||
'border-vega-yellow-550 text-vega-yellow-550 dark:border-vega-yellow-500 dark:text-vega-yellow-500':
|
||||
color === 'yellow',
|
||||
'border-vega-green-550 text-vega-green-550 dark:border-vega-green-500 dark:text-vega-green-500':
|
||||
color === 'green',
|
||||
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
|
||||
'border-vega-green-500 text-vega-green-500': color === 'green',
|
||||
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
|
||||
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
|
||||
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
|
||||
@@ -1,19 +1,14 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { Table } from '../../components/table';
|
||||
import { Table } from './table';
|
||||
import classNames from 'classnames';
|
||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||
import { Tag } from '../../components/helpers/tag';
|
||||
import { getTierColor, getTierGradient } from '../../components/helpers/tiers';
|
||||
import type { ReactNode } from 'react';
|
||||
import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { Tag } from './tag';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
DApp,
|
||||
DocsLinks,
|
||||
TOKEN_PROPOSAL,
|
||||
TOKEN_PROPOSALS,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
@@ -33,65 +28,51 @@ const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
||||
|
||||
const StakingTier = ({
|
||||
tier,
|
||||
label,
|
||||
referralRewardMultiplier,
|
||||
minimumStakedTokens,
|
||||
max,
|
||||
}: {
|
||||
tier: number;
|
||||
label: string;
|
||||
referralRewardMultiplier: string;
|
||||
minimumStakedTokens: string;
|
||||
max?: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18);
|
||||
|
||||
// TODO: Decide what to do with the multiplier images
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const multiplierImage = (
|
||||
<div
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
'w-full max-w-[80px] h-full min-h-[80px]',
|
||||
'bg-cover bg-right-bottom',
|
||||
{
|
||||
"bg-[url('/1x.png')]": tier === 1,
|
||||
"bg-[url('/2x.png')]": tier === 2,
|
||||
"bg-[url('/3x.png')]": tier === 3,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">{`${referralRewardMultiplier}x multiplier`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const color: Record<number, ComponentProps<typeof Tag>['color']> = {
|
||||
1: 'green',
|
||||
2: 'blue',
|
||||
3: 'pink',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'overflow-hidden',
|
||||
'border rounded-md w-full',
|
||||
'flex flex-row',
|
||||
'bg-white dark:bg-vega-cdark-900',
|
||||
GRADIENT,
|
||||
BORDER_COLOR
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'p-3 flex flex-row min-h-[80px] h-full items-center'
|
||||
<div aria-hidden className="max-w-[120px]">
|
||||
{tier < 4 && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={`/${tier}x.png`}
|
||||
alt={`${referralRewardMultiplier}x multiplier`}
|
||||
width={240}
|
||||
height={240}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tag color={getTierColor(tier, max)}>
|
||||
{t('Multiplier')} {referralRewardMultiplier}x
|
||||
</Tag>
|
||||
<p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
<Trans
|
||||
defaults="Stake a minimum of <0>{{minimum}}</0> $VEGA tokens"
|
||||
values={{ minimum }}
|
||||
components={[<b key={minimum}></b>]}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames('p-3')}>
|
||||
<Tag color={color[tier]}>Multiplier {referralRewardMultiplier}x</Tag>
|
||||
<h3 className="mt-1 mb-1 text-base">{label}</h3>
|
||||
<p className="text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{t('Stake a minimum of {{minimumStakedTokens}} $VEGA tokens', {
|
||||
minimumStakedTokens,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -110,29 +91,21 @@ export const TiersContainer = () => {
|
||||
|
||||
if ((!loading && !details) || error) {
|
||||
return (
|
||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20 text-sm text-center">
|
||||
<div className="text-base px-5 py-10 text-center">
|
||||
<Trans
|
||||
defaults="There are currently no active referral programs. Check the <0>Governance App</0> to see if there are any proposals in progress and vote."
|
||||
components={[
|
||||
<ExternalLink
|
||||
href={governanceLink(TOKEN_PROPOSALS)}
|
||||
key="link"
|
||||
className="underline"
|
||||
>
|
||||
<ExternalLink href={governanceLink(TOKEN_PROPOSALS)} key="link">
|
||||
{t('Governance App')}
|
||||
</ExternalLink>,
|
||||
]}
|
||||
ns={ns}
|
||||
/>{' '}
|
||||
/>
|
||||
<Trans
|
||||
defaults="Use the <0>docs</0> tutorial to propose a new program."
|
||||
defaults="You can propose a new program via the <0>Docs</0>."
|
||||
components={[
|
||||
<ExternalLink
|
||||
href={DocsLinks?.REFERRALS}
|
||||
key="link"
|
||||
className="underline"
|
||||
>
|
||||
{t('docs')}
|
||||
<ExternalLink href={DocsLinks?.REFERRALS} key="link">
|
||||
{t('Docs')}
|
||||
</ExternalLink>,
|
||||
]}
|
||||
ns={ns}
|
||||
@@ -143,106 +116,47 @@ export const TiersContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-3xl mt-10 font-alpha calt">
|
||||
{t('Current program details')}
|
||||
</h2>
|
||||
{details?.id && (
|
||||
<p>
|
||||
<Trans
|
||||
defaults="As a result of governance proposal <0>{{proposal}}</0> the program below is currently active on the Vega network."
|
||||
values={{ proposal: truncateMiddle(details.id) }}
|
||||
components={[
|
||||
<ExternalLink
|
||||
key="referral-program-proposal-link"
|
||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
||||
className="underline"
|
||||
>
|
||||
proposal
|
||||
</ExternalLink>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Meta */}
|
||||
<div className="mt-10 flex flex-row items-baseline justify-between text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-alpha calt">
|
||||
{details?.id && (
|
||||
<span>
|
||||
{t('Proposal ID:')}{' '}
|
||||
<ExternalLink
|
||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
||||
>
|
||||
<span>{truncateMiddle(details.id)}</span>
|
||||
</ExternalLink>
|
||||
</span>
|
||||
)}
|
||||
{/* Benefit tiers */}
|
||||
<div className="flex flex-col items-baseline justify-between mt-10 mb-5">
|
||||
<h2 className="text-2xl">{t('Referral tiers')}</h2>
|
||||
{ends && (
|
||||
<span>
|
||||
<span className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
{t('Program ends:')} {ends}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Container */}
|
||||
<div
|
||||
className={classNames(
|
||||
'md:bg-vega-clight-800',
|
||||
'md:dark:bg-vega-cdark-800',
|
||||
'md:text-black',
|
||||
'md:dark:text-white',
|
||||
'md:rounded-lg',
|
||||
'md:p-6',
|
||||
'mt-1',
|
||||
'mb-20'
|
||||
<div className="mb-20">
|
||||
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
||||
<Loading variant="large" />
|
||||
) : (
|
||||
<TiersTable
|
||||
windowLength={details?.windowLength}
|
||||
data={benefitTiers.map((bt) => ({
|
||||
...bt,
|
||||
tierElement: (
|
||||
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
||||
{bt.tier}
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{/* Benefit tiers */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h3 className="text-2xl calt">{t('Benefit tiers')}</h3>
|
||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
{t(
|
||||
'Members of a referral group can access the increasing commission and discount benefits defined in the program based on their combined running volume.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
||||
<Loading variant="large" />
|
||||
) : (
|
||||
<TiersTable
|
||||
windowLength={details?.windowLength}
|
||||
data={benefitTiers.map((bt) => ({
|
||||
...bt,
|
||||
tierElement: (
|
||||
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
||||
{bt.tier}
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Staking tiers */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h3 className="text-2xl calt">{t('Staking multipliers')}</h3>
|
||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||
{t(
|
||||
'Referrers can access the commission multipliers defined in the program by staking VEGA tokens in the amounts shown.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="gap-5 grid lg:grid-cols-3">
|
||||
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
||||
<>
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
</>
|
||||
) : (
|
||||
<StakingTiers data={stakingTiers} />
|
||||
)}
|
||||
</div>
|
||||
{/* Staking tiers */}
|
||||
<div className="flex flex-row items-baseline justify-between mb-5">
|
||||
<h2 className="text-2xl">{t('Staking multipliers')}</h2>
|
||||
</div>
|
||||
<div className="mb-20 flex flex-col justify-items-stretch lg:flex-row gap-5">
|
||||
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
||||
<>
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
<Loading variant="large" />
|
||||
</>
|
||||
) : (
|
||||
<StakingTiers data={stakingTiers} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -254,15 +168,17 @@ const StakingTiers = ({
|
||||
data: ReturnType<typeof useReferralProgram>['stakingTiers'];
|
||||
}) => (
|
||||
<>
|
||||
{data.map(({ tier, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
||||
<StakingTier
|
||||
key={i}
|
||||
tier={tier}
|
||||
max={data.length}
|
||||
referralRewardMultiplier={referralRewardMultiplier}
|
||||
minimumStakedTokens={minimumStakedTokens}
|
||||
/>
|
||||
))}
|
||||
{data.map(
|
||||
({ tier, label, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
||||
<StakingTier
|
||||
key={i}
|
||||
tier={tier}
|
||||
label={label}
|
||||
referralRewardMultiplier={referralRewardMultiplier}
|
||||
minimumStakedTokens={minimumStakedTokens}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -287,40 +203,29 @@ const TiersTable = ({
|
||||
{
|
||||
name: 'commission',
|
||||
displayName: t('Referrer commission'),
|
||||
tooltip: t(
|
||||
"The proportion of the referee's taker fees to be rewarded to the referrer"
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'discount',
|
||||
displayName: t('Referee trading discount'),
|
||||
tooltip: t(
|
||||
"The proportion of the referee's taker fees to be discounted"
|
||||
),
|
||||
tooltip: t('A percentage of commission earned by the referrer'),
|
||||
},
|
||||
{ name: 'discount', displayName: t('Referrer trading discount') },
|
||||
{
|
||||
name: 'volume',
|
||||
displayName: t(
|
||||
'minTradingVolume',
|
||||
'Min. trading volume (last {{count}} epochs)',
|
||||
{
|
||||
count: windowLength,
|
||||
}
|
||||
),
|
||||
tooltip: t('The minimum running notional for the given benefit tier'),
|
||||
},
|
||||
{
|
||||
name: 'epochs',
|
||||
displayName: t('Min. epochs'),
|
||||
tooltip: t(
|
||||
'The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit'
|
||||
),
|
||||
displayName: t('Min. trading volume (last {{count}} epochs)', {
|
||||
count: windowLength,
|
||||
}),
|
||||
},
|
||||
{ name: 'epochs', displayName: t('Min. epochs') },
|
||||
]}
|
||||
className="bg-white dark:bg-vega-cdark-900"
|
||||
data={data.map((d) => ({
|
||||
...d,
|
||||
className: classNames(getTierGradient(d.tier, data.length)),
|
||||
className: classNames({
|
||||
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
|
||||
d.tier === 1,
|
||||
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
|
||||
d.tier === 2,
|
||||
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
|
||||
d.tier === 3,
|
||||
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
|
||||
d.tier > 3,
|
||||
}),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,11 +19,9 @@ export const Tile = ({
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'text-black dark:text-white',
|
||||
'overflow-hidden relative',
|
||||
'p-3 md:p-6',
|
||||
'rounded-lg',
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800',
|
||||
'rounded-lg overflow-hidden relative',
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white',
|
||||
'p-6',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -33,55 +31,22 @@ export const Tile = ({
|
||||
};
|
||||
|
||||
type StatTileProps = {
|
||||
title: ReactNode;
|
||||
testId?: string;
|
||||
title: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
overrideWithNoProgram?: boolean;
|
||||
};
|
||||
export const StatTile = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
testId,
|
||||
overrideWithNoProgram = false,
|
||||
}: StatTileProps) => {
|
||||
if (overrideWithNoProgram) {
|
||||
return <NoProgramTile title={title} />;
|
||||
}
|
||||
return (
|
||||
<Tile>
|
||||
<h3
|
||||
data-testid={testId}
|
||||
className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt"
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<div
|
||||
data-testid={`${testId}-value`}
|
||||
className="text-2xl lg:text-5xl text-left"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{description && (
|
||||
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</Tile>
|
||||
);
|
||||
};
|
||||
|
||||
export const NoProgramTile = ({ title }: Pick<StatTileProps, 'title'>) => {
|
||||
const t = useT();
|
||||
export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||
return (
|
||||
<Tile>
|
||||
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="text-xs text-vega-clight-300 dark:text-vega-cdark-300 leading-[3rem]">
|
||||
{t('No active program')}
|
||||
</div>
|
||||
<div className="text-5xl text-left">{children}</div>
|
||||
{description && (
|
||||
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</Tile>
|
||||
);
|
||||
};
|
||||
@@ -124,7 +89,7 @@ export const CodeTile = ({
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative bg-rainbow bg-clip-text text-transparent text-2xl lg:text-5xl overflow-hidden',
|
||||
'relative bg-rainbow bg-clip-text text-transparent text-5xl overflow-hidden',
|
||||
FADE_OUT_STYLE
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { RewardsContainer } from '../../components/rewards-container';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Rewards = () => {
|
||||
const t = useT();
|
||||
@@ -15,11 +14,9 @@ export const Rewards = () => {
|
||||
updateTitle(titlefy([title]));
|
||||
}, [updateTitle, title]);
|
||||
return (
|
||||
<ErrorBoundary feature="rewards">
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||
<RewardsContainer />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||
<RewardsContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { Teams } from './teams';
|
||||
@@ -1,7 +0,0 @@
|
||||
export const Teams = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Teams</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,6 @@ import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { usePersistentDepositStore } from '@vegaprotocol/deposits';
|
||||
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
@@ -26,7 +25,6 @@ export const AccountsContainer = ({
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setDepositAsset = usePersistentDepositStore((store) => store.saveValue);
|
||||
|
||||
const gridStore = useAccountStore((store) => store.gridStore);
|
||||
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
||||
@@ -57,10 +55,7 @@ export const AccountsContainer = ({
|
||||
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
|
||||
}}
|
||||
onClickDeposit={(assetId) => {
|
||||
setViews({ type: ViewType.Deposit }, currentRouteId);
|
||||
if (assetId) {
|
||||
setDepositAsset({ assetId });
|
||||
}
|
||||
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
|
||||
}}
|
||||
onClickTransfer={(assetId) => {
|
||||
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
|
||||
@@ -73,7 +68,7 @@ export const AccountsContainer = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const useAccountStore = create<DataGridSlice>()(
|
||||
const useAccountStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_accounts_store',
|
||||
})
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { GridSettings } from '../grid-settings/grid-settings';
|
||||
import { useAccountStore } from './accounts-container';
|
||||
|
||||
export const AccountsSettings = () => (
|
||||
<GridSettings
|
||||
updateGridStore={useAccountStore((store) => store.updateGridStore)}
|
||||
/>
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user