Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
261f32aa5b | ||
|
|
053775bef6 | ||
|
|
0660eda334 | ||
|
|
f22a3bc2d2 | ||
|
|
fde77ebccb | ||
|
|
e309669736 | ||
|
|
b1621d1191 | ||
|
|
39907f07db | ||
|
|
3dab5f3d9b | ||
|
|
3cf9ae7582 | ||
|
|
557894e2ef | ||
|
|
baf9875c69 | ||
|
|
51199b02ce | ||
|
|
bb826c88f0 | ||
|
|
0da20b750f | ||
|
|
e16c447564 | ||
|
|
a2170d27d6 | ||
|
|
00d840b7b3 | ||
|
|
2a4a05630f | ||
|
|
97f1f40f2c | ||
|
|
a8e6963521 | ||
|
|
a2bffa1dfd | ||
|
|
9253e8067a | ||
|
|
58972f0a11 | ||
|
|
a3c55fd7c3 | ||
|
|
c63cba1071 | ||
|
|
bc9d87fe30 | ||
|
|
6a21862378 | ||
|
|
0fb4348e9c | ||
|
|
b751bcf17d | ||
|
|
d32f27fcb1 | ||
|
|
d05cd6a2ed | ||
|
|
c003e5fa30 | ||
|
|
f62d3289ab | ||
|
|
a3d3d18c5c | ||
|
|
77ca101781 | ||
|
|
d238c37d0c | ||
|
|
79feb485f6 | ||
|
|
6aa5c3b6e3 | ||
|
|
82abc13fda | ||
|
|
933f07cddf | ||
|
|
c8067669c2 | ||
|
|
78f5a9c520 | ||
|
|
462066959d | ||
|
|
f12f5ab961 | ||
|
|
98b2460d8b | ||
|
|
67c182f3de | ||
|
|
68e0009ab0 | ||
|
|
ad74b12908 | ||
|
|
7fff33e493 | ||
|
|
e7cb2c8276 | ||
|
|
4adaeea40a | ||
|
|
d47d894147 | ||
|
|
515932d401 | ||
|
|
723ff2805d | ||
|
|
424cced4be | ||
|
|
b2c7ee82c5 | ||
|
|
4cc63be4f9 | ||
|
|
9fbb7a13e6 | ||
|
|
1874095222 | ||
|
|
e459e92127 | ||
|
|
4dd65b4923 | ||
|
|
5d792d2458 | ||
|
|
59b2f75b13 | ||
|
|
95775679ca | ||
|
|
4cffee29f8 | ||
|
|
bd70b0c233 | ||
|
|
145792f216 | ||
|
|
10add7c236 | ||
|
|
362a2031c7 | ||
|
|
5aaeb87059 | ||
|
|
d240dddab5 | ||
|
|
0af1d0ba82 | ||
|
|
665ab6693a | ||
|
|
902ee2696c | ||
|
|
3beeb0140c | ||
|
|
4b208e90bf | ||
|
|
ec837854cf | ||
|
|
8b94a75ba4 | ||
|
|
08c57b6759 | ||
|
|
bb2184498f | ||
|
|
8b07ce3024 | ||
|
|
8a110584dd | ||
|
|
b82615a3d8 | ||
|
|
f178b85846 | ||
|
|
0796f2b31f | ||
|
|
67be224138 | ||
|
|
51c426ef4b | ||
|
|
fa28d31ef3 | ||
|
|
201a586b05 | ||
|
|
7d96d9bcd1 | ||
|
|
345be81142 | ||
|
|
2221e9faca |
@@ -1,3 +1,4 @@
|
||||
* text eol=lf
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.woff2 binary
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
create-docker-image:
|
||||
name: Create docker image for console-test
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# check-out frontend-monorepo
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
name: run-tests
|
||||
runs-on: 8-cores
|
||||
needs: [create-docker-image, console-test-branch]
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# load docker image
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
# run tests
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 1 --dist loadfile --durations=45
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# upload traces
|
||||
@@ -227,3 +227,46 @@ jobs:
|
||||
name: worker-logs
|
||||
path: ./logs/
|
||||
retention-days: 15
|
||||
#----------------------------------------------
|
||||
# ----- upload market-sim logs -----
|
||||
#----------------------------------------------
|
||||
- name: Prepare and Zip market-sim-logs
|
||||
if: always()
|
||||
run: |
|
||||
parent_dir="/tmp/market-sim-logs"
|
||||
echo "Creating parent directory at $parent_dir"
|
||||
mkdir -p "$parent_dir"
|
||||
|
||||
echo "Waiting for vega-sim-* folders to be created..."
|
||||
sleep 10 # Waits 10 seconds to ensure all folders are created
|
||||
|
||||
echo "Before searching for vega-sim-* folders in /tmp..."
|
||||
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
|
||||
|
||||
echo "After searching for vega-sim-* folders in /tmp..."
|
||||
|
||||
if [ -z "$folders" ]; then
|
||||
echo "No vega-sim-* folders found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Moving vega-sim-* folders to $parent_dir"
|
||||
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
|
||||
|
||||
echo "Checking if $parent_dir is not empty..."
|
||||
if [ "$(ls -A $parent_dir)" ]; then
|
||||
echo "Zipping the parent directory..."
|
||||
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
|
||||
else
|
||||
echo "$parent_dir is empty. No zip file created."
|
||||
exit 0
|
||||
fi
|
||||
shell: /usr/bin/bash -e {0}
|
||||
|
||||
- name: Upload market-sim-logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: market-sim-logs
|
||||
path: market-sim-logs.zip
|
||||
retention-days: 15
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/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
|
||||
yarn nx format:check
|
||||
# Lint all staged files - this brings more value as pre-commit
|
||||
# yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
# yarn nx affected -t test --exclude trading
|
||||
|
||||
@@ -44,7 +44,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.getByTestId('icon-cross').click();
|
||||
});
|
||||
|
||||
it.skip('Proposal page displayed on mobile', function () {
|
||||
it('Proposal page displayed on mobile', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
@@ -55,7 +55,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view new asset proposal', function () {
|
||||
it.skip('Able to view new asset proposal', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
const newAssetProposalBody = getNewAssetTxBody();
|
||||
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
||||
|
||||
@@ -12,7 +12,8 @@ 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 { router } from './routes/router-config';
|
||||
import { useRouterConfig } from './routes/router-config';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@@ -38,7 +39,10 @@ function App() {
|
||||
}
|
||||
>
|
||||
<Suspense fallback={splashLoading}>
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
<RouterProvider
|
||||
router={createBrowserRouter(useRouterConfig())}
|
||||
fallbackElement={splashLoading}
|
||||
/>
|
||||
</Suspense>
|
||||
</NodeGuard>
|
||||
<NodeSwitcherDialog
|
||||
|
||||
@@ -24,7 +24,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef<AgGridReact>(null);
|
||||
const showColumnsOnDesktop = () => {
|
||||
ref.current?.columnApi.setColumnsVisible(
|
||||
ref.current?.api.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 { routerConfig } from '../../routes/router-config';
|
||||
import { useRouterConfig } from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
@@ -26,6 +26,7 @@ const routeToNavigationItem = (r: Navigable) => (
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const routerConfig = useRouterConfig();
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const pages = routerConfig[0].children || [];
|
||||
const mainItems = compact(
|
||||
|
||||
@@ -60,7 +60,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="whitespace-nowrap">
|
||||
<span>
|
||||
{useName && <Icon size={4} name="cube" className="mr-2" />}
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
['id', 'state', 'asset'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
BORDER_COLOURS,
|
||||
NestedDataList,
|
||||
sortNestedDataByChildren,
|
||||
} from './nested-data-list';
|
||||
import { NestedDataList, sortNestedDataByChildren } from './nested-data-list';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const mockData = {
|
||||
@@ -61,38 +57,6 @@ describe('NestedDataList', () => {
|
||||
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
|
||||
});
|
||||
|
||||
it('should repeat the border colours in the correct order', () => {
|
||||
const colourMockData = {
|
||||
t0: {
|
||||
t1: {
|
||||
t2: {
|
||||
t3: {
|
||||
t4: {
|
||||
t5: {
|
||||
t6: {
|
||||
t7: {
|
||||
t8: {
|
||||
hello: 'world',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const tree = render(<NestedDataList data={colourMockData} />);
|
||||
const { getByTestId } = tree;
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const item = getByTestId(`T${i}`);
|
||||
const expected = BORDER_COLOURS.light[i % 5];
|
||||
expect(item.style.borderColor.toUpperCase()).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should sort the data by values with children', () => {
|
||||
const mockData = {
|
||||
nonce: '5980890939790185837',
|
||||
|
||||
@@ -60,7 +60,7 @@ const DeterministicOrderDetails = ({
|
||||
const o = data.orderByID;
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="mb-0">
|
||||
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
|
||||
@@ -89,9 +89,9 @@ const DeterministicOrderDetails = ({
|
||||
<span>{t('Reference')}</span>: {o.reference}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Status')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||
@@ -99,15 +99,17 @@ const DeterministicOrderDetails = ({
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Size')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<SizeInMarket size={o.size} marketId={o.market.id} />
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Version')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
@@ -115,8 +117,8 @@ const DeterministicOrderDetails = ({
|
||||
</h5>
|
||||
</div>
|
||||
{o.type ? (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<div className="mb-6 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
|
||||
{t('Type')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
|
||||
@@ -44,11 +44,11 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
gridRef.current?.api.setColumnsVisible(
|
||||
['voting', 'cDate', 'eDate', 'type'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
gridRef.current?.columnApi.setColumnWidth(
|
||||
gridRef.current?.api.setColumnWidth(
|
||||
'actions',
|
||||
window.innerWidth > BREAKPOINT_MD ? 221 : 80
|
||||
);
|
||||
|
||||
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
|
||||
|
||||
return (
|
||||
<div className="inline-flex border rounded signature-component relative pr-[20px]">
|
||||
<span
|
||||
<div
|
||||
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
|
||||
title={`Version ${signature.version}`}
|
||||
title={`${signature.algo}`}
|
||||
>
|
||||
{signature.algo}
|
||||
</span>
|
||||
<span>v{signature.version}</span>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
isOpen
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerReferralCodeOwnerQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerReferralCodeOwnerQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', createdAt: any, updatedAt: any, referrer: string } } | null> } };
|
||||
|
||||
|
||||
export const ExplorerReferralCodeOwnerDocument = gql`
|
||||
query ExplorerReferralCodeOwner($id: ID!) {
|
||||
referralSets(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
createdAt
|
||||
updatedAt
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerReferralCodeOwnerQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerReferralCodeOwnerQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerReferralCodeOwnerQuery` 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 } = useExplorerReferralCodeOwnerQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerReferralCodeOwnerQuery(baseOptions: Apollo.QueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||
}
|
||||
export function useExplorerReferralCodeOwnerLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
|
||||
}
|
||||
export type ExplorerReferralCodeOwnerQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerQuery>;
|
||||
export type ExplorerReferralCodeOwnerLazyQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerLazyQuery>;
|
||||
export type ExplorerReferralCodeOwnerQueryResult = Apollo.QueryResult<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>;
|
||||
@@ -0,0 +1,11 @@
|
||||
query ExplorerReferralCodeOwner($id: ID!) {
|
||||
referralSets(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
createdAt
|
||||
updatedAt
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { ReferralCodeOwner } from './referral-code-owner';
|
||||
import type { ReferralCodeOwnerProps } from './referral-code-owner';
|
||||
import { ExplorerReferralCodeOwnerDocument } from './__generated__/code-owner';
|
||||
const renderComponent = (
|
||||
props: ReferralCodeOwnerProps,
|
||||
mocks: MockedResponse[]
|
||||
) => {
|
||||
return render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MemoryRouter>
|
||||
<ReferralCodeOwner {...props} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('ReferralCodeOwner', () => {
|
||||
it('should render loading state', () => {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
expect(getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error state', async () => {
|
||||
const errorMessage = 'Error fetching referrer: ABC123';
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
error: new Error('nope'),
|
||||
},
|
||||
];
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(errorMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render link to referring party', async () => {
|
||||
const referrerId = 'DEF789';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerReferralCodeOwnerDocument,
|
||||
variables: {
|
||||
id: 'ABC123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
referralSets: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'ReferralSet',
|
||||
referrer: referrerId,
|
||||
createdAt: '123',
|
||||
updatedAt: '456',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(referrerId)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { TableCell } from '../../../table';
|
||||
import { useExplorerReferralCodeOwnerQuery } from './__generated__/code-owner';
|
||||
import { PartyLink } from '../../../links';
|
||||
|
||||
export interface ReferralCodeOwnerProps {
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the owner of a referral code
|
||||
*/
|
||||
export const ReferralCodeOwner = ({ code }: ReferralCodeOwnerProps) => {
|
||||
const { data, error, loading } = useExplorerReferralCodeOwnerQuery({
|
||||
variables: {
|
||||
id: code,
|
||||
},
|
||||
});
|
||||
const referrer = data?.referralSets.edges[0]?.node.referrer || '';
|
||||
return (
|
||||
<TableCell>
|
||||
{loading && 'Loading...'}
|
||||
{error && `Error fetching referrer: ${code}`}
|
||||
{referrer.length > 0 && <PartyLink id={referrer} />}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { ReferralTeam } from './team';
|
||||
import type { CreateReferralSet } from './team';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
describe('ReferralTeam', () => {
|
||||
const team = {
|
||||
name: 'Test Team',
|
||||
teamUrl: 'https://example.com/team',
|
||||
avatarUrl: 'https://example.com/avatar',
|
||||
closed: false,
|
||||
};
|
||||
|
||||
const mockTx: CreateReferralSet = {
|
||||
team,
|
||||
};
|
||||
|
||||
const mockId = '123456';
|
||||
const mockCreator = 'JohnDoe';
|
||||
|
||||
it('should render the team name', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Test Team')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the team ID', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Id')).toBeInTheDocument();
|
||||
expect(getByText(mockId)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the creator', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Creator')).toBeInTheDocument();
|
||||
expect(getByText(mockCreator)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the team URL', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Team URL')).toBeInTheDocument();
|
||||
expect(getByText(team.teamUrl)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the avatar URL', () => {
|
||||
const { getByText } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByText('Avatar')).toBeInTheDocument();
|
||||
expect(getByText(team.avatarUrl)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the open status as a tick if closed is falsy', () => {
|
||||
const { getByTestId } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByTestId('open-yes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the open status as a cross if it is truthy', () => {
|
||||
const m = {
|
||||
team: {
|
||||
closed: true,
|
||||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<MockedProvider>
|
||||
<ReferralTeam tx={m} id={mockId} creator={mockCreator} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(getByTestId('open-no')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
VegaIcon,
|
||||
Icon,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import Hash from '../../../links/hash';
|
||||
import { t } from 'i18next';
|
||||
import { PartyLink } from '../../../links';
|
||||
|
||||
export type CreateReferralSet = components['schemas']['v1CreateReferralSet'];
|
||||
export type ReferralTeam = CreateReferralSet['team'];
|
||||
|
||||
export interface ReferralTeamProps {
|
||||
tx: CreateReferralSet;
|
||||
id: string;
|
||||
creator: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the details for a team in a CreateReferralSet or UpdateReferralSet transaction.
|
||||
*
|
||||
* Intentionally does not render the avatar image or link to the team url.
|
||||
*/
|
||||
export const ReferralTeam = ({ tx, id, creator }: ReferralTeamProps) => {
|
||||
if (!tx.team) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="inline-block mr-2 leading-none">
|
||||
<VegaIcon name={VegaIconNames.TEAM} />
|
||||
</div>
|
||||
{tx.team.name && (
|
||||
<h3 className="inline-block leading-loose">{tx.team.name}</h3>
|
||||
)}
|
||||
|
||||
<div className="min-w-fit max-w-2xl block">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
{t('Id')}
|
||||
<Hash text={id} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('Creator')}
|
||||
<PartyLink id={creator} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
{tx.team.teamUrl && (
|
||||
<KeyValueTableRow>
|
||||
{t('Team URL')}
|
||||
<Hash text={tx.team.teamUrl} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{tx.team.avatarUrl && (
|
||||
<KeyValueTableRow>
|
||||
{t('Avatar')}
|
||||
<Hash text={tx.team.avatarUrl} truncate={false} />
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow>
|
||||
{t('Open')}
|
||||
<span data-testid={!tx.team.closed ? 'open-yes' : 'open-no'}>
|
||||
{!tx.team.closed ? <Icon name="tick" /> : <Icon name="cross" />}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -27,7 +27,8 @@ export const sharedHeaderProps = {
|
||||
className: 'align-top',
|
||||
};
|
||||
|
||||
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||
// The incoming type field is usually the right thing to show. Exceptions are listed here
|
||||
const LabelOverrides: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||
'Stop Orders Submission': 'Stop Order',
|
||||
'Stop Orders Cancellation': 'Cancel Stop Order',
|
||||
};
|
||||
@@ -50,7 +51,7 @@ export const TxDetailsShared = ({
|
||||
const time: string = blockData?.result.block.header.time || '';
|
||||
const height: string = blockData?.result.block.header.height || txData.block;
|
||||
|
||||
const type = Labels[txData.type] || txData.type;
|
||||
const type = LabelOverrides[txData.type] || txData.type;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||
|
||||
interface TxDetailsApplyReferralCodeProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsApplyReferralCode = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsApplyReferralCodeProps) => {
|
||||
if (!txData || !txData.command.applyReferralCode || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const referralCode = txData.command.applyReferralCode.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Applied Code')}</TableCell>
|
||||
<TableCell>{referralCode}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Referrer')}</TableCell>
|
||||
<ReferralCodeOwner code={referralCode} />
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import { ReferralTeam } from './referrals/team';
|
||||
|
||||
interface TxDetailsCreateReferralProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsCreateReferralSet = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsCreateReferralProps) => {
|
||||
if (!txData || !txData.command.createReferralSet || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
|
||||
const isTeam = txData.command.createReferralSet.isTeam || false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||
<TableCell>{id}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
<ReferralTeam
|
||||
tx={txData.command.createReferralSet}
|
||||
id={id}
|
||||
creator={txData.submitter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,6 +28,10 @@ import { TxProposal } from './tx-proposal';
|
||||
import { TxDetailsTransfer } from './tx-transfer';
|
||||
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
|
||||
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||
import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
|
||||
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
|
||||
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
||||
import { TxDetailsJoinTeam } from './tx-join-team';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -121,6 +125,14 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsStopOrderSubmission;
|
||||
case 'Transfer Funds':
|
||||
return TxDetailsTransfer;
|
||||
case 'Create Referral Set':
|
||||
return TxDetailsCreateReferralSet;
|
||||
case 'Update Referral Set':
|
||||
return TxDetailsUpdateReferralSet;
|
||||
case 'Apply Referral Code':
|
||||
return TxDetailsApplyReferralCode;
|
||||
case 'Join Team':
|
||||
return TxDetailsJoinTeam;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { ReferralCodeOwner } from './referrals/referral-code-owner';
|
||||
|
||||
interface TxDetailsJoinTeamProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature can be turned in to an id with txSignatureToDeterministicId but
|
||||
*/
|
||||
export const TxDetailsJoinTeam = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsJoinTeamProps) => {
|
||||
if (!txData || !txData.command.joinTeam || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const team = txData.command.joinTeam.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Team')}</TableCell>
|
||||
<TableCell>{team}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Referrer')}</TableCell>
|
||||
<ReferralCodeOwner code={team} />
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import { ReferralTeam } from './referrals/team';
|
||||
|
||||
interface TxDetailsUpdateReferralProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A copy of create referral set, effectively.
|
||||
* Updating a referral set without a team doesn't make sense,
|
||||
* but is valid, so this component ignores sense.
|
||||
*/
|
||||
export const TxDetailsUpdateReferralSet = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsUpdateReferralProps) => {
|
||||
if (!txData || !txData.command.updateReferralSet || !txData.signature) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const id = txSignatureToDeterministicId(txData.signature.value);
|
||||
|
||||
const isTeam = txData.command.updateReferralSet.isTeam || false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
|
||||
<TableCell>{id}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
<ReferralTeam
|
||||
tx={txData.command.updateReferralSet}
|
||||
id={id}
|
||||
creator={txData.submitter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,6 +28,7 @@ export type FilterOption =
|
||||
| 'Delegate'
|
||||
| 'Ethereum Key Rotate Submission'
|
||||
| 'Issue Signatures'
|
||||
| 'Join Team'
|
||||
| 'Key Rotate Submission'
|
||||
| 'Liquidity Provision Order'
|
||||
| 'Node Signature'
|
||||
@@ -47,45 +48,46 @@ export type FilterOption =
|
||||
| 'Vote on Proposal'
|
||||
| 'Withdraw';
|
||||
|
||||
// Alphabetised list of transaction types to appear at the top level
|
||||
export const PrimaryFilterOptions: FilterOption[] = [
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Order',
|
||||
'Cancel Transfer Funds',
|
||||
'Delegate',
|
||||
'Liquidity Provision Order',
|
||||
'Proposal',
|
||||
'Stop Orders Submission',
|
||||
'Stop Orders Cancellation',
|
||||
'Submit Oracle Data',
|
||||
'Submit Order',
|
||||
'Transfer Funds',
|
||||
'Undelegate',
|
||||
'Vote on Proposal',
|
||||
'Withdraw',
|
||||
];
|
||||
export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Market Instructions': [
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Order',
|
||||
'Liquidity Provision Order',
|
||||
'Stop Orders Submission',
|
||||
'Stop Orders Cancellation',
|
||||
'Submit Order',
|
||||
],
|
||||
'Transfers and Withdrawals': [
|
||||
'Transfer Funds',
|
||||
'Cancel Transfer Funds',
|
||||
'Withdraw',
|
||||
],
|
||||
Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
|
||||
Referrals: [
|
||||
'Apply Referral Code',
|
||||
'Create Referral Set',
|
||||
'Join Team',
|
||||
'Update Referral Set',
|
||||
],
|
||||
'External Data': ['Chain Event', 'Submit Oracle Data'],
|
||||
Validators: [
|
||||
'Ethereum Key Rotate Submission',
|
||||
'Issue Signatures',
|
||||
'Key Rotate Submission',
|
||||
'Node Signature',
|
||||
'Node Vote',
|
||||
'Protocol Upgrade',
|
||||
'Register new Node',
|
||||
'State Variable Proposal',
|
||||
'Validator Heartbeat',
|
||||
],
|
||||
};
|
||||
|
||||
// Alphabetised list of transaction types to nest under a 'More...' submenu
|
||||
export const SecondaryFilterOptions: FilterOption[] = [
|
||||
'Chain Event',
|
||||
'Ethereum Key Rotate Submission',
|
||||
'Issue Signatures',
|
||||
'Key Rotate Submission',
|
||||
'Node Signature',
|
||||
'Node Vote',
|
||||
'Protocol Upgrade',
|
||||
'Register new Node',
|
||||
'State Variable Proposal',
|
||||
'Validator Heartbeat',
|
||||
];
|
||||
|
||||
export const AllFilterOptions: FilterOption[] = [
|
||||
...PrimaryFilterOptions,
|
||||
...SecondaryFilterOptions,
|
||||
];
|
||||
export const AllFilterOptions: FilterOption[] =
|
||||
Object.values(filterOptions).flat();
|
||||
|
||||
export interface TxFilterProps {
|
||||
filters: Set<FilterOption>;
|
||||
@@ -122,46 +124,33 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{PrimaryFilterOptions.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={() => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
|
||||
{Object.entries(filterOptions).map(([key, value]) => (
|
||||
<DropdownMenuSub key={key}>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t(key)}
|
||||
<Icon name="chevron-right" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{value.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={(checked) => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" className="inline" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
))}
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t('More Types')}
|
||||
<Icon name="chevron-right" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{SecondaryFilterOptions.map((f) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={f}
|
||||
checked={filters.has(f)}
|
||||
onCheckedChange={(checked) => {
|
||||
// NOTE: These act like radio buttons until the API supports multiple filters
|
||||
setFilters(new Set([f]));
|
||||
}}
|
||||
id={`radio-${f}`}
|
||||
>
|
||||
{f}
|
||||
<DropdownMenuItemIndicator>
|
||||
<Icon name="tick-circle" className="inline" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,8 @@ const displayString: StringMap = {
|
||||
StopOrdersSubmission: 'Stop',
|
||||
StopOrdersCancellation: 'Cancel stop',
|
||||
'Stop Orders Cancellation': 'Cancel stop',
|
||||
'Apply Referral Code': 'Referral',
|
||||
'Create Referral Set': 'Create referral',
|
||||
};
|
||||
|
||||
export function getLabelForStopOrderType(
|
||||
|
||||
@@ -54,7 +54,7 @@ const Block = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{blockData && (
|
||||
{blockData && 'result' in blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -73,7 +73,7 @@ export const Layout = () => {
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
</div>
|
||||
<div className={fixedWidthClasses}>
|
||||
<main className="p-4">
|
||||
<main className="md:p-4">
|
||||
{!isHome && <BreadcrumbsContainer className="mb-4" />}
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
@@ -18,7 +18,6 @@ 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';
|
||||
@@ -29,7 +28,7 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||
import { Disclaimer } from './pages/disclaimer';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
|
||||
export type Navigable = {
|
||||
@@ -60,311 +59,315 @@ type Route = RouteItem & {
|
||||
children?: RouteItem[];
|
||||
};
|
||||
|
||||
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 />,
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
export const useRouterConfig = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
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>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Parties />,
|
||||
},
|
||||
{
|
||||
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>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 assetsRoutes: Route[] = FLAGS.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 />,
|
||||
]
|
||||
: [];
|
||||
|
||||
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 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
|
||||
? [
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
]
|
||||
: [];
|
||||
|
||||
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 />,
|
||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
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.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>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
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: <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>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
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>
|
||||
),
|
||||
{
|
||||
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>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
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>,
|
||||
{
|
||||
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,
|
||||
],
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
{
|
||||
path: Routes.RESTRICTED,
|
||||
element: <RestrictedPage />,
|
||||
handle: {
|
||||
name: t('Restricted'),
|
||||
text: t('Restricted'),
|
||||
},
|
||||
{
|
||||
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>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
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'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter(routerConfig);
|
||||
];
|
||||
return routerConfig;
|
||||
};
|
||||
|
||||
@@ -31,6 +31,10 @@ const Tx = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!data || !data?.transaction) {
|
||||
errorMessage = 'Transaction not found';
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<PageHeader
|
||||
@@ -49,7 +53,7 @@ const Tx = () => {
|
||||
<TxDetails
|
||||
className="mb-28"
|
||||
txData={data?.transaction}
|
||||
pubKey={data?.transaction.submitter}
|
||||
pubKey={data?.transaction?.submitter}
|
||||
/>
|
||||
</RenderFetched>
|
||||
</section>
|
||||
|
||||
@@ -11,8 +11,8 @@ interface TxDetailsProps {
|
||||
export const txDetailsTruncateLength = 30;
|
||||
|
||||
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!txData || !pubKey) {
|
||||
return <>{t('Transaction could not be found')}</>;
|
||||
}
|
||||
return (
|
||||
<section className="mb-10" key={txData.hash}>
|
||||
|
||||
+2
-2
@@ -8,13 +8,13 @@ type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
|
||||
type XOR<T, U> = T | U extends object
|
||||
? (Without<T, U> & U) | (Without<U, T> & T)
|
||||
: T | U;
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
type OneOf<T extends any[]> = T extends [infer Only]
|
||||
? Only
|
||||
: T extends [infer A, infer B, ...infer Rest]
|
||||
? OneOf<[XOR<A, B>, ...Rest]>
|
||||
: never;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface paths {
|
||||
'/info': {
|
||||
|
||||
@@ -47,7 +47,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
.and('contain.text', 'USDC (fake)');
|
||||
});
|
||||
|
||||
it('Unable to submit proposal with public key', function () {
|
||||
it.skip('Unable to submit proposal with public key', function () {
|
||||
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
|
||||
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
@@ -55,7 +55,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
cy.getByTestId('dialog-content')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h1').should('have.text', 'Transaction failed');
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'have.text',
|
||||
'Transaction failed'
|
||||
);
|
||||
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,6 +112,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
||||
performanceHysteresisEpochs: 2,
|
||||
slaCompetitionFactor: '0.1',
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
quadraticSlippageFactor: '0',
|
||||
instrument: {
|
||||
name: 'Token test market',
|
||||
@@ -196,6 +197,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
||||
timeWindow: '3600',
|
||||
scalingFactor: 10,
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
triggeringRatio: '0.7',
|
||||
auctionExtension: '1',
|
||||
},
|
||||
@@ -240,6 +242,7 @@ export function createSuccessorMarketProposalTxBody(
|
||||
decimalPlaces: '5',
|
||||
positionDecimalPlaces: '5',
|
||||
linearSlippageFactor: '0.001',
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
quadraticSlippageFactor: '0',
|
||||
liquiditySlaParameters: {
|
||||
priceRange: '0.5',
|
||||
@@ -334,6 +337,7 @@ export function createSuccessorMarketProposalTxBody(
|
||||
timeWindow: '3600',
|
||||
scalingFactor: 10,
|
||||
},
|
||||
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
|
||||
triggeringRatio: '0.7',
|
||||
auctionExtension: '1',
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
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=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
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=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
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=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
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 { FLAGS, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React, { Suspense } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,21 +15,23 @@ import {
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
import { useConnectors } from './lib/vega-connectors';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const useVegaWalletEagerConnect = () => {
|
||||
const vegaConnecting = useEagerConnect(Connectors);
|
||||
const connectors = useConnectors();
|
||||
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();
|
||||
@@ -79,10 +81,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (!featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
run();
|
||||
}
|
||||
}, [token, appDispatch, staking, vesting]);
|
||||
}, [
|
||||
token,
|
||||
appDispatch,
|
||||
staking,
|
||||
vesting,
|
||||
featureFlags.GOVERNANCE_NETWORK_DOWN,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (account && pubKey) {
|
||||
@@ -147,16 +155,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 (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
|
||||
if (featureFlags.GOVERNANCE_NETWORK_LIMITS) {
|
||||
getNetworkLimits();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopPoll();
|
||||
};
|
||||
}, [appDispatch, VEGA_URL, t]);
|
||||
}, [appDispatch, VEGA_URL, t, featureFlags.GOVERNANCE_NETWORK_LIMITS]);
|
||||
|
||||
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||
if (featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashError />
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
@@ -96,7 +96,9 @@ const cache: InMemoryCacheConfig = {
|
||||
const Web3Container = ({
|
||||
chainId,
|
||||
}: {
|
||||
/** Ethereum chain id */
|
||||
chainId: number;
|
||||
/** Ethereum provider url */
|
||||
providerUrl: string;
|
||||
}) => {
|
||||
const InitializeHandlers = () => {
|
||||
@@ -123,6 +125,9 @@ const Web3Container = ({
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
const vegaChainId = useChainId(VEGA_URL);
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
@@ -157,7 +162,8 @@ const Web3Container = ({
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!vegaChainId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -169,6 +175,7 @@ const Web3Container = ({
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
chainId: vegaChainId,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
|
||||
@@ -7,16 +7,16 @@ import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { Connectors } from '../../lib/vega-connectors';
|
||||
import { useConnectors } 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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import {
|
||||
PARTY_NOT_FOUND,
|
||||
filterAcceptableGraphqlErrors,
|
||||
isPartyNotFoundError,
|
||||
} from './party';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param message
|
||||
* @returns GraphQLError
|
||||
*/
|
||||
function createMockApolloErrors(message: string): GraphQLError {
|
||||
return {
|
||||
message,
|
||||
extensions: {
|
||||
code: message.toUpperCase().replace(/ /g, '_'),
|
||||
},
|
||||
locations: [],
|
||||
originalError: new Error(message),
|
||||
path: [],
|
||||
nodes: [],
|
||||
positions: [1],
|
||||
name: message,
|
||||
source: {
|
||||
body: message,
|
||||
name: message,
|
||||
locationOffset: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('filterAcceptableGraphqlErrors', () => {
|
||||
it('should return undefined if the error is a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('failed to get party for ID')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the error if it is not a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('Some other error')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are multiple errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [
|
||||
createMockApolloErrors('failed to get party for ID'),
|
||||
createMockApolloErrors('Some other error'),
|
||||
],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are no errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined if the error is undefined', () => {
|
||||
const result = filterAcceptableGraphqlErrors(undefined);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPartyNotFoundError', () => {
|
||||
it('should return true if the error message includes PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'failed to get party for ID' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the error message does not include PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'Some other error' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
// Will trip if the error message changes, which should not be a problem, but there
|
||||
// might be logic that depends on it
|
||||
it('expects party not found error to remain consistent', () => {
|
||||
const error = 'failed to get party for ID';
|
||||
|
||||
expect(PARTY_NOT_FOUND).toStrictEqual(error);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
|
||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||
|
||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
@@ -6,3 +8,23 @@ export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* If a party has no accounts or data, then this GraphQL query believes it does not exist
|
||||
* Not having any rewards is a valid state, so in some cases we can filter this error out.
|
||||
*
|
||||
* @param error ApolloError | undefined
|
||||
* @returns ApolloError | undefined
|
||||
*/
|
||||
export function filterAcceptableGraphqlErrors(
|
||||
error?: ApolloError
|
||||
): ApolloError | undefined {
|
||||
// Currently the only error we expect is when a party has no accounts
|
||||
if (error && error.graphQLErrors.length === 1) {
|
||||
if (isPartyNotFoundError(error.graphQLErrors[0])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
@@ -13,13 +14,17 @@ export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
export const useConnectors = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
return useMemo(
|
||||
() => ({
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
||||
}),
|
||||
[featureFlags.METAMASK_SNAPS]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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, FLAGS } from '@vegaprotocol/environment';
|
||||
import { ExternalLinks, useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
@@ -175,6 +175,7 @@ export const ValidatorDetailsLink = ({
|
||||
};
|
||||
|
||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -186,9 +187,9 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+3
-4
@@ -19,7 +19,7 @@ import {
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||
@@ -62,8 +62,7 @@ describe('Proposal header', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('Renders New market proposal', () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = true;
|
||||
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
rationale: {
|
||||
@@ -125,7 +124,7 @@ describe('Proposal header', () => {
|
||||
screen.queryByTestId('proposal-description')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'Market change: MarketId'
|
||||
'Update to market ID: MarketId'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+50
-10
@@ -1,5 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Lozenge, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Lozenge,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { shorten } from '@vegaprotocol/utils';
|
||||
import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -12,7 +18,12 @@ import {
|
||||
useNewTransferProposalDetails,
|
||||
useSuccessorMarketProposalDetails,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
CONSOLE_MARKET_PAGE,
|
||||
DApp,
|
||||
useFeatureFlags,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { VoteState } from '../vote-details/use-user-vote';
|
||||
@@ -28,9 +39,12 @@ export const ProposalHeader = ({
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const { t } = useTranslation();
|
||||
const change = proposal?.terms.change;
|
||||
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
let details: ReactNode;
|
||||
let proposalType = '';
|
||||
let fallbackTitle = '';
|
||||
@@ -54,13 +68,14 @@ export const ProposalHeader = ({
|
||||
switch (change?.__typename) {
|
||||
case 'NewMarket': {
|
||||
proposalType =
|
||||
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
|
||||
featureFlags.PRODUCT_PERPETUALS &&
|
||||
change?.instrument?.product?.__typename
|
||||
? `NewMarket${change?.instrument?.product?.__typename}`
|
||||
: 'NewMarket';
|
||||
fallbackTitle = t('NewMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
{featureFlags.SUCCESSOR_MARKETS && (
|
||||
<SuccessorCode proposalId={proposal?.id} />
|
||||
)}
|
||||
<span>
|
||||
@@ -82,13 +97,13 @@ export const ProposalHeader = ({
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
proposalType =
|
||||
FLAGS.UPDATE_MARKET_STATE && change?.updateType
|
||||
featureFlags.UPDATE_MARKET_STATE && change?.updateType
|
||||
? t(change.updateType)
|
||||
: 'UpdateMarketState';
|
||||
fallbackTitle = t('UpdateMarketStateProposal');
|
||||
details = (
|
||||
<span>
|
||||
{FLAGS.UPDATE_MARKET_STATE &&
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
change?.market?.id &&
|
||||
change.updateType ? (
|
||||
<>
|
||||
@@ -104,8 +119,33 @@ export const ProposalHeader = ({
|
||||
fallbackTitle = t('UpdateMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
<span>{t('MarketChange')}:</span>{' '}
|
||||
<span>{truncateMiddle(change.marketId)}</span>
|
||||
<span>{t('UpdateToMarket')}:</span>{' '}
|
||||
<span className="inline-flex items-start gap-2">
|
||||
<span className="break-all">{change.marketId} </span>
|
||||
<span className="inline-flex items-end gap-0">
|
||||
<CopyWithTooltip
|
||||
text={change.marketId}
|
||||
description={t('copyToClipboard')}
|
||||
>
|
||||
<button className="inline-block px-1">
|
||||
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<Tooltip description={t('OpenInConsole')} align="center">
|
||||
<button
|
||||
className="inline-block px-1"
|
||||
onClick={() => {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(':marketId', change.marketId)
|
||||
);
|
||||
window.open(marketPageLink, '_blank');
|
||||
}}
|
||||
>
|
||||
<VegaIcon size={20} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
@@ -177,14 +217,14 @@ export const ProposalHeader = ({
|
||||
case 'NewTransfer':
|
||||
proposalType = 'NewTransfer';
|
||||
fallbackTitle = t('NewTransferProposal');
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<NewTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
case 'CancelTransfer':
|
||||
proposalType = 'CancelTransfer';
|
||||
fallbackTitle = t('CancelTransferProposal');
|
||||
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
||||
<CancelTransferSummary proposalId={proposal?.id} />
|
||||
) : null;
|
||||
break;
|
||||
|
||||
@@ -48,6 +48,7 @@ const vegaWalletConfig: VegaWalletConfig = {
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
chainId: 'VEGA_CHAIN_ID',
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
ProposalCancelTransferDetails,
|
||||
ProposalTransferDetails,
|
||||
} from '../proposal-transfer';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
|
||||
export interface ProposalProps {
|
||||
@@ -53,6 +53,7 @@ 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);
|
||||
@@ -132,7 +133,7 @@ export const Proposal = ({
|
||||
}
|
||||
|
||||
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
||||
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
|
||||
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
|
||||
<>
|
||||
{proposal.terms.change.__typename === 'NewTransfer' && (
|
||||
/** Governance New Transfer Details */
|
||||
|
||||
+2
-2
@@ -281,8 +281,8 @@ describe('VoteBreakdown', () => {
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP majority', () => {
|
||||
const yesVotesLP = 800;
|
||||
const noVotesLP = 200;
|
||||
const yesVotesLP = 0.8;
|
||||
const noVotesLP = 0.2;
|
||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||
|
||||
renderComponent(
|
||||
|
||||
@@ -105,8 +105,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
noEquityLikeShareWeight,
|
||||
totalEquityLikeShareWeight,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
@@ -135,6 +133,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
.multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
);
|
||||
|
||||
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
@@ -202,50 +201,24 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
yesEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={yesEquityLikeShareWeight} />
|
||||
</button>
|
||||
<button>{yesLPPercentage.toFixed(1)}%</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(0)}%</button>
|
||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,13 +255,8 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
<CompactVotes number={totalEquityLikeShareWeight} />
|
||||
</button>
|
||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
||||
</Tooltip>
|
||||
<span>
|
||||
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
|
||||
interface VoteButtonsContainerProps {
|
||||
voteState: VoteState | null;
|
||||
@@ -42,8 +43,10 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||
<VoteButtons
|
||||
{...props}
|
||||
currentStakeAvailable={toBigNum(
|
||||
|
||||
@@ -54,8 +54,8 @@ describe('use-vote-information', () => {
|
||||
it('returns all required vote information', () => {
|
||||
const yesVotes = 40;
|
||||
const noVotes = 60;
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '70';
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.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 = 20;
|
||||
const noVotes = 70;
|
||||
const yesEquityLikeShareWeight = '30';
|
||||
const noEquityLikeShareWeight = '60';
|
||||
const yesVotes = 0.2;
|
||||
const noVotes = 0.7;
|
||||
const yesEquityLikeShareWeight = '0.30';
|
||||
const noEquityLikeShareWeight = '0.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);
|
||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
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);
|
||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
@@ -81,12 +81,7 @@ export const useVoteInformation = ({
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
|
||||
const yesLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalEquityLikeShareWeight);
|
||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
||||
|
||||
const noPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
@@ -103,9 +98,7 @@ export const useVoteInformation = ({
|
||||
);
|
||||
|
||||
const participationLPMet = requiredParticipationLP
|
||||
? totalEquityLikeShareWeight.isGreaterThan(
|
||||
totalSupply.multipliedBy(requiredParticipationLP)
|
||||
)
|
||||
? totalEquityLikeShareWeight.isGreaterThan(requiredParticipationLP)
|
||||
: false;
|
||||
|
||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||
@@ -120,9 +113,7 @@ export const useVoteInformation = ({
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight
|
||||
.multipliedBy(100)
|
||||
.dividedBy(totalSupply);
|
||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
||||
|
||||
const willPassByTokenVote =
|
||||
participationMet &&
|
||||
|
||||
@@ -205,7 +205,6 @@ query Proposal(
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
triggeringRatio
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
@@ -213,7 +212,6 @@ query Proposal(
|
||||
}
|
||||
positionDecimalPlaces
|
||||
linearSlippageFactor
|
||||
quadraticSlippageFactor
|
||||
}
|
||||
... on UpdateMarket {
|
||||
marketId
|
||||
@@ -366,7 +364,6 @@ query Proposal(
|
||||
}
|
||||
}
|
||||
liquidityMonitoringParameters {
|
||||
triggeringRatio
|
||||
targetStakeParameters {
|
||||
timeWindow
|
||||
scalingFactor
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -15,10 +15,11 @@ import {
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||
|
||||
export const ProposalContainer = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const [
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
setMostRecentlyEnactedAssociatedMarketProposal,
|
||||
@@ -59,9 +60,9 @@ export const ProposalContainer = () => {
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
proposalId: params.proposalId || '',
|
||||
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||
includeNewMarketProductField: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!featureFlags.REFERRALS,
|
||||
},
|
||||
skip: !params.proposalId,
|
||||
});
|
||||
@@ -120,7 +121,7 @@ export const ProposalContainer = () => {
|
||||
variables: {
|
||||
marketId: marketData?.id || '',
|
||||
},
|
||||
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
||||
skip: !featureFlags.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -133,7 +134,7 @@ export const ProposalContainer = () => {
|
||||
variables: {
|
||||
marketId: parentMarketId?.market?.parentMarketID || '',
|
||||
skip:
|
||||
!FLAGS.SUCCESSOR_MARKETS ||
|
||||
!featureFlags.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 { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
|
||||
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
|
||||
return flow([
|
||||
@@ -43,15 +43,16 @@ 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: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ export const ProtocolUpgradeProposalContainer = () => {
|
||||
time={
|
||||
pending && time ? (
|
||||
convertToCountdownString(time, '0:00:00:00')
|
||||
) : blockInfo?.result ? (
|
||||
) : blockInfo && 'result' in blockInfo && 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 { FLAGS } from '@vegaprotocol/environment';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
|
||||
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
||||
orderBy(
|
||||
@@ -33,15 +33,16 @@ 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: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -116,7 +116,8 @@ export const generateYesVotes = (
|
||||
fixedTokenValue?: number,
|
||||
totalEquityLikeShareWeight?: string
|
||||
): Votes => {
|
||||
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||
const votes = [];
|
||||
for (let i = 0; i < numberOfVotes; i++) {
|
||||
const vote: Vote = {
|
||||
__typename: 'Vote',
|
||||
value: Schema.VoteValue.VALUE_YES,
|
||||
@@ -152,8 +153,9 @@ export const generateYesVotes = (
|
||||
datetime: faker.date.past().toISOString(),
|
||||
};
|
||||
|
||||
return vote;
|
||||
});
|
||||
votes.push(vote);
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: votes.length.toString(),
|
||||
@@ -172,7 +174,8 @@ export const generateNoVotes = (
|
||||
fixedTokenValue?: number,
|
||||
totalEquityLikeShareWeight?: string
|
||||
): Votes => {
|
||||
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||
const votes = [];
|
||||
for (let i = 0; i < numberOfVotes; i++) {
|
||||
const vote: Vote = {
|
||||
__typename: 'Vote',
|
||||
value: Schema.VoteValue.VALUE_NO,
|
||||
@@ -207,8 +210,9 @@ export const generateNoVotes = (
|
||||
},
|
||||
datetime: faker.date.past().toISOString(),
|
||||
};
|
||||
return vote;
|
||||
});
|
||||
votes.push(vote);
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: 'ProposalVoteSide',
|
||||
totalNumber: votes.length.toString(),
|
||||
|
||||
+23
-13
@@ -10,6 +10,7 @@ import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../lib/party';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -99,17 +100,24 @@ export const EpochIndividualRewards = ({
|
||||
prevEpochIdRef.current = epochId;
|
||||
}, [epochId, refetchData]);
|
||||
|
||||
// Workarounds for the error handling of AsyncRenderer
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
const filteredData = data || [];
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
error={filteredErrors}
|
||||
data={filteredData}
|
||||
render={() => (
|
||||
<div>
|
||||
<p data-testid="connected-vega-key" className="mb-10">
|
||||
{t('Connected Vega key')}:{' '}
|
||||
<span className="text-white">{pubKey}</span>
|
||||
</p>
|
||||
{epochIndividualRewardSummaries.length === 0 && (
|
||||
<p>{t('No rewards for key')}</p>
|
||||
)}
|
||||
{epochIndividualRewardSummaries.map(
|
||||
(epochIndividualRewardSummary) => (
|
||||
<EpochIndividualRewardsTable
|
||||
@@ -118,17 +126,19 @@ export const EpochIndividualRewards = ({
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Pagination
|
||||
isLoading={loading}
|
||||
hasPrevPage={page > 1}
|
||||
hasNextPage={page < totalPages}
|
||||
onBack={() => refetchData(page - 1)}
|
||||
onNext={() => refetchData(page + 1)}
|
||||
onFirst={() => refetchData(1)}
|
||||
onLast={() => refetchData(totalPages)}
|
||||
>
|
||||
{t('Page')} {page}
|
||||
</Pagination>
|
||||
{epochIndividualRewardSummaries.length > 0 && (
|
||||
<Pagination
|
||||
isLoading={loading}
|
||||
hasPrevPage={page > 1}
|
||||
hasNextPage={page < totalPages}
|
||||
onBack={() => refetchData(page - 1)}
|
||||
onNext={() => refetchData(page + 1)}
|
||||
onFirst={() => refetchData(1)}
|
||||
onLast={() => refetchData(totalPages)}
|
||||
>
|
||||
{t('Page')} {page}
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ import type { ValidatorsView } from './validator-tables';
|
||||
const nodeFactory = (
|
||||
overrides?: PartialDeep<NodesFragmentFragment>
|
||||
): NodesFragmentFragment => {
|
||||
const defaultNode = {
|
||||
const defaultNode: NodesFragmentFragment = {
|
||||
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
|
||||
name: 'high',
|
||||
avatarUrl: 'https://upload.wikimedia.org/wikipedia/en/2/25/Marvin-TV-3.jpg',
|
||||
@@ -288,7 +288,7 @@ describe('Consensus validators table', () => {
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
|
||||
).toHaveTextContent('10.07%');
|
||||
).toHaveTextContent('13.16%');
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
|
||||
|
||||
+11
-2
@@ -185,6 +185,15 @@ export const ConsensusValidatorsTable = ({
|
||||
const { rawValidatorScore: previousEpochValidatorScore } =
|
||||
getLastEpochScoreAndPerformance(previousEpochData, id);
|
||||
|
||||
const overstakingPenalty = calculateOverallPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
const totalPenalty = calculateOverstakedPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
@@ -213,11 +222,11 @@ export const ConsensusValidatorsTable = ({
|
||||
2
|
||||
),
|
||||
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
||||
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
|
||||
overstakingPenalty,
|
||||
2
|
||||
),
|
||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||
calculateOverallPenalty(id, allNodesInPreviousEpoch),
|
||||
totalPenalty,
|
||||
2
|
||||
),
|
||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||
|
||||
+11
-2
@@ -124,6 +124,15 @@ export const StandbyPendingValidatorsTable = ({
|
||||
}
|
||||
}
|
||||
|
||||
const overstakingPenalty = calculateOverallPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
const totalPenalty = calculateOverstakedPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
@@ -154,11 +163,11 @@ export const StandbyPendingValidatorsTable = ({
|
||||
2
|
||||
),
|
||||
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
|
||||
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
|
||||
overstakingPenalty,
|
||||
2
|
||||
),
|
||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||
calculateOverallPenalty(id, allNodesInPreviousEpoch),
|
||||
totalPenalty,
|
||||
2
|
||||
),
|
||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||
|
||||
@@ -266,7 +266,9 @@ export const ValidatorTable = ({
|
||||
|
||||
<Tooltip description={t('OverstakedPenaltyDescription')}>
|
||||
<span data-testid="overstaking-penalty">
|
||||
{formatNumberPercentage(penalties.overstaked, 2)}
|
||||
{penalties.overstaked
|
||||
? formatNumberPercentage(penalties.overstaked, 2)
|
||||
: '-'}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
@@ -285,7 +287,9 @@ export const ValidatorTable = ({
|
||||
</span>
|
||||
<span data-testid="total-penalties">
|
||||
<strong>
|
||||
{formatNumberPercentage(penalties.overall, 2)}
|
||||
{penalties.overall
|
||||
? formatNumberPercentage(penalties.overall, 2)
|
||||
: '-'}
|
||||
</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
|
||||
@@ -3,11 +3,11 @@ import {
|
||||
getLastEpochScoreAndPerformance,
|
||||
getNormalisedVotingPower,
|
||||
getUnnormalisedVotingPower,
|
||||
getOverstakingPenalty,
|
||||
getFormattedPerformanceScore,
|
||||
getPerformancePenalty,
|
||||
getTotalPenalties,
|
||||
getStakePercentage,
|
||||
calculateOverallPenalty,
|
||||
calculateOverstakedPenalty,
|
||||
} from './shared';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
@@ -106,38 +106,6 @@ describe('getUnnormalisedVotingPower', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOverstakingPenalty', () => {
|
||||
it('returns "0%" when both arguments are null or undefined', () => {
|
||||
expect(getOverstakingPenalty(null, null)).toBe('0%');
|
||||
expect(getOverstakingPenalty(undefined, undefined)).toBe('0%');
|
||||
expect(getOverstakingPenalty(null, undefined)).toBe('0%');
|
||||
expect(getOverstakingPenalty(undefined, null)).toBe('0%');
|
||||
});
|
||||
|
||||
it('returns "0%" when one argument is null or undefined', () => {
|
||||
expect(getOverstakingPenalty('10', null)).toBe('0%');
|
||||
expect(getOverstakingPenalty(null, '20')).toBe('0%');
|
||||
expect(getOverstakingPenalty('10', undefined)).toBe('0%');
|
||||
expect(getOverstakingPenalty(undefined, '20')).toBe('0%');
|
||||
});
|
||||
|
||||
it('returns "0%" when validatorScore or stakeScore is zero', () => {
|
||||
expect(getOverstakingPenalty('0', '20')).toBe('0%');
|
||||
expect(getOverstakingPenalty('10', '0')).toBe('0%');
|
||||
});
|
||||
|
||||
it('returns the correct overstaking penalty', () => {
|
||||
expect(getOverstakingPenalty('0.18', '0.2')).toBe('10.00%');
|
||||
expect(getOverstakingPenalty('0.2', '0.2')).toBe('0.00%');
|
||||
expect(getOverstakingPenalty('0.04', '0.2')).toBe('80.00%');
|
||||
});
|
||||
|
||||
it('handles string numbers with decimals', () => {
|
||||
expect(getOverstakingPenalty('7.5', '15')).toBe('50.00%');
|
||||
expect(getOverstakingPenalty('12.5', '25')).toBe('50.00%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFormattedPerformanceScore', () => {
|
||||
it('should return the formatted performance score', () => {
|
||||
expect(getFormattedPerformanceScore('0.25')).toEqual(new BigNumber(0.25));
|
||||
@@ -152,17 +120,6 @@ describe('getPerformancePenalty', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalPenalties', () => {
|
||||
it('should return the total penalties', () => {
|
||||
expect(getTotalPenalties('0.25', '1', '5000', '10000')).toEqual('50.00%');
|
||||
expect(getTotalPenalties('0.25', '0.5', '5000', '10000')).toEqual('75.00%');
|
||||
});
|
||||
|
||||
it('should return 0 if the total penalties is negative', () => {
|
||||
expect(getTotalPenalties('0.25', '0.5', '1000', '10000')).toEqual('0.00%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStakePercentage', () => {
|
||||
it('should return the stake percentage', () => {
|
||||
expect(
|
||||
@@ -182,3 +139,107 @@ describe('getStakePercentage', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateOverallPenalty', () => {
|
||||
it('returns null if rewardScore is null', () => {
|
||||
const res = calculateOverallPenalty('1', [
|
||||
{
|
||||
id: '1',
|
||||
rewardScore: null,
|
||||
stakedTotal: '',
|
||||
rankingScore: {
|
||||
stakeScore: '0.25',
|
||||
performanceScore: '0.75',
|
||||
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
previousStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
rankingScore: '',
|
||||
votingPower: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if rewardScore.rawValidatorScore is null (should not happen)', () => {
|
||||
const res = calculateOverallPenalty('1', [
|
||||
{
|
||||
id: '1',
|
||||
stakedTotal: '',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.25',
|
||||
performanceScore: '0.75',
|
||||
multisigScore: '',
|
||||
validatorScore: null as unknown as string,
|
||||
normalisedScore: '',
|
||||
validatorStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
},
|
||||
rankingScore: {
|
||||
stakeScore: '0.25',
|
||||
performanceScore: '0.75',
|
||||
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
previousStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
rankingScore: '',
|
||||
votingPower: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateOverstakedPenalty', () => {
|
||||
it('returns null if rewardScore is null', () => {
|
||||
const res = calculateOverstakedPenalty('1', [
|
||||
{
|
||||
id: '1',
|
||||
rewardScore: null,
|
||||
stakedTotal: '',
|
||||
rankingScore: {
|
||||
stakeScore: '0.25',
|
||||
performanceScore: '0.75',
|
||||
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
previousStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
rankingScore: '',
|
||||
votingPower: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if rewardScore.rawValidatorScore is null (should not happen)', () => {
|
||||
const res = calculateOverstakedPenalty('1', [
|
||||
{
|
||||
id: '1',
|
||||
stakedTotal: '',
|
||||
rewardScore: {
|
||||
rawValidatorScore: null as unknown as string,
|
||||
performanceScore: '0.75',
|
||||
multisigScore: '',
|
||||
validatorScore: '',
|
||||
normalisedScore: '',
|
||||
validatorStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
},
|
||||
rankingScore: {
|
||||
stakeScore: '0.25',
|
||||
performanceScore: '0.75',
|
||||
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
previousStatus:
|
||||
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
|
||||
rankingScore: '',
|
||||
votingPower: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import type { PreviousEpochQuery } from './__generated__/PreviousEpoch';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import type { LastArrayElement } from 'type-fest';
|
||||
import isNull from 'lodash/isNull';
|
||||
|
||||
type Node = NonNullable<
|
||||
LastArrayElement<
|
||||
@@ -21,7 +22,10 @@ type Node = NonNullable<
|
||||
* @returns Theoretical stake score for given node based on the staked total
|
||||
* of all node of the same type (status)
|
||||
*/
|
||||
const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
|
||||
const calculateTheoreticalStakeScore = (
|
||||
nodeId: string,
|
||||
nodes: Node[]
|
||||
): BigNumber | null => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
if (!node) {
|
||||
return new BigNumber(0);
|
||||
@@ -42,14 +46,25 @@ const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
|
||||
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
||||
* @returns %
|
||||
*/
|
||||
export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
|
||||
export const calculateOverallPenalty = (
|
||||
nodeId: string,
|
||||
nodes: Node[]
|
||||
): BigNumber | null => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
||||
if (!node || tts.isZero()) {
|
||||
if (
|
||||
!node ||
|
||||
isNull(tts) ||
|
||||
!node.rewardScore ||
|
||||
(node.rewardScore && isNull(node.rewardScore.validatorScore))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (tts.isZero()) {
|
||||
return new BigNumber(0);
|
||||
}
|
||||
const penalty = new BigNumber(1)
|
||||
.minus(new BigNumber(node.rewardScore?.validatorScore || 0).dividedBy(tts))
|
||||
.minus(new BigNumber(node.rewardScore.validatorScore).dividedBy(tts))
|
||||
.times(100);
|
||||
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
|
||||
};
|
||||
@@ -60,10 +75,21 @@ export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
|
||||
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
|
||||
* @returns %
|
||||
*/
|
||||
export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
|
||||
export const calculateOverstakedPenalty = (
|
||||
nodeId: string,
|
||||
nodes: Node[]
|
||||
): BigNumber | null => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
|
||||
if (!node || tts.isZero()) {
|
||||
if (
|
||||
!node ||
|
||||
isNull(tts) ||
|
||||
isNull(node.rewardScore) ||
|
||||
(node.rewardScore && node.rewardScore.rawValidatorScore === null)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (tts.isZero()) {
|
||||
return new BigNumber(0);
|
||||
}
|
||||
const penalty = new BigNumber(1)
|
||||
@@ -78,7 +104,9 @@ export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
|
||||
* Calculates performance penalty based on the given performance score.
|
||||
* @returns %
|
||||
*/
|
||||
export const calculatesPerformancePenalty = (performanceScore: string) => {
|
||||
export const calculatesPerformancePenalty = (
|
||||
performanceScore: string
|
||||
): BigNumber => {
|
||||
const penalty = new BigNumber(1)
|
||||
.minus(new BigNumber(performanceScore))
|
||||
.times(100);
|
||||
@@ -123,60 +151,6 @@ export const getPerformancePenalty = (performanceScore?: string) =>
|
||||
2
|
||||
);
|
||||
|
||||
export const getOverstakingPenalty = (
|
||||
validatorScore: string | null | undefined,
|
||||
stakeScore: string | null | undefined
|
||||
) => {
|
||||
if (!validatorScore || !stakeScore) {
|
||||
return '0%';
|
||||
}
|
||||
|
||||
// avoid division by zero
|
||||
if (
|
||||
new BigNumber(validatorScore).isZero() ||
|
||||
new BigNumber(stakeScore).isZero()
|
||||
) {
|
||||
return '0%';
|
||||
}
|
||||
|
||||
return formatNumberPercentage(
|
||||
BigNumber.max(
|
||||
new BigNumber(1)
|
||||
.minus(
|
||||
new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore))
|
||||
)
|
||||
.times(100),
|
||||
new BigNumber(0)
|
||||
),
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
export const getTotalPenalties = (
|
||||
rawValidatorScore: string | null | undefined,
|
||||
performanceScore: string | undefined,
|
||||
stakedOnNode: string,
|
||||
totalStake: string
|
||||
) => {
|
||||
const calc =
|
||||
rawValidatorScore &&
|
||||
performanceScore &&
|
||||
new BigNumber(totalStake).isGreaterThan(0)
|
||||
? new BigNumber(1).minus(
|
||||
new BigNumber(performanceScore)
|
||||
.times(new BigNumber(rawValidatorScore))
|
||||
.dividedBy(
|
||||
new BigNumber(stakedOnNode).dividedBy(new BigNumber(totalStake))
|
||||
)
|
||||
)
|
||||
: new BigNumber(0);
|
||||
|
||||
return formatNumberPercentage(
|
||||
calc.isPositive() ? calc.times(100) : new BigNumber(0),
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
export const getStakePercentage = (total: BigNumber, stakedOnNode: BigNumber) =>
|
||||
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
|
||||
? '0%'
|
||||
|
||||
@@ -1,14 +1,50 @@
|
||||
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Intent,
|
||||
ToastsContainer,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
useToasts,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useEthereumTransactionToasts,
|
||||
useEthereumWithdrawApprovalsToasts,
|
||||
useVegaTransactionToasts,
|
||||
useWalletDisconnectToastActions,
|
||||
useWalletDisconnectedToasts,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const WalletDisconnectAdditionalContent = () => {
|
||||
const { t } = useTranslation();
|
||||
const { hideToast } = useWalletDisconnectToastActions();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
return (
|
||||
<p className="mt-2">
|
||||
<TradingButton
|
||||
data-testid="connect-vega-wallet"
|
||||
onClick={() => {
|
||||
hideToast();
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
size="small"
|
||||
intent={Intent.Danger}
|
||||
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||
>
|
||||
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
||||
</TradingButton>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToastsManager = () => {
|
||||
useVegaTransactionToasts();
|
||||
useEthereumTransactionToasts();
|
||||
useEthereumWithdrawApprovalsToasts();
|
||||
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
|
||||
|
||||
const toasts = useToasts((store) => store.toasts);
|
||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||
|
||||
+8
-1
@@ -21,7 +21,14 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
# NX_DISABLE_CLOSE_POSITION=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
|
||||
@@ -21,6 +21,7 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
NX_ISOLATED_MARGIN=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -20,6 +20,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -21,11 +21,14 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=false
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=false
|
||||
NX_REFERRALS=true
|
||||
# NX_DISABLE_CLOSE_POSITION=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
NX_DISABLE_CLOSE_POSITION=true
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
|
||||
@@ -21,6 +21,7 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=false
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -21,7 +21,11 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=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=
|
||||
|
||||
@@ -22,10 +22,13 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=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=
|
||||
|
||||
@@ -22,6 +22,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ISOLATED_MARGIN=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -54,3 +54,5 @@ To run the UI automation tests with a mocked API, run:
|
||||
```bash
|
||||
yarn nx run trading-e2e:e2e
|
||||
```
|
||||
|
||||
To run tests with market sim please read [the readme](e2e/README.md).
|
||||
|
||||
@@ -188,12 +188,11 @@ const useNow = () => {
|
||||
return now;
|
||||
};
|
||||
|
||||
const useEvery = (marketId: string) => {
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const useEvery = (marketId: string, skip: boolean) => {
|
||||
const { data: marketInfo } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
variables: { marketId },
|
||||
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||
skip,
|
||||
});
|
||||
let every: number | undefined = undefined;
|
||||
const sourceType =
|
||||
@@ -211,8 +210,10 @@ const useEvery = (marketId: string) => {
|
||||
return every;
|
||||
};
|
||||
|
||||
const useStartTime = (marketId: string) => {
|
||||
const useStartTime = (marketId: string, skip: boolean) => {
|
||||
const { data: fundingPeriods } = useFundingPeriodsQuery({
|
||||
pollInterval: 5000,
|
||||
skip,
|
||||
variables: {
|
||||
marketId: marketId,
|
||||
pagination: { first: 1 },
|
||||
@@ -246,8 +247,10 @@ const useFormatCountdown = (
|
||||
|
||||
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
|
||||
const now = useNow();
|
||||
const startTime = useStartTime(marketId);
|
||||
const every = useEvery(marketId);
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const skip = !marketTradingMode || isMarketInAuction(marketTradingMode);
|
||||
const startTime = useStartTime(marketId, skip);
|
||||
const every = useEvery(marketId, skip);
|
||||
|
||||
return (
|
||||
<div data-testid="funding-countdown">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
||||
@@ -9,8 +9,6 @@ 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';
|
||||
|
||||
@@ -61,9 +59,6 @@ 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);
|
||||
@@ -72,31 +67,11 @@ export const MarketPage = () => {
|
||||
const { data, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
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>
|
||||
@@ -132,6 +107,8 @@ export const MarketPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const pinnedAsset = data && getAsset(data);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleUpdater
|
||||
@@ -139,7 +116,11 @@ export const MarketPage = () => {
|
||||
marketName={data?.tradableInstrument.instrument.name}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
{tradeView}
|
||||
{largeScreen ? (
|
||||
<TradeGrid market={data} pinnedAsset={pinnedAsset} />
|
||||
) : (
|
||||
<TradePanels market={data} pinnedAsset={pinnedAsset} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { memo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, type ReactNode } from 'react';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import classNames from 'classnames';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { OracleBanner, useMarket } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { type Market } from '@vegaprotocol/markets';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ResizableGrid,
|
||||
@@ -13,30 +12,19 @@ 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 | null;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
market: Market;
|
||||
pinnedAsset?: PinnedAsset | undefined;
|
||||
}
|
||||
|
||||
const MainGrid = memo(
|
||||
({
|
||||
marketId,
|
||||
pinnedAsset,
|
||||
}: {
|
||||
marketId: string;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}) => {
|
||||
({ market, pinnedAsset }: { market: Market; pinnedAsset?: PinnedAsset }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const t = useT();
|
||||
const { data: market } = useMarket(marketId);
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
|
||||
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
|
||||
id: 'middle-1',
|
||||
@@ -61,20 +49,20 @@ const MainGrid = memo(
|
||||
id="chart"
|
||||
overflowHidden
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
menu={<TradingViews.chart.menu />}
|
||||
>
|
||||
<ErrorBoundary feature="chart">
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
<TradingViews.chart.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
<ErrorBoundary feature="depth">
|
||||
<TradingViews.depth.component marketId={marketId} />
|
||||
<TradingViews.depth.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="liquidity" name={t('Liquidity')}>
|
||||
<ErrorBoundary feature="liquidity">
|
||||
<TradingViews.liquidity.component marketId={marketId} />
|
||||
<TradingViews.liquidity.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
{market &&
|
||||
@@ -82,17 +70,21 @@ const MainGrid = memo(
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding-history" name={t('Funding history')}>
|
||||
<ErrorBoundary feature="funding-history">
|
||||
<TradingViews.funding.component marketId={marketId} />
|
||||
<TradingViews.funding.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
) : null}
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<Tab
|
||||
id="funding-payments"
|
||||
name={t('Funding payments')}
|
||||
settings={<TradingViews.fundingPayments.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="funding-payments">
|
||||
<TradingViews.fundingPayments.component
|
||||
marketId={marketId}
|
||||
marketId={market.id}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
@@ -108,12 +100,16 @@ const MainGrid = memo(
|
||||
<Tabs storageKey="console-trade-grid-main-right">
|
||||
<Tab id="orderbook" name={t('Orderbook')}>
|
||||
<ErrorBoundary feature="orderbook">
|
||||
<TradingViews.orderbook.component marketId={marketId} />
|
||||
<TradingViews.orderbook.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="trades" name={t('Trades')}>
|
||||
<Tab
|
||||
id="trades"
|
||||
name={t('Trades')}
|
||||
settings={<TradingViews.trades.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="trades">
|
||||
<TradingViews.trades.component marketId={marketId} />
|
||||
<TradingViews.trades.component marketId={market.id} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -132,6 +128,7 @@ const MainGrid = memo(
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<TradingViews.positions.menu />}
|
||||
settings={<TradingViews.positions.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="positions">
|
||||
<TradingViews.positions.component />
|
||||
@@ -141,17 +138,26 @@ const MainGrid = memo(
|
||||
id="open-orders"
|
||||
name={t('Open')}
|
||||
menu={<TradingViews.activeOrders.menu />}
|
||||
settings={<TradingViews.activeOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="activeOrders">
|
||||
<TradingViews.activeOrders.component />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<Tab
|
||||
id="closed-orders"
|
||||
name={t('Closed')}
|
||||
settings={<TradingViews.closedOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="closedOrders">
|
||||
<TradingViews.closedOrders.component />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<Tab
|
||||
id="rejected-orders"
|
||||
name={t('Rejected')}
|
||||
settings={<TradingViews.rejectedOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="rejectedOrders">
|
||||
<TradingViews.rejectedOrders.component />
|
||||
</ErrorBoundary>
|
||||
@@ -160,25 +166,35 @@ const MainGrid = memo(
|
||||
id="orders"
|
||||
name={t('All')}
|
||||
menu={<TradingViews.orders.menu />}
|
||||
settings={<TradingViews.orders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="orders">
|
||||
<TradingViews.orders.component />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
{featureFlags.STOP_ORDERS ? (
|
||||
<Tab
|
||||
id="stop-orders"
|
||||
name={t('Stop orders')}
|
||||
settings={<TradingViews.stopOrders.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="stop-orders">
|
||||
<TradingViews.stopOrders.component />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<Tab
|
||||
id="fills"
|
||||
name={t('Fills')}
|
||||
settings={<TradingViews.fills.settings />}
|
||||
>
|
||||
<TradingViews.fills.component />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="accounts"
|
||||
name={t('Collateral')}
|
||||
menu={<TradingViews.collateral.menu />}
|
||||
settings={<TradingViews.collateral.settings />}
|
||||
>
|
||||
<ErrorBoundary feature="collateral">
|
||||
<TradingViews.collateral.component
|
||||
@@ -204,17 +220,10 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
<MarketBanner market={market} />
|
||||
</div>
|
||||
<div className="min-h-0 p-0.5">
|
||||
<MainGrid marketId={market?.id || ''} pinnedAsset={pinnedAsset} />
|
||||
<MainGrid market={market} pinnedAsset={pinnedAsset} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { type Market } from '@vegaprotocol/markets';
|
||||
import { OracleBanner } from '@vegaprotocol/markets';
|
||||
// TODO: handle oracle banner
|
||||
// import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import classNames from 'classnames';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
Popover,
|
||||
Splash,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { MarketBanner } from '../../components/market-banner';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { type TradingView } from './trade-views';
|
||||
import { TradingViews } from './trade-views';
|
||||
|
||||
interface TradePanelsProps {
|
||||
market: Market | null;
|
||||
market: Market;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
const [view, setView] = useState<TradingView>('chart');
|
||||
const viewCfg = TradingViews[view];
|
||||
|
||||
const renderView = () => {
|
||||
const Component = TradingViews[view].component;
|
||||
@@ -43,35 +45,36 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
const viewCfg = TradingViews[view];
|
||||
|
||||
if ('menu' in viewCfg) {
|
||||
const Menu = viewCfg.menu;
|
||||
|
||||
if ('menu' in viewCfg || 'settings' in viewCfg) {
|
||||
return (
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu />
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
<MarketBanner market={market} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
<div className="h-full">
|
||||
<div className="h-full relative">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<div style={{ width, height }} className="overflow-auto">
|
||||
@@ -109,7 +112,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
key={key}
|
||||
view={key}
|
||||
isActive={isActive}
|
||||
onClick={() => setView(key)}
|
||||
onClick={() => {
|
||||
setView(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -148,7 +153,7 @@ const useViewLabel = (view: TradingView) => {
|
||||
const t = useT();
|
||||
|
||||
const labels = {
|
||||
candles: t('Candles'),
|
||||
chart: t('Chart'),
|
||||
depth: t('Depth'),
|
||||
liquidity: t('Liquidity'),
|
||||
funding: t('Funding'),
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
||||
import { TradesContainer } from '../../components/trades-container';
|
||||
import {
|
||||
TradesContainer,
|
||||
TradesSettings,
|
||||
} from '../../components/trades-container';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import {
|
||||
FillsContainer,
|
||||
FillsSettings,
|
||||
} from '../../components/fills-container';
|
||||
import {
|
||||
PositionsContainer,
|
||||
PositionsSettings,
|
||||
} from '../../components/positions-container';
|
||||
import {
|
||||
AccountsContainer,
|
||||
AccountsSettings,
|
||||
} from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import { FundingContainer } from '../../components/funding-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-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 { 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 = {
|
||||
candles: {
|
||||
component: CandlesChartContainer,
|
||||
menu: CandlesMenu,
|
||||
chart: {
|
||||
component: ChartContainer,
|
||||
menu: ChartMenu,
|
||||
},
|
||||
depth: {
|
||||
component: DepthChartContainer,
|
||||
@@ -35,37 +53,46 @@ 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 },
|
||||
fills: { component: FillsContainer, settings: FillsSettings },
|
||||
} as const;
|
||||
|
||||
@@ -314,7 +314,7 @@ describe('Closed', () => {
|
||||
});
|
||||
|
||||
it('display market actions', async () => {
|
||||
// Use market with a succcessor Id as the actions dropdown will optionally
|
||||
// Use market with a successor Id as the actions dropdown will optionally
|
||||
// show a link to the successor market
|
||||
const marketsWithSuccessorAndParent = [
|
||||
{
|
||||
|
||||
@@ -137,6 +137,8 @@ const ClosedMarketsDataGrid = ({
|
||||
headerName: t('Market'),
|
||||
field: 'code',
|
||||
cellRenderer: 'MarketCodeCell',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
@@ -280,6 +282,7 @@ const ClosedMarketsDataGrid = ({
|
||||
return (
|
||||
<AgGrid
|
||||
rowData={rowData}
|
||||
defaultColDef={COL_DEFS.default}
|
||||
columnDefs={colDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultColDef = {
|
||||
filter: true,
|
||||
resizable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
minWidth: 120,
|
||||
};
|
||||
|
||||
const components = {
|
||||
@@ -42,7 +43,7 @@ export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const useMarketsStore = create<DataGridSlice>()(
|
||||
export const useMarketsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_market_list_store',
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} 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();
|
||||
@@ -34,7 +35,11 @@ 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')}>
|
||||
<Tab
|
||||
id="open-markets"
|
||||
name={t('Open markets')}
|
||||
settings={<MarketsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="markets-open">
|
||||
<OpenMarkets />
|
||||
</ErrorBoundary>
|
||||
@@ -42,11 +47,13 @@ export const MarketsPage = () => {
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
name={t('Proposed markets')}
|
||||
settings={<MarketsSettings />}
|
||||
menu={
|
||||
<TradingAnchorButton
|
||||
size="extra-small"
|
||||
data-testid="propose-new-market"
|
||||
href={externalLink}
|
||||
target="_blank"
|
||||
>
|
||||
{t('Propose a new market')}
|
||||
</TradingAnchorButton>
|
||||
@@ -56,7 +63,11 @@ export const MarketsPage = () => {
|
||||
<Proposed />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||
<Tab
|
||||
id="closed-markets"
|
||||
name={t('Closed markets')}
|
||||
settings={<MarketsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="markets-closed">
|
||||
<Closed />
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { GridSettings } from '../../components/grid-settings/grid-settings';
|
||||
import { useMarketsStore } from './market-list-table';
|
||||
|
||||
export const MarketsSettings = () => (
|
||||
<GridSettings
|
||||
updateGridStore={useMarketsStore((store) => store.updateGridStore)}
|
||||
/>
|
||||
);
|
||||
@@ -4,9 +4,26 @@ 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();
|
||||
@@ -37,6 +54,7 @@ export const MarketsSidebar = () => {
|
||||
path=":marketId"
|
||||
element={
|
||||
<>
|
||||
<ViewInitializer />
|
||||
<SidebarDivider />
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
|
||||
@@ -29,6 +29,7 @@ export const useColumnDefs = () => {
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
pinned: true,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
|
||||
@@ -5,14 +5,29 @@ 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 } from '../../components/accounts-container';
|
||||
import {
|
||||
AccountsContainer,
|
||||
AccountsSettings,
|
||||
} from '../../components/accounts-container';
|
||||
import { DepositsContainer } from '../../components/deposits-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import {
|
||||
FillsContainer,
|
||||
FillsSettings,
|
||||
} from '../../components/fills-container';
|
||||
import {
|
||||
FundingPaymentsContainer,
|
||||
FundingPaymentsSettings,
|
||||
} from '../../components/funding-payments-container';
|
||||
import {
|
||||
PositionsContainer,
|
||||
PositionsSettings,
|
||||
} from '../../components/positions-container';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import {
|
||||
OrdersContainer,
|
||||
OrdersSettings,
|
||||
} from '../../components/orders-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import {
|
||||
ResizableGrid,
|
||||
@@ -39,11 +54,21 @@ const WithdrawalsIndicator = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const t = useT();
|
||||
const SidebarViewInitializer = () => {
|
||||
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,
|
||||
@@ -53,17 +78,11 @@ 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>
|
||||
@@ -72,22 +91,27 @@ export const Portfolio = () => {
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<PositionsMenu />}
|
||||
settings={<PositionsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-positions">
|
||||
<PositionsContainer allKeys />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<Tab id="orders" name={t('Orders')} settings={<OrdersSettings />}>
|
||||
<ErrorBoundary feature="portfolio-orders">
|
||||
<OrdersContainer />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<Tab id="fills" name={t('Fills')} settings={<FillsSettings />}>
|
||||
<ErrorBoundary feature="portfolio-fills">
|
||||
<FillsContainer />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<Tab
|
||||
id="funding-payments"
|
||||
name={t('Funding payments')}
|
||||
settings={<FundingPaymentsSettings />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-funding-payments">
|
||||
<FundingPaymentsContainer />
|
||||
</ErrorBoundary>
|
||||
@@ -110,6 +134,7 @@ export const Portfolio = () => {
|
||||
<Tab
|
||||
id="collateral"
|
||||
name={t('Collateral')}
|
||||
settings={<AccountsSettings />}
|
||||
menu={<AccountsMenu />}
|
||||
>
|
||||
<ErrorBoundary feature="portfolio-accounts">
|
||||
|
||||
@@ -18,13 +18,35 @@ import { Routes } from '../../lib/links';
|
||||
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
||||
import { Statistics, useStats } from './referral-statistics';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { useT } from '../../lib/use-t';
|
||||
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';
|
||||
|
||||
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) {
|
||||
@@ -76,7 +98,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
setValue,
|
||||
setError,
|
||||
watch,
|
||||
clearErrors,
|
||||
} = useForm();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
@@ -90,30 +111,11 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
*/
|
||||
const validateFundsAvailable = useCallback(() => {
|
||||
if (requiredFunds && !isEligible) {
|
||||
const err = t(
|
||||
'Require minimum of {{requiredFunds}} to join a referral set to protect the network from spam.',
|
||||
{ replace: { requiredFunds } }
|
||||
);
|
||||
const err = SPAM_PROTECTION_ERR;
|
||||
return err;
|
||||
}
|
||||
return true;
|
||||
}, [isEligible, requiredFunds, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (codeField) {
|
||||
const err = validateFundsAvailable();
|
||||
if (err !== true) {
|
||||
setStatus('no-funds');
|
||||
setError('code', {
|
||||
type: 'required',
|
||||
message: err,
|
||||
});
|
||||
} else {
|
||||
setStatus(null);
|
||||
clearErrors('code');
|
||||
}
|
||||
}
|
||||
}, [clearErrors, codeField, isEligible, setError, validateFundsAvailable]);
|
||||
}, [isEligible, requiredFunds]);
|
||||
|
||||
/**
|
||||
* Validates the set a user tries to apply to.
|
||||
@@ -138,6 +140,15 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
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;
|
||||
@@ -285,7 +296,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
<>
|
||||
<div
|
||||
data-testid="referral-apply-code-form"
|
||||
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto w-2/3 max-w-md rounded-lg p-8"
|
||||
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto md:w-2/3 max-w-md rounded-lg p-8"
|
||||
>
|
||||
<h3 className="calt mb-4 text-center text-2xl">
|
||||
{t('Apply a referral code')}
|
||||
@@ -321,10 +332,26 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
</label>
|
||||
<RainbowButton variant="border" {...getButtonProps()} />
|
||||
</form>
|
||||
{errors.code && (
|
||||
<InputError className="overflow-auto break-words">
|
||||
{errors.code.message?.toString()}
|
||||
{status === 'no-funds' ? (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
<span>
|
||||
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
|
||||
</span>
|
||||
</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 ? (
|
||||
|
||||
@@ -3,7 +3,7 @@ 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-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
|
||||
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[37%_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 =
|
||||
|
||||
@@ -49,7 +49,7 @@ export const CreateCodeForm = () => {
|
||||
return (
|
||||
<div
|
||||
data-testid="referral-create-code-form"
|
||||
className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
|
||||
className="md: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')}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
|
||||
import compact from 'lodash/compact';
|
||||
import sum from 'lodash/sum';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
/**
|
||||
* Gets the funds for given public key and required min for
|
||||
@@ -24,14 +24,16 @@ export const useFundsAvailable = (pubKey?: string) => {
|
||||
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
|
||||
: undefined;
|
||||
const requiredFunds = data
|
||||
? BigInt(data.networkParameter?.value || '0')
|
||||
? BigNumber(data.networkParameter?.value || '0')
|
||||
: undefined;
|
||||
|
||||
const sumOfFunds = sum(
|
||||
fundsAvailable?.filter((fa) => fa.balance).map((fa) => BigInt(fa.balance))
|
||||
);
|
||||
const sumOfFunds =
|
||||
fundsAvailable
|
||||
?.filter((fa) => fa.balance)
|
||||
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
|
||||
BigNumber(0);
|
||||
|
||||
if (requiredFunds && sumOfFunds >= requiredFunds) {
|
||||
if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
@@ -41,6 +43,6 @@ export const useFundsAvailable = (pubKey?: string) => {
|
||||
isEligible:
|
||||
fundsAvailable != null &&
|
||||
requiredFunds != null &&
|
||||
sumOfFunds >= requiredFunds,
|
||||
sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ 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,
|
||||
@@ -122,6 +123,66 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
};
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Table } from './table';
|
||||
import { Table } from '../../components/table';
|
||||
|
||||
export const HowItWorksTable = () => {
|
||||
const t = useT();
|
||||
|
||||
@@ -5,24 +5,24 @@ import { useT } from '../../lib/use-t';
|
||||
export const LandingBanner = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<div className={classNames('relative mb-20')}>
|
||||
<div className={classNames('relative mb-10 lg:mb-20')}>
|
||||
<div className="">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden"
|
||||
className="absolute top-20 right-[220px] md:right-[240px] max-sm:hidden"
|
||||
>
|
||||
<AnimatedDudeWithWire />
|
||||
</div>
|
||||
<div className="pt-20 sm:w-[50%]">
|
||||
<h1 className="text-6xl font-alpha calt mb-10">
|
||||
{t('Vega community referral program')}
|
||||
<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')}
|
||||
</h1>
|
||||
<p className="text-lg mb-1">
|
||||
{t(
|
||||
'Referral programs can be proposed and created via community governance.'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-lg mb-10">
|
||||
<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.'
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user