Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b55d6c3681 | ||
|
|
4314398a51 | ||
|
|
bb9fce4831 | ||
|
|
918825aca8 | ||
|
|
74bf183fad | ||
|
|
674baa1ee3 | ||
|
|
d18a0c7e15 | ||
|
|
76a6005b77 | ||
|
|
79414614c2 | ||
|
|
fde49e5446 | ||
|
|
45283161aa | ||
|
|
375d447fa8 | ||
|
|
1dd97a2bce | ||
|
|
d4a1a9b193 | ||
|
|
376c5241a8 | ||
|
|
8e6d8517cf | ||
|
|
5f9ec222c1 | ||
|
|
8954c41c0a |
+2
-1
@@ -51,7 +51,8 @@
|
||||
"ul": ["list"],
|
||||
"ol": ["list"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"no-console": ["error", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Feature Epic
|
||||
about:
|
||||
A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||
title: 'Epic: '
|
||||
labels: feature-epic
|
||||
---
|
||||
|
||||
## Core Feature
|
||||
|
||||
<Name>
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Define high level requirements
|
||||
- [ ] Create basic mockups
|
||||
- [ ] Update "API Requirements" in core spec
|
||||
- [ ] Update "User-Interface Spec" in relevant front end repo
|
||||
- [ ] Create detailed user stories using normal template
|
||||
|
||||
## High Level Requirements
|
||||
|
||||
## Basic Mockups
|
||||
|
||||
## Link to API Requirements in Core spec
|
||||
|
||||
## Link to User Interface Specs
|
||||
|
||||
## Linked User Stories
|
||||
@@ -29,6 +29,12 @@ jobs:
|
||||
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
|
||||
|
||||
- name: Is dev image
|
||||
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
|
||||
run: |
|
||||
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -75,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Log in to the Container registry (docker hub)
|
||||
uses: docker/login-action@v2
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -173,7 +179,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: dockerhub-push
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -183,7 +189,7 @@ jobs:
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
|
||||
|
||||
- name: Publish dist as docker image (ghcr - retry)
|
||||
uses: docker/build-push-action@v3
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSubContent,
|
||||
Icon,
|
||||
Button,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { FilterLabel } from './tx-filter-label';
|
||||
@@ -100,15 +99,13 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
trigger={
|
||||
<DropdownMenuTrigger className="ml-0">
|
||||
<Button size="xs" data-testid="filter-trigger">
|
||||
<FilterLabel filters={filters} />
|
||||
</Button>
|
||||
<DropdownMenuTrigger>
|
||||
<FilterLabel filters={filters} />
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{filters.size > 0 ? null : (
|
||||
{filters.size > 1 ? null : (
|
||||
<>
|
||||
<DropdownMenuCheckboxItem
|
||||
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const TxsListNavigation = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
disabled={!hasMoreTxs || loading}
|
||||
disabled={!hasMoreTxs}
|
||||
onClick={() => {
|
||||
nextPage();
|
||||
}}
|
||||
|
||||
@@ -86,14 +86,18 @@ describe('Txs infinite list item', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,9 @@ const DEFAULT_TRUNCATE_LENGTH = 7;
|
||||
|
||||
export function getIdTruncateLength(screen: Screen): number {
|
||||
if (['xxxl', 'xxl'].includes(screen)) {
|
||||
return 64;
|
||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||
return 32;
|
||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||
return 16;
|
||||
}
|
||||
return DEFAULT_TRUNCATE_LENGTH;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,17 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
const generateHash = (): string =>
|
||||
Array.from(
|
||||
{ length: 64 },
|
||||
() => '0123456789ABCDEF'[Math.floor(Math.random() * 16)]
|
||||
).join('');
|
||||
|
||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
return Array.from(Array(number)).map((_) => ({
|
||||
block: '87901',
|
||||
index: 2,
|
||||
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
|
||||
hash: generateHash(),
|
||||
submitter:
|
||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||
type: 'Submit Order',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Table, TableRow } from '../table';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||
import { getTxsDataUrl } from '../../hooks/use-txs-data';
|
||||
import { getTxsDataUrl } from '../../hooks/get-txs-data-url';
|
||||
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
@@ -14,7 +14,7 @@ interface TxsPerBlockProps {
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
const filters = `filters[block.height]=${blockHeight}`;
|
||||
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
|
||||
const url = getTxsDataUrl({ filters, count: txCount });
|
||||
const {
|
||||
state: { data, loading, error },
|
||||
} = useFetch<BlockExplorerTransactions>(url);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { DATA_SOURCES } from '../config';
|
||||
|
||||
type IGetTxsDataFirstPage = {
|
||||
baseUrl?: string;
|
||||
count?: number;
|
||||
party?: string;
|
||||
filters?: string;
|
||||
};
|
||||
|
||||
interface IGetTxsDataPrevious extends IGetTxsDataFirstPage {
|
||||
before: string;
|
||||
}
|
||||
|
||||
interface IGetTxsDataNext extends IGetTxsDataFirstPage {
|
||||
after: string;
|
||||
}
|
||||
|
||||
type IGetTxsDataUrl =
|
||||
| IGetTxsDataPrevious
|
||||
| IGetTxsDataNext
|
||||
| IGetTxsDataFirstPage;
|
||||
|
||||
export const BE_TXS_PER_REQUEST = 25;
|
||||
|
||||
/**
|
||||
* Properly encodes the filters and parameters for a request to the block explorer
|
||||
* API for transactions. As the API uses a slightly less common format for encoding
|
||||
* filters, some of it is more manual than you might expect.
|
||||
*
|
||||
* @param params An object containing the pagination and filters
|
||||
* @returns string URL to call
|
||||
*/
|
||||
export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
|
||||
const baseUrl =
|
||||
params.baseUrl || `${DATA_SOURCES.blockExplorerUrl}/transactions`;
|
||||
const url = new URL(baseUrl);
|
||||
const count = `${params.count || BE_TXS_PER_REQUEST}`;
|
||||
|
||||
if ('before' in params && params.before?.length > 0) {
|
||||
url.searchParams.append('last', count);
|
||||
url.searchParams.append('before', params.before);
|
||||
} else if ('after' in params && params.after?.length > 0) {
|
||||
url.searchParams.append('first', count);
|
||||
url.searchParams.append('after', params.after);
|
||||
} else {
|
||||
url.searchParams.append('first', count);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (params.filters && params.filters?.length > 0) {
|
||||
urlAsString += '&' + params.filters.replaceAll(' ', '%20');
|
||||
}
|
||||
if (params.party && params.party?.length > 0) {
|
||||
urlAsString += `&filters[tx.submitter]=${params.party}`;
|
||||
}
|
||||
|
||||
return urlAsString;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { getTxsDataUrl } from './get-txs-data-url'; // import the function to be tested
|
||||
|
||||
describe('getTxsDataUrl', () => {
|
||||
it('should return the correct URL without filters and party', () => {
|
||||
const params = {
|
||||
count: 10,
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?first=10';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with "before" in params', () => {
|
||||
const params = {
|
||||
count: 5,
|
||||
before: '100.1',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?last=5&before=100.1';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with "after" in params', () => {
|
||||
const params = {
|
||||
count: 5,
|
||||
after: '222.1',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?first=5&after=222.1';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with filters and party', () => {
|
||||
const params = {
|
||||
count: 10,
|
||||
filters: 'filters[cmd.type]=Made Up Transaction',
|
||||
party: '1234',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl =
|
||||
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
@@ -1,130 +1,141 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import type { URLSearchParamsInit } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
BlockExplorerTransactionResult,
|
||||
BlockExplorerTransactions,
|
||||
} from '../routes/types/block-explorer-response';
|
||||
import { DATA_SOURCES } from '../config';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import { AllFilterOptions } from '../components/txs/tx-filter';
|
||||
import type { FilterOption } from '../components/txs/tx-filter';
|
||||
import { BE_TXS_PER_REQUEST, getTxsDataUrl } from './get-txs-data-url';
|
||||
|
||||
export interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
hasMoreTxs: boolean;
|
||||
cursor: string;
|
||||
previousCursors: string[];
|
||||
hasPreviousPage: boolean;
|
||||
export function getTypeFilters(filters?: Set<FilterOption>) {
|
||||
if (!filters) {
|
||||
return '';
|
||||
} else if (filters.size > 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const forcedSingleFilter = Array.from(filters)[0];
|
||||
return `filters[cmd.type]=${forcedSingleFilter}`;
|
||||
}
|
||||
|
||||
export interface IUseTxsData {
|
||||
limit: number;
|
||||
filters?: string;
|
||||
count?: number;
|
||||
before?: string;
|
||||
after?: string;
|
||||
party?: string;
|
||||
filters?: Set<FilterOption>;
|
||||
}
|
||||
|
||||
interface IGetTxsDataUrl {
|
||||
limit: string;
|
||||
filters?: string;
|
||||
}
|
||||
export const useTxsData = ({
|
||||
count = 25,
|
||||
before,
|
||||
after,
|
||||
filters,
|
||||
party,
|
||||
}: IUseTxsData) => {
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
let hasMoreTxs = true;
|
||||
let txsData: BlockExplorerTransactionResult[] = [];
|
||||
|
||||
export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
||||
const url = new URL(`${DATA_SOURCES.blockExplorerUrl}/transactions`);
|
||||
|
||||
if (limit) {
|
||||
url.searchParams.append('limit', limit);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (filters) {
|
||||
urlAsString += '&' + filters.replace(' ', '%20');
|
||||
}
|
||||
|
||||
return urlAsString;
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
const [
|
||||
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
|
||||
setTxsState,
|
||||
] = useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: false,
|
||||
previousCursors: [],
|
||||
cursor: '',
|
||||
hasPreviousPage: false,
|
||||
const url = getTxsDataUrl({
|
||||
filters: getTypeFilters(filters),
|
||||
count,
|
||||
before,
|
||||
after,
|
||||
party,
|
||||
});
|
||||
|
||||
const url = getTxsDataUrl({ limit: limit.toString(), filters });
|
||||
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && data && isNumber(data.transactions.length)) {
|
||||
setTxsState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
txsData: data.transactions,
|
||||
hasMoreTxs: data.transactions.length >= limit,
|
||||
cursor: data?.transactions.at(-1)?.cursor || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [loading, setTxsState, data, limit]);
|
||||
if (!loading && data && isNumber(data.transactions.length)) {
|
||||
hasMoreTxs = data.transactions.length >= count;
|
||||
txsData = data.transactions;
|
||||
}
|
||||
|
||||
const nextPage = useCallback(() => {
|
||||
const c = data?.transactions.at(0)?.cursor;
|
||||
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
|
||||
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
hasPreviousPage: true,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
|
||||
return refetch({
|
||||
limit,
|
||||
before: cursor,
|
||||
});
|
||||
}, [data, previousCursors, cursor, limit, refetch]);
|
||||
const after = data?.transactions.at(-1)?.cursor || '';
|
||||
const params: URLSearchParamsInit = { after };
|
||||
if (filters) {
|
||||
params.filters = Array.from(filters).join(',');
|
||||
}
|
||||
setSearchParams(params);
|
||||
}, [filters, data, setSearchParams]);
|
||||
|
||||
const previousPage = useCallback(() => {
|
||||
const previousCursor = [...previousCursors].pop();
|
||||
const newPreviousCursors = previousCursors.slice(0, -1);
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
hasPreviousPage: newPreviousCursors.length > 0,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
return refetch({
|
||||
limit,
|
||||
before: previousCursor,
|
||||
});
|
||||
}, [previousCursors, limit, refetch]);
|
||||
const before = data?.transactions[0]?.cursor || '';
|
||||
const params: URLSearchParamsInit = { before };
|
||||
if (filters && filters.size > 0 && filters.size === 1) {
|
||||
params.filters = Array.from(filters)[0];
|
||||
}
|
||||
setSearchParams(params);
|
||||
}, [filters, data, setSearchParams]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState(() => ({
|
||||
txsData: [],
|
||||
cursor: '',
|
||||
previousCursors: [],
|
||||
hasMoreTxs: false,
|
||||
hasPreviousPage: false,
|
||||
}));
|
||||
const params: URLSearchParamsInit = {};
|
||||
if (filters && filters.size > 0 && filters.size === 1) {
|
||||
params.filters = Array.from(filters)[0];
|
||||
}
|
||||
setSearchParams(params);
|
||||
|
||||
refetch({ limit });
|
||||
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
refetch({ count: BE_TXS_PER_REQUEST });
|
||||
}, [setSearchParams, refetch, filters]);
|
||||
|
||||
const updateFilters = useCallback(
|
||||
(newFilters: Set<FilterOption>) => {
|
||||
const params: URLSearchParamsInit = {};
|
||||
if (newFilters && newFilters.size === 1) {
|
||||
params.filters = Array.from(newFilters)[0];
|
||||
}
|
||||
|
||||
setSearchParams(params);
|
||||
},
|
||||
[setSearchParams]
|
||||
);
|
||||
|
||||
return {
|
||||
updateFilters,
|
||||
txsData,
|
||||
loading,
|
||||
error,
|
||||
hasMoreTxs,
|
||||
hasPreviousPage,
|
||||
previousCursors,
|
||||
cursor,
|
||||
refreshTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a Set of filters based on the URLSearchParams, or
|
||||
* defaults to all.
|
||||
* @param params
|
||||
* @returns Set
|
||||
*/
|
||||
export function getInitialFilters(params: URLSearchParams): Set<FilterOption> {
|
||||
const defaultFilters = new Set(AllFilterOptions);
|
||||
|
||||
const p = params.get('filters');
|
||||
|
||||
if (!p) {
|
||||
return defaultFilters;
|
||||
}
|
||||
|
||||
const filters = new Set<FilterOption>();
|
||||
p.split(',').forEach((f) => {
|
||||
if (AllFilterOptions.includes(f as FilterOption)) {
|
||||
filters.add(f as FilterOption);
|
||||
}
|
||||
});
|
||||
|
||||
if (filters.size === 0) {
|
||||
return defaultFilters;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ describe('Block', () => {
|
||||
(useFetch as jest.Mock).mockReturnValue({
|
||||
state: { data: createBlockResponse(1), loading: false, error: null },
|
||||
});
|
||||
|
||||
render(renderComponent(1));
|
||||
await waitFor(() => screen.getByTestId('block-header'));
|
||||
expect(screen.getByTestId('previous-block-button')).toHaveAttribute(
|
||||
|
||||
@@ -15,9 +15,13 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
|
||||
import { isValidPartyId } from './components/party-id-error';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const Party = () => {
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
const { party } = useParams<{ party: string }>();
|
||||
|
||||
@@ -27,24 +31,21 @@ const Party = () => {
|
||||
const partyId = toNonHex(party ? party : '');
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
||||
const baseFilters = `filters[tx.submitter]=${partyId}`;
|
||||
const f =
|
||||
filters && filters.size === 1
|
||||
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: baseFilters;
|
||||
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
refreshTxs,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
hasMoreTxs,
|
||||
updateFilters,
|
||||
} = useTxsData({
|
||||
limit: 25,
|
||||
filters: f,
|
||||
filters: filters.size === 1 ? filters : undefined,
|
||||
before: params.get('before') || undefined,
|
||||
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||
party: partyId,
|
||||
});
|
||||
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
@@ -102,15 +103,21 @@ const Party = () => {
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
hasPreviousPage={true}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
<TxsFilter
|
||||
filters={filters}
|
||||
setFilters={(f) => {
|
||||
setFilters(f);
|
||||
updateFilters(f as Set<FilterOption>);
|
||||
}}
|
||||
/>
|
||||
</TxsListNavigation>
|
||||
{!error && txsData ? (
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
hasMoreTxs={true}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={nextPage}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { useTxsData, getInitialFilters } from '../../../hooks/use-txs-data';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import { TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
|
||||
const BE_TXS_PER_REQUEST = 25;
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
export const TxsList = () => {
|
||||
useDocumentTitle(['Transactions']);
|
||||
@@ -27,25 +27,22 @@ export const TxsList = () => {
|
||||
* @returns {JSX.Element} Transaction List and controls
|
||||
*/
|
||||
export const TxsListFiltered = () => {
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
|
||||
const f =
|
||||
filters && filters.size === 1
|
||||
? `filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: '';
|
||||
const [params] = useSearchParams();
|
||||
const [filters, setFilters] = useState(getInitialFilters(params));
|
||||
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
hasMoreTxs,
|
||||
updateFilters,
|
||||
} = useTxsData({
|
||||
limit: BE_TXS_PER_REQUEST,
|
||||
filters: f,
|
||||
filters: filters.size === 1 ? filters : undefined,
|
||||
before: params.get('before') || undefined,
|
||||
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -54,11 +51,17 @@ export const TxsListFiltered = () => {
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
hasPreviousPage={true}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
<TxsFilter
|
||||
filters={filters}
|
||||
setFilters={(f) => {
|
||||
setFilters(f);
|
||||
updateFilters(f as Set<FilterOption>);
|
||||
}}
|
||||
/>
|
||||
</TxsListNavigation>
|
||||
<TxsInfiniteList
|
||||
hasFilters={filters.size > 0}
|
||||
|
||||
@@ -18,10 +18,7 @@ import { VegaWallet } from '../vega-wallet';
|
||||
import { useLocation, useMatch } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
|
||||
import {
|
||||
ProtocolUpgradeCountdown,
|
||||
ProtocolUpgradeCountdownMode,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
|
||||
|
||||
export const SettingsLink = () => {
|
||||
const { open, isOpen, close } = useTelemetryDialog();
|
||||
@@ -68,9 +65,7 @@ export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
|
||||
actions={
|
||||
<>
|
||||
<SettingsLink />
|
||||
<ProtocolUpgradeCountdown
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeCountdown />
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -20,6 +20,6 @@ export const downloadJson = (jsonString: string, proposalTitle: string) => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ export const ProposalMarketData = ({
|
||||
title={marketData.tradableInstrument.instrument.code}
|
||||
open={isOpen}
|
||||
onChange={(isOpen) => (isOpen ? open() : close())}
|
||||
size="medium"
|
||||
size="large"
|
||||
dataTestId="market-json-dialog"
|
||||
>
|
||||
<CopyWithTooltip text={JSON.stringify(marketData)}>
|
||||
|
||||
-15
@@ -5,7 +5,6 @@ import {
|
||||
RewardsTable,
|
||||
} from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
||||
import type { EpochIndividualReward } from './generate-epoch-individual-rewards-list';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface EpochIndividualRewardsGridProps {
|
||||
data: EpochIndividualReward;
|
||||
@@ -22,14 +21,11 @@ interface RewardItemProps {
|
||||
const DisplayReward = ({
|
||||
reward,
|
||||
decimals,
|
||||
percentageOfTotal,
|
||||
}: {
|
||||
reward: string;
|
||||
decimals: number;
|
||||
percentageOfTotal?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (Number(reward) === 0) {
|
||||
return <span className="text-vega-dark-300">-</span>;
|
||||
}
|
||||
@@ -39,23 +35,12 @@ const DisplayReward = ({
|
||||
description={
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{formatNumber(toBigNum(reward, decimals), decimals)}</span>
|
||||
{percentageOfTotal && (
|
||||
<span className="text-vega-dark-300">
|
||||
({percentageOfTotal}% {t('ofTotalDistributed')})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button>
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{formatNumber(toBigNum(reward, decimals), 4)}</span>
|
||||
{percentageOfTotal && (
|
||||
<span className="text-vega-dark-300">
|
||||
({formatNumber(percentageOfTotal, 4).toString()}
|
||||
%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
-76
@@ -168,17 +168,6 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
expect(result2[1].epoch).toEqual(1);
|
||||
});
|
||||
|
||||
it('correctly calculates the total value of rewards for an asset', () => {
|
||||
const rewards = [reward1, reward4];
|
||||
const result = generateEpochIndividualRewardsList({
|
||||
rewards,
|
||||
epochId: 1,
|
||||
epochRewardSummaries: [],
|
||||
});
|
||||
|
||||
expect(result[0].rewards[0].totalAmount).toEqual('200');
|
||||
});
|
||||
|
||||
it('returns data in the expected shape', () => {
|
||||
// Just sanity checking the whole structure here
|
||||
const rewards = [reward1, reward2, reward3, reward4];
|
||||
@@ -458,69 +447,4 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('correctly calculates the percentage of two or more rewards by referencing the total rewards amount', () => {
|
||||
const result = generateEpochIndividualRewardsList({
|
||||
rewards: [
|
||||
// reward1 is 100 usd, which is 10% of the total rewards amount
|
||||
reward1,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
amount: '200',
|
||||
percentageOfTotal: '0.2',
|
||||
receivedAt: new Date(),
|
||||
asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 },
|
||||
party: { id: 'blah' },
|
||||
epoch: { id: '1' },
|
||||
},
|
||||
],
|
||||
epochId: 1,
|
||||
epochRewardSummaries: [
|
||||
{
|
||||
__typename: 'EpochRewardSummary',
|
||||
epoch: 1,
|
||||
assetId: 'usd',
|
||||
amount: '1000',
|
||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result[0]).toEqual({
|
||||
epoch: 1,
|
||||
rewards: [
|
||||
{
|
||||
asset: 'USD',
|
||||
decimals: 6,
|
||||
totalAmount: '300',
|
||||
rewardTypes: {
|
||||
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||
amount: '300',
|
||||
percentageOfTotal: '30',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1
-16
@@ -59,7 +59,6 @@ export const generateEpochIndividualRewardsList = ({
|
||||
const epochIndividualRewards = rewards.reduce((acc, reward) => {
|
||||
const epochId = reward.epoch.id;
|
||||
const assetName = reward.asset.name;
|
||||
const assetId = reward.asset.id;
|
||||
const assetDecimals = reward.asset.decimals;
|
||||
const rewardType = reward.rewardType;
|
||||
const amount = reward.amount;
|
||||
@@ -77,13 +76,6 @@ export const generateEpochIndividualRewardsList = ({
|
||||
const epoch = acc.get(epochId);
|
||||
|
||||
// matchingTotalReward is the total awarded for all users for the reward type in the epoch of the asset
|
||||
const matchingTotalRewardAmount = epochRewardSummaries.find(
|
||||
(summary) =>
|
||||
summary.epoch === Number(epochId) &&
|
||||
summary.assetId === assetId &&
|
||||
summary.rewardType === rewardType
|
||||
)?.amount;
|
||||
|
||||
let asset = epoch?.rewards.find((r) => r.asset === assetName);
|
||||
|
||||
if (!asset) {
|
||||
@@ -106,14 +98,7 @@ export const generateEpochIndividualRewardsList = ({
|
||||
|
||||
asset.rewardTypes[rewardType] = {
|
||||
amount: newAmount,
|
||||
percentageOfTotal: matchingTotalRewardAmount
|
||||
? new BigNumber(newAmount)
|
||||
.dividedBy(matchingTotalRewardAmount)
|
||||
.multipliedBy(100)
|
||||
.toString()
|
||||
: // this should never be reached, if there's an individual reward there should
|
||||
// always be a reward total from the api too, but set it as a fallback just in case
|
||||
percentageOfTotal,
|
||||
percentageOfTotal: percentageOfTotal,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ export const RewardsPage = () => {
|
||||
error: paramsError,
|
||||
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
|
||||
|
||||
console.log('params', params);
|
||||
|
||||
const payoutDuration = useMemo(() => {
|
||||
if (!params) {
|
||||
return 0;
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ const ROWS = [
|
||||
|
||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
return (
|
||||
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
||||
<Dialog size="large" open={isOpen} onChange={onChange}>
|
||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
||||
{t('Health')}
|
||||
</h1>
|
||||
|
||||
@@ -535,6 +535,7 @@ function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
|
||||
// unexpected latency
|
||||
const minBefore = subSeconds(new Date(), 5);
|
||||
const maxAfter = addSeconds(new Date(), 5);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(maxAfter);
|
||||
const date = new Date($dateTime.toString());
|
||||
expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal(
|
||||
|
||||
@@ -2,6 +2,7 @@ describe('charts', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('Depth').click();
|
||||
|
||||
@@ -202,6 +202,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
const specDataConnection = createDataConnection();
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
@@ -455,6 +456,7 @@ describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ const nodeHealthTrigger = 'node-health-trigger';
|
||||
|
||||
describe('home', { tags: '@regression' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
@@ -68,22 +69,4 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.getByTestId('connect').click();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Network switcher', () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('switch to fairground network and check status & incidents link', () => {
|
||||
// 0006-NETW-002
|
||||
// 0006-NETW-003
|
||||
cy.getByTestId('navigation')
|
||||
.find('[data-testid="network-switcher"]')
|
||||
.should('have.text', 'Custom')
|
||||
.click();
|
||||
cy.getByTestId('network-item').contains('Fairground testnet');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const dialogContent = 'dialog-content';
|
||||
const dialogContent = 'welcome-dialog';
|
||||
|
||||
const generateProposal = (code: string): ProposalListFieldsFragment => ({
|
||||
__typename: 'Proposal',
|
||||
@@ -140,43 +140,43 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.wait('@MarketsData');
|
||||
});
|
||||
|
||||
it('redirects to market/all and displays welcome notice', () => {
|
||||
it('close welcome dialog should redirect to market/all', () => {
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.getByTestId('welcome-notice-title').should(
|
||||
'contain.text',
|
||||
'Welcome to Console'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no proposal nor markets found', () => {
|
||||
it('there are welcome text and a link to propose market', () => {
|
||||
cy.mockGQL((req) => {
|
||||
const data = {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [],
|
||||
},
|
||||
};
|
||||
aliasGQLQuery(req, 'Markets', data);
|
||||
aliasGQLQuery(req, 'MarketsData', data);
|
||||
aliasGQLQuery(req, 'ProposalsList', {
|
||||
proposalsConnection: {
|
||||
__typename: 'ProposalsConnection',
|
||||
edges: null,
|
||||
},
|
||||
});
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('welcome-title').should('contain.text', 'Console CUSTOM');
|
||||
cy.getByTestId('browse-markets-button').should('not.be.disabled');
|
||||
cy.getByTestId('get-started-banner').should('be.visible');
|
||||
cy.getByTestId('get-started-button').should('not.be.disabled');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
cy.getByTestId('welcome-notice-title').should(
|
||||
'contain.text',
|
||||
'Welcome to Console'
|
||||
);
|
||||
cy.getByTestId('external-link')
|
||||
.contains('Propose a market')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('click browse markets button should redirect to market/all', () => {
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('browse-markets-button').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('click get started button should open connect dialog', () => {
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('get-started-button').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,7 +185,7 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.window().then((window) => {
|
||||
window.localStorage.setItem('marketId', 'market-1');
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-1');
|
||||
cy.getByTestId(dialogContent).should('not.exist');
|
||||
});
|
||||
@@ -199,6 +199,7 @@ describe('home', { tags: '@regression' }, () => {
|
||||
});
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
||||
cy.getByTestId(dialogContent).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { MarketsQuery } from '@vegaprotocol/markets';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
|
||||
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
|
||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
@@ -34,7 +35,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
'Settlement asset',
|
||||
'',
|
||||
];
|
||||
cy.getByTestId('tab-all-markets').within(($headers) => {
|
||||
cy.getByTestId('tab-open-markets').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
@@ -44,7 +45,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('markets tab should be rendered properly', () => {
|
||||
cy.get('[data-testid="All markets"]').should(
|
||||
cy.get('[data-testid="Open markets"]').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'active'
|
||||
@@ -166,7 +167,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
'ETHBTC.QM21',
|
||||
'SOLUSD',
|
||||
];
|
||||
cy.get('[data-testid="All markets"]').click({ force: true });
|
||||
cy.get('[data-testid="Open markets"]').click({ force: true });
|
||||
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
|
||||
cy.contains('AAPL.MF21').should('be.visible');
|
||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||
@@ -191,7 +192,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const markets: MarketsQuery = {};
|
||||
@@ -199,11 +200,12 @@ describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
|
||||
it.skip('can see no markets message', () => {
|
||||
// 6001-MARK-048
|
||||
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
|
||||
cy.getByTestId('tab-open-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
|
||||
@@ -36,6 +36,7 @@ const headers = [
|
||||
|
||||
describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
@@ -119,6 +120,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
|
||||
describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.window().then((window) => {
|
||||
window.localStorage.setItem('marketId', 'market-1');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('Market trading page', () => {
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@MarketData');
|
||||
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
@@ -84,7 +85,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContent = '[data-testid="proposal-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
|
||||
// 6001-MARK-059
|
||||
@@ -100,7 +101,6 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
'VEGA_TOKEN_URL'
|
||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||
);
|
||||
cy.getByTestId('market-actions-content').click();
|
||||
});
|
||||
|
||||
// 6001-MARK-060
|
||||
@@ -214,11 +214,13 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
cy.setOnBoardingViewed();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { mockConnectWallet } from '@vegaprotocol/cypress';
|
||||
|
||||
describe('Navbar', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
});
|
||||
|
||||
const pages = [
|
||||
{ name: 'Markets', link: '#/markets/all' },
|
||||
{ name: 'Trading', link: '#/markets' },
|
||||
{ name: 'Portfolio', link: '#/portfolio' },
|
||||
];
|
||||
|
||||
describe('desktop view', () => {
|
||||
pages.forEach(({ name, link }) => {
|
||||
it(`${name} should be correctly rendered`, () => {
|
||||
cy.get('nav')
|
||||
.find(`a[data-testid=${name}]:visible`)
|
||||
.then((element) => {
|
||||
cy.wrap(element).click();
|
||||
cy.location('hash').should('contain', link);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Resources dropdown should be correctly rendered', () => {
|
||||
const resourceSelector = 'ul li:contains(Resources)';
|
||||
['Docs', 'Give Feedback'].forEach((text, index) => {
|
||||
cy.get('nav').find(resourceSelector).contains('Resources').click();
|
||||
cy.get('nav')
|
||||
.find(resourceSelector)
|
||||
.find('.navigation-content li')
|
||||
.eq(index)
|
||||
.find('a')
|
||||
.then((element) => {
|
||||
expect(element.attr('target')).to.eq('_blank');
|
||||
expect(element.attr('href')).to.not.be.empty;
|
||||
expect(element.text()).to.eq(text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Disclaimer should be presented after choosing from menu', () => {
|
||||
cy.get('nav')
|
||||
.find('ul li:contains(Resources)')
|
||||
.contains('Resources')
|
||||
.click();
|
||||
cy.getByTestId('Disclaimer').eq(0).click();
|
||||
cy.location('hash').should('equal', '#/disclaimer');
|
||||
cy.get('p').contains(
|
||||
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mobile view', () => {
|
||||
const viewportHeight = Cypress.config('viewportHeight');
|
||||
const viewportWidth = Cypress.config('viewportWidth');
|
||||
before(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth: 560,
|
||||
viewportHeight: 890,
|
||||
});
|
||||
cy.viewport(560, 890);
|
||||
});
|
||||
|
||||
describe('wallet drawer', () => {
|
||||
it('wallet drawer should be correctly rendered', () => {
|
||||
mockConnectWallet();
|
||||
cy.connectVegaWallet(true);
|
||||
cy.getByTestId('connect-vega-wallet-mobile').click();
|
||||
cy.getByTestId('wallets-drawer').should('be.visible');
|
||||
cy.getByTestId('wallets-drawer').within((el) => {
|
||||
cy.wrap(el).get('button').contains('Disconnect').click();
|
||||
});
|
||||
cy.getByTestId('wallets-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('menu drawer', () => {
|
||||
pages.forEach(({ name, link }) => {
|
||||
it(`${name} should be correctly rendered`, () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId(name).click();
|
||||
cy.location('hash').should('contain', link);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Menu drawer should not be visible until opened', () => {
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
after(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('oracle information', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
|
||||
@@ -14,6 +14,7 @@ const resPrice = 'price-990';
|
||||
|
||||
describe('order book', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
|
||||
@@ -4,6 +4,7 @@ describe('Settings page', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/');
|
||||
|
||||
// Only click if not already active otherwise sidebar will close
|
||||
|
||||
@@ -77,6 +77,7 @@ function getButtonSelectorByText(text: string): string {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
@@ -171,6 +172,7 @@ describe(
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
const actualDate = text.slice(0, -67);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(actualDate);
|
||||
const actualOhlc = text.slice(-67);
|
||||
assert.isTrue(expectedDateRegex.test(actualDate));
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.clearAllLocalStorage();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
@@ -28,16 +29,16 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must be able to select order direction - long/short', function () {
|
||||
// 7002-SORD-004
|
||||
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleShort).click().next('input').should('be.checked');
|
||||
cy.getByTestId(toggleLong).click().next('input').should('be.checked');
|
||||
});
|
||||
|
||||
it('must be able to select order type - limit/market', function () {
|
||||
// 7002-SORD-005
|
||||
// 7002-SORD-006
|
||||
// 7002-SORD-007
|
||||
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLimit).click().next('input').should('be.checked');
|
||||
cy.getByTestId(toggleMarket).click().next('input').should('be.checked');
|
||||
});
|
||||
|
||||
it('order connect vega wallet button should connect', () => {
|
||||
@@ -51,7 +52,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
.click();
|
||||
cy.wait('@walletReq');
|
||||
cy.getByTestId(placeOrderBtn).should('be.visible');
|
||||
cy.getByTestId(toggleLimit).children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLimit).next('input').should('be.checked');
|
||||
cy.getByTestId(orderPriceField).should('have.value', '101');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.clearAllLocalStorage();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const closePosition = 'close-position';
|
||||
const dialogCloseX = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const dropDownMenu = 'dropdown-menu';
|
||||
const marketActionsContent = 'market-actions-content';
|
||||
const marketActionsContent = 'position-actions-content';
|
||||
const positions = 'Positions';
|
||||
const tabPositions = 'tab-positions';
|
||||
const toastContent = 'toast-content';
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('trades', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.intercept('POST', '/graphql', (req) => {
|
||||
if (req.body.operationName === 'Trades') {
|
||||
req.alias = '@Trades';
|
||||
|
||||
@@ -17,6 +17,7 @@ describe(
|
||||
cy.visit('/#/portfolio');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
@@ -122,6 +123,7 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Assets');
|
||||
cy.wait('@Accounts');
|
||||
@@ -57,7 +59,6 @@ describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
||||
// 1003-TRAN-019
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
|
||||
const orderSizeField = 'order-size';
|
||||
@@ -9,7 +10,9 @@ export const createOrder = (order: OrderSubmission): void => {
|
||||
cy.log('Placing order', order);
|
||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||
|
||||
cy.getByTestId(`order-type-${type}`).click();
|
||||
cy.getByTestId(
|
||||
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
|
||||
).click();
|
||||
cy.getByTestId(`order-side-${side}`).click();
|
||||
cy.getByTestId(orderSizeField).clear().type(size);
|
||||
if (price) {
|
||||
|
||||
@@ -6,8 +6,8 @@ export const orderTIFDropDown = 'order-tif';
|
||||
export const placeOrderBtn = 'place-order';
|
||||
export const toggleShort = 'order-side-SIDE_SELL';
|
||||
export const toggleLong = 'order-side-SIDE_BUY';
|
||||
export const toggleLimit = 'order-type-TYPE_LIMIT';
|
||||
export const toggleMarket = 'order-type-TYPE_MARKET';
|
||||
export const toggleLimit = 'order-type-Limit';
|
||||
export const toggleMarket = 'order-type-Market';
|
||||
|
||||
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
||||
return {
|
||||
|
||||
+1
-1
@@ -16,6 +16,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -17,6 +17,6 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -17,6 +17,6 @@ NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import { GetStarted } from '../../components/welcome-dialog';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const Deposit = () => {
|
||||
return (
|
||||
<div className="py-16 px-8 flex w-full justify-center">
|
||||
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
|
||||
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
|
||||
{t('Deposit')}
|
||||
</h1>
|
||||
<div className="mt-10">
|
||||
<DepositContainer />
|
||||
<GetStarted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Deposit } from './deposit';
|
||||
|
||||
export default Deposit;
|
||||
@@ -23,7 +23,6 @@ export const Home = () => {
|
||||
replace: true,
|
||||
});
|
||||
} else if (data) {
|
||||
update({ shouldDisplayWelcomeDialog: true });
|
||||
const marketDataId = data[0]?.id;
|
||||
if (marketDataId) {
|
||||
navigate(Links[Routes.MARKET](marketDataId), {
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { Market } from '@vegaprotocol/markets';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
@@ -58,7 +57,11 @@ const MainGrid = memo(
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-left">
|
||||
<Tab id="chart" name={t('Chart')}>
|
||||
<Tab
|
||||
id="chart"
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
>
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
@@ -95,57 +98,48 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</Tab>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
<TradingViews.stopOrders.component />
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
|
||||
@@ -37,6 +37,7 @@ export const TradePanels = ({
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
|
||||
const renderView = () => {
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
@@ -65,8 +66,23 @@ export const TradePanels = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
const viewCfg = TradingViews[view];
|
||||
|
||||
if ('menu' in viewCfg) {
|
||||
const Menu = viewCfg.menu;
|
||||
return (
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
|
||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
@@ -76,6 +92,7 @@ export const TradePanels = ({
|
||||
)}
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
<div className="h-full">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
@@ -88,9 +105,12 @@ export const TradePanels = ({
|
||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||
{Object.keys(TradingViews).map((key) => {
|
||||
const isActive = view === key;
|
||||
const className = classNames('p-4 min-w-[100px] capitalize', {
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
||||
});
|
||||
const className = classNames(
|
||||
'py-2 px-4 min-w-[100px] capitalize text-sm',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<button
|
||||
data-testid={key}
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { ComponentProps } from 'react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
@@ -12,6 +15,7 @@ import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -34,6 +38,7 @@ export const TradingViews = {
|
||||
candles: {
|
||||
label: 'Candles',
|
||||
component: requiresMarket(CandlesChartContainer),
|
||||
menu: CandlesMenu,
|
||||
},
|
||||
depth: {
|
||||
label: 'Depth',
|
||||
@@ -74,6 +79,10 @@ export const TradingViews = {
|
||||
label: 'All',
|
||||
component: OrdersContainer,
|
||||
},
|
||||
stopOrders: {
|
||||
label: 'Stop',
|
||||
component: StopOrdersContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export const MarketsPage = () => {
|
||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||
<div className="h-full my-1 border border-default rounded-sm">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="all-markets" name={t('All markets')}>
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
</Tab>
|
||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||
|
||||
@@ -71,7 +71,7 @@ export const AccountHistoryContainer = () => {
|
||||
const { data: assets } = useAssetsDataProvider();
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Connect wallet')}</Splash>;
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,6 @@ import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
import {
|
||||
@@ -62,29 +61,19 @@ export const Portfolio = () => {
|
||||
<PortfolioGridChild>
|
||||
<Tabs storageKey="console-portfolio-top">
|
||||
<Tab id="account-history" name={t('Account history')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountHistoryContainer />
|
||||
</VegaWalletContainer>
|
||||
<AccountHistoryContainer />
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</VegaWalletContainer>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<VegaWalletContainer>
|
||||
<OrdersContainer />
|
||||
</VegaWalletContainer>
|
||||
<OrdersContainer />
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<VegaWalletContainer>
|
||||
<LedgerContainer />
|
||||
</VegaWalletContainer>
|
||||
<LedgerContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</PortfolioGridChild>
|
||||
@@ -97,14 +86,10 @@ export const Portfolio = () => {
|
||||
<PortfolioGridChild>
|
||||
<Tabs storageKey="console-portfolio-bottom">
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountsContainer />
|
||||
</VegaWalletContainer>
|
||||
<AccountsContainer />
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')}>
|
||||
<VegaWalletContainer>
|
||||
<DepositsContainer />
|
||||
</VegaWalletContainer>
|
||||
<DepositsContainer />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
@@ -21,7 +20,7 @@ export const WithdrawalsContainer = () => {
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
|
||||
return (
|
||||
<VegaWalletContainer>
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<WithdrawalsTable
|
||||
data-testid="withdrawals-history"
|
||||
@@ -43,6 +42,6 @@ export const WithdrawalsContainer = () => {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</VegaWalletContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { AccountManager } from '@vegaprotocol/accounts';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const THROTTLE_UPDATE_TIME = 500;
|
||||
export const RISK_ACCEPTED_KEY = 'vega_risk_accepted';
|
||||
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||
export const MAINNET_WELCOME_HEADER = t(
|
||||
'Trade cash settled futures on the fully decentralised Vega network.'
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { FillsManager } from '@vegaprotocol/fills';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
||||
);
|
||||
return (
|
||||
<header className={headerClasses}>
|
||||
<div className="flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||
<div className="hidden lg:flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||
{title}
|
||||
</div>
|
||||
<div data-testid="header-summary" className="min-w-0">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const WalletIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
width="26"
|
||||
height="18"
|
||||
viewBox="0 0 26 18"
|
||||
className={className}
|
||||
className={classNames('fill-current', className)}
|
||||
data-testid="wallet-icon"
|
||||
>
|
||||
<path d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z" />
|
||||
|
||||
@@ -11,9 +11,9 @@ export const LayoutWithSidebar = () => {
|
||||
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid-rows-[min-content_1fr]',
|
||||
'grid-cols-[1fr_45px]',
|
||||
'lg:grid-cols-[1fr_350px_45px]'
|
||||
'grid-rows-[min-content_1fr_40px]',
|
||||
'lg:grid-rows-[min-content_1fr]',
|
||||
'lg:grid-cols-[1fr_350px_40px]'
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -40,7 +40,14 @@ export const LayoutWithSidebar = () => {
|
||||
>
|
||||
<SidebarContent />
|
||||
</aside>
|
||||
<div className="col-start-2 lg:col-start-3 bg-vega-clight-800 dark:bg-vega-cdark-800 border-l border-default">
|
||||
<div
|
||||
className={classNames(
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800',
|
||||
'border-t lg:border-l lg:border-t-0 border-default',
|
||||
'row-start-3 col-start-1 cols-span-full',
|
||||
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,10 +12,9 @@ import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
@@ -26,8 +25,6 @@ export const LiquidityContainer = ({
|
||||
marketId: string | undefined;
|
||||
filter?: Filter;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
|
||||
const gridStore = useLiquidityStore((store) => store.gridStore);
|
||||
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
|
||||
|
||||
@@ -60,7 +57,6 @@ export const LiquidityContainer = ({
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
|
||||
@@ -42,8 +42,6 @@ export const LiquidityHeader = () => {
|
||||
triggeringRatio,
|
||||
});
|
||||
|
||||
console.log(market);
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
|
||||
@@ -4,10 +4,12 @@ import { useParams } from 'react-router-dom';
|
||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const MarketHeader = () => {
|
||||
const { marketId } = useParams();
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
@@ -15,6 +17,8 @@ export const MarketHeader = () => {
|
||||
<Header
|
||||
title={
|
||||
<Popover
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={
|
||||
<HeaderTitle>
|
||||
{data.tradableInstrument.instrument.code}
|
||||
@@ -23,7 +27,10 @@ export const MarketHeader = () => {
|
||||
}
|
||||
alignOffset={-10}
|
||||
>
|
||||
<MarketSelector currentMarketId={marketId} />
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('MarketSelectorItem', () => {
|
||||
market={market}
|
||||
currentMarketId={market.id}
|
||||
style={{}}
|
||||
onSelect={jest.fn()}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
|
||||
@@ -17,10 +17,12 @@ export const MarketSelectorItem = ({
|
||||
market,
|
||||
style,
|
||||
currentMarketId,
|
||||
onSelect,
|
||||
}: {
|
||||
market: MarketMaybeWithDataAndCandles;
|
||||
style: CSSProperties;
|
||||
currentMarketId?: string;
|
||||
onSelect: (marketId: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div style={style} role="row">
|
||||
@@ -32,6 +34,7 @@ export const MarketSelectorItem = ({
|
||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||
market.id === currentMarketId,
|
||||
})}
|
||||
onClick={() => onSelect(market.id)}
|
||||
>
|
||||
<MarketData market={market} />
|
||||
</Link>
|
||||
@@ -80,7 +83,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-2/5" role="gridcell">
|
||||
<h3 className="text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||
{market.tradableInstrument.instrument.code}
|
||||
</h3>
|
||||
{mode && (
|
||||
@@ -90,7 +93,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
title={instrument.product.settlementAsset.symbol}
|
||||
data-testid="market-selector-price"
|
||||
role="gridcell"
|
||||
@@ -98,7 +101,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
{price} {instrument.product.settlementAsset.symbol}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
title={t('24h vol')}
|
||||
data-testid="market-selector-volume"
|
||||
role="gridcell"
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('MarketSelector', () => {
|
||||
it('renders only active markets', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(
|
||||
@@ -148,7 +148,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by product type', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by search term', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by asset', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by gained', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -256,7 +256,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by lost', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -272,7 +272,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by new', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MarketSelector = ({
|
||||
onSelect,
|
||||
}: {
|
||||
currentMarketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
}) => {
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
@@ -48,7 +48,7 @@ export const MarketSelector = ({
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector">
|
||||
<div className="pt-2 px-2 mb-2 w-[320px] lg:w-[584px]">
|
||||
<div className="pt-2 px-2 mb-2">
|
||||
<ProductSelector
|
||||
product={filter.product}
|
||||
onSelect={(product) => {
|
||||
@@ -147,16 +147,17 @@ const MarketList = ({
|
||||
loading: boolean;
|
||||
searchTerm: string;
|
||||
currentMarketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
noItems: string;
|
||||
}) => {
|
||||
const itemSize = 45;
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
const rect = listRef.current?.getBoundingClientRect();
|
||||
// allow virtualized list to grow until it runs out of space
|
||||
const height = rect
|
||||
const computedHeight = rect
|
||||
? Math.min(data.length * itemSize, window.innerHeight - rect.y)
|
||||
: 400;
|
||||
const height = Math.max(computedHeight, 45);
|
||||
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
@@ -199,7 +200,7 @@ const MarketList = ({
|
||||
|
||||
interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}
|
||||
|
||||
@@ -216,6 +217,7 @@ const ListItem = ({
|
||||
market={data.data[index]}
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -252,7 +254,11 @@ const List = ({
|
||||
|
||||
if (!data.length) {
|
||||
return (
|
||||
<div style={{ height }} data-testid="no-items">
|
||||
<div
|
||||
style={{ height }}
|
||||
className="flex items-center"
|
||||
data-testid="no-items"
|
||||
>
|
||||
<div className="mx-4 my-2 text-sm">{noItems}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './navbar';
|
||||
export * from './nav-header';
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelector } from '../market-selector';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { useState } from 'react';
|
||||
|
||||
/**
|
||||
* This is only rendered for the mobile navigation
|
||||
*/
|
||||
export const NavHeader = () => {
|
||||
const { marketId } = useParams();
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!marketId) return null;
|
||||
|
||||
return (
|
||||
<FullScreenPopover
|
||||
open={open}
|
||||
onOpenChange={(x) => {
|
||||
setOpen(x);
|
||||
}}
|
||||
trigger={
|
||||
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
|
||||
{data ? data.tradableInstrument.instrument.code : t('Select market')}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</FullScreenPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||
trigger: React.ReactNode | string;
|
||||
}
|
||||
|
||||
export const FullScreenPopover = ({
|
||||
trigger,
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverPrimitive.Trigger data-testid="popover-trigger">
|
||||
{trigger}
|
||||
</PopoverPrimitive.Trigger>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-testid="popover-content"
|
||||
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 text-default border border-default"
|
||||
sideOffset={5}
|
||||
>
|
||||
{children}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,42 +1,158 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { Navbar } from './navbar';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
jest.mock('@vegaprotocol/proposals', () => ({
|
||||
ProtocolUpgradeCountdown: () => null,
|
||||
}));
|
||||
|
||||
describe('Navbar', () => {
|
||||
const pubKey = 'pubKey';
|
||||
it('should be properly rendered', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Navbar theme="dark" />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
const pubKey = '000';
|
||||
const pubKeys = [
|
||||
{
|
||||
publicKey: pubKey,
|
||||
name: 'Pub key 0',
|
||||
},
|
||||
{
|
||||
publicKey: '111',
|
||||
name: 'Pub key 1',
|
||||
},
|
||||
];
|
||||
const marketId = 'abc';
|
||||
const navbarContent = 'navbar-menu-content';
|
||||
|
||||
const renderComponent = (
|
||||
initialEntries?: string[],
|
||||
walletContext?: Partial<VegaWalletContextShape>
|
||||
) => {
|
||||
const context = {
|
||||
pubKey,
|
||||
pubKeys,
|
||||
selectPubKey: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
...walletContext,
|
||||
} as VegaWalletContextShape;
|
||||
return render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<VegaWalletContext.Provider value={context}>
|
||||
<Navbar />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByTestId('Markets')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Trading')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Portfolio')).toBeInTheDocument();
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
useGlobalStore.setState({ marketId });
|
||||
});
|
||||
|
||||
it('should be properly rendered', () => {
|
||||
renderComponent();
|
||||
|
||||
const expectedLinks = [
|
||||
['/', ''],
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
];
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
links.forEach((link, i) => {
|
||||
const [href, text] = expectedLinks[i];
|
||||
expect(link).toHaveAttribute('href', href);
|
||||
expect(link).toHaveTextContent(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('Markets page route should not match empty market page', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter initialEntries={['/markets/all']}>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Navbar theme="dark" />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
renderComponent(['/markets/all']);
|
||||
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
|
||||
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
|
||||
'active'
|
||||
);
|
||||
expect(screen.getByTestId('Markets')).toHaveClass('active');
|
||||
expect(screen.getByTestId('Trading')).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('can open menu and navigate on small screens', async () => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Menu' }));
|
||||
|
||||
const menuEl = screen.getByTestId(navbarContent);
|
||||
expect(menuEl).toBeInTheDocument();
|
||||
const menu = within(menuEl);
|
||||
|
||||
const expectedLinks = [
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
];
|
||||
const links = menu.getAllByRole('link');
|
||||
links.forEach((link, i) => {
|
||||
const [href, text] = expectedLinks[i];
|
||||
expect(link).toHaveAttribute('href', href);
|
||||
expect(link).toHaveTextContent(text);
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Close menu' }));
|
||||
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can close menu by clicking overlay', async () => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Menu' }));
|
||||
expect(screen.getByTestId(navbarContent)).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByTestId('navbar-menu-overlay'));
|
||||
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can open wallet menu on small screens and change pubkey', async () => {
|
||||
const mockSelectPubKey = jest.fn();
|
||||
renderComponent(undefined, { selectPubKey: mockSelectPubKey });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||
|
||||
const menuEl = screen.getByTestId(navbarContent);
|
||||
expect(menuEl).toBeInTheDocument();
|
||||
const menu = within(menuEl);
|
||||
|
||||
expect(menu.getAllByTestId(/key-\d+-mobile/)).toHaveLength(pubKeys.length);
|
||||
|
||||
const activeKey = within(menu.getByTestId('key-000-mobile'));
|
||||
expect(activeKey.getByText(pubKeys[0].name)).toBeInTheDocument();
|
||||
expect(activeKey.getByTestId('icon-tick')).toBeInTheDocument();
|
||||
|
||||
const inactiveKey = within(menu.getByTestId('key-111-mobile'));
|
||||
await userEvent.click(inactiveKey.getByText(pubKeys[1].name));
|
||||
expect(mockSelectPubKey).toHaveBeenCalledWith(pubKeys[1].publicKey);
|
||||
});
|
||||
|
||||
it('can transfer and close menu', async () => {
|
||||
renderComponent();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||
|
||||
const menuEl = screen.getByTestId(navbarContent);
|
||||
expect(menuEl).toBeInTheDocument();
|
||||
const menu = within(menuEl);
|
||||
|
||||
await userEvent.click(menu.getByText('Transfer'));
|
||||
|
||||
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can disconnect and close menu', async () => {
|
||||
const mockDisconnect = jest.fn();
|
||||
renderComponent(undefined, { disconnect: mockDisconnect });
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Wallet' }));
|
||||
|
||||
const menuEl = screen.getByTestId(navbarContent);
|
||||
expect(menuEl).toBeInTheDocument();
|
||||
const menu = within(menuEl);
|
||||
|
||||
await userEvent.click(menu.getByText('Disconnect'));
|
||||
|
||||
expect(mockDisconnect).toHaveBeenCalled();
|
||||
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,140 +1,400 @@
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import {
|
||||
DApp,
|
||||
NetworkSwitcher,
|
||||
TOKEN_GOVERNANCE,
|
||||
useEnvironment,
|
||||
useLinks,
|
||||
DocsLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import type { ButtonHTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEnvironment, DocsLinks, Networks } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
import {
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
ExternalLink,
|
||||
NavigationBreakpoint,
|
||||
NavigationTrigger,
|
||||
NavigationContent,
|
||||
VegaIconNames,
|
||||
VegaIcon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaIconNames, VegaIcon, VLogo } from '@vegaprotocol/ui-toolkit';
|
||||
import * as N from '@radix-ui/react-navigation-menu';
|
||||
import * as D from '@radix-ui/react-dialog';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import {
|
||||
ProtocolUpgradeCountdown,
|
||||
ProtocolUpgradeCountdownMode,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import classNames from 'classnames';
|
||||
import { VegaWalletMenu } from '../vega-wallet';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { WalletIcon } from '../icons/wallet';
|
||||
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
|
||||
|
||||
type MenuState = 'wallet' | 'nav' | null;
|
||||
type Theme = 'system' | 'yellow';
|
||||
|
||||
export const Navbar = ({
|
||||
children,
|
||||
theme = 'system',
|
||||
}: {
|
||||
theme: ComponentProps<typeof Navigation>['theme'];
|
||||
children?: ReactNode;
|
||||
theme?: Theme;
|
||||
}) => {
|
||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
// menu state for small screens
|
||||
const [menu, setMenu] = useState<MenuState>(null);
|
||||
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const isConnected = pubKey !== null;
|
||||
|
||||
const navTextClasses = 'text-vega-clight-200 dark:text-vega-cdark-200';
|
||||
const rootClasses = classNames(
|
||||
navTextClasses,
|
||||
'flex gap-3 h-10 pr-1',
|
||||
'border-b border-default',
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800'
|
||||
);
|
||||
return (
|
||||
<N.Root className={rootClasses}>
|
||||
<NavLink
|
||||
to="/"
|
||||
className={classNames('flex items-center px-3', {
|
||||
'bg-vega-yellow text-vega-clight-50': theme === 'yellow',
|
||||
'text-default': theme === 'system',
|
||||
})}
|
||||
>
|
||||
<VLogo className="w-4" />
|
||||
</NavLink>
|
||||
{/* Left section */}
|
||||
<div className="lg:hidden flex items-center">{children}</div>
|
||||
{/* Used to show header in nav on mobile */}
|
||||
<div className="hidden lg:block">
|
||||
<NavbarMenu onClick={() => setMenu(null)} />
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="ml-auto flex justify-end items-center gap-2">
|
||||
<ProtocolUpgradeCountdown />
|
||||
<NavbarMobileButton
|
||||
onClick={() => {
|
||||
if (isConnected) {
|
||||
setMenu((x) => (x === 'wallet' ? null : 'wallet'));
|
||||
} else {
|
||||
openVegaWalletDialog();
|
||||
}
|
||||
}}
|
||||
data-testid="navbar-mobile-wallet"
|
||||
>
|
||||
<span className="sr-only">{t('Wallet')}</span>
|
||||
<WalletIcon className="w-6" />
|
||||
</NavbarMobileButton>
|
||||
<NavbarMobileButton
|
||||
onClick={() => {
|
||||
setMenu((x) => (x === 'nav' ? null : 'nav'));
|
||||
}}
|
||||
data-testid="navbar-mobile-burger"
|
||||
>
|
||||
<span className="sr-only">{t('Menu')}</span>
|
||||
<BurgerIcon />
|
||||
</NavbarMobileButton>
|
||||
<div className="hidden lg:block">
|
||||
<VegaWalletConnectButton />
|
||||
</div>
|
||||
</div>
|
||||
{menu !== null && (
|
||||
<D.Root
|
||||
open={menu !== null}
|
||||
onOpenChange={(open) => setMenu((x) => (open ? x : null))}
|
||||
>
|
||||
<D.Overlay
|
||||
className="lg:hidden fixed inset-0 dark:bg-black/80 bg-black/50 z-20"
|
||||
data-testid="navbar-menu-overlay"
|
||||
/>
|
||||
<D.Content
|
||||
className={classNames(
|
||||
'lg:hidden',
|
||||
'fixed top-0 right-0 z-20 w-3/4 h-screen border-l border-default bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||
navTextClasses
|
||||
)}
|
||||
data-testid="navbar-menu-content"
|
||||
>
|
||||
<div className="flex justify-end items-center h-10 p-1">
|
||||
<NavbarMobileButton onClick={() => setMenu(null)}>
|
||||
<span className="sr-only">{t('Close menu')}</span>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={24} />
|
||||
</NavbarMobileButton>
|
||||
</div>
|
||||
{menu === 'nav' && <NavbarMenu onClick={() => setMenu(null)} />}
|
||||
{menu === 'wallet' && <VegaWalletMenu setMenu={setMenu} />}
|
||||
</D.Content>
|
||||
</D.Root>
|
||||
)}
|
||||
</N.Root>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* List of links or dropdown triggers to show in the main section
|
||||
* of the navigation
|
||||
*/
|
||||
const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
// If we have a stored marketId make Trade link go to that market
|
||||
// otherwise always go to /markets/all
|
||||
const tradingPath = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKET]();
|
||||
: Links[Routes.MARKET]('');
|
||||
|
||||
return (
|
||||
<Navigation
|
||||
appName="console"
|
||||
theme={theme}
|
||||
actions={
|
||||
<>
|
||||
<ProtocolUpgradeCountdown
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<VegaWalletConnectButton />
|
||||
</>
|
||||
}
|
||||
breakpoints={[521, 1122]}
|
||||
>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
||||
hide={[NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<NetworkSwitcher className="[.drawer-content_&]:w-full" />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem>
|
||||
<NavigationLink data-testid="Markets" to={Links[Routes.MARKETS]()}>
|
||||
<div className="lg:flex lg:h-full gap-3">
|
||||
<NavbarList>
|
||||
<NavbarItem>
|
||||
<NavbarTrigger data-testid="navbar-network-switcher-trigger">
|
||||
{envNameMapping[VEGA_ENV]}
|
||||
</NavbarTrigger>
|
||||
<NavbarContent data-testid="navbar-content-network-switcher">
|
||||
<ul className="lg:p-4">
|
||||
{[Networks.MAINNET, Networks.TESTNET].map((n) => {
|
||||
const url = VEGA_NETWORKS[n];
|
||||
if (!url) return;
|
||||
return (
|
||||
<NavbarSubItem key={n}>
|
||||
<NavbarLink to={url}>{envNameMapping[n]}</NavbarLink>
|
||||
</NavbarSubItem>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</NavbarContent>
|
||||
</NavbarItem>
|
||||
</NavbarList>
|
||||
<NavbarListDivider />
|
||||
<NavbarList>
|
||||
<NavbarItem>
|
||||
<NavbarLink to={Links[Routes.MARKETS]()} onClick={onClick}>
|
||||
{t('Markets')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink data-testid="Trading" to={tradingPath} end>
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarLink to={tradingPath} onClick={onClick}>
|
||||
{t('Trading')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink
|
||||
data-testid="Portfolio"
|
||||
to={Links[Routes.PORTFOLIO]()}
|
||||
>
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarLink to={Links[Routes.PORTFOLIO]()} onClick={onClick}>
|
||||
{t('Portfolio')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavExternalLink href={tokenLink(TOKEN_GOVERNANCE)}>
|
||||
{t('Governance')}
|
||||
</NavExternalLink>
|
||||
</NavigationItem>
|
||||
{DocsLinks?.NEW_TO_VEGA && GITHUB_FEEDBACK_URL && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger>{t('Resources')}</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
<NavigationItem>
|
||||
<NavExternalLink href={DocsLinks.NEW_TO_VEGA}>
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
||||
<NavbarContent data-testid="navbar-content-resources">
|
||||
<ul className="lg:p-4">
|
||||
{DocsLinks?.NEW_TO_VEGA && (
|
||||
<NavbarSubItem>
|
||||
<NavbarLinkExternal to={DocsLinks?.NEW_TO_VEGA}>
|
||||
{t('Docs')}
|
||||
</NavExternalLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavExternalLink href={GITHUB_FEEDBACK_URL}>
|
||||
</NavbarLinkExternal>
|
||||
</NavbarSubItem>
|
||||
)}
|
||||
{GITHUB_FEEDBACK_URL && (
|
||||
<NavbarSubItem>
|
||||
<NavbarLinkExternal to={GITHUB_FEEDBACK_URL}>
|
||||
{t('Give Feedback')}
|
||||
</NavExternalLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink
|
||||
data-testid="Disclaimer"
|
||||
to={Links[Routes.DISCLAIMER]()}
|
||||
>
|
||||
{t('Disclaimer')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
)}
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
</NavbarLinkExternal>
|
||||
</NavbarSubItem>
|
||||
)}
|
||||
<NavbarSubItem>
|
||||
<NavbarLink to={Links[Routes.DISCLAIMER]()} onClick={onClick}>
|
||||
{t('Disclaimer')}
|
||||
</NavbarLink>
|
||||
</NavbarSubItem>
|
||||
</ul>
|
||||
</NavbarContent>
|
||||
</NavbarItem>
|
||||
</NavbarList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NavExternalLink = ({
|
||||
/**
|
||||
* Wrapper for radix-ux Trigger for consistent styles
|
||||
*/
|
||||
const NavbarTrigger = ({
|
||||
children,
|
||||
href,
|
||||
...props
|
||||
}: N.NavigationMenuTriggerProps) => {
|
||||
return (
|
||||
<N.Trigger
|
||||
{...props}
|
||||
onPointerMove={preventHover}
|
||||
onPointerLeave={preventHover}
|
||||
className={classNames(
|
||||
'w-full lg:w-auto lg:h-full',
|
||||
'flex items-center justify-between lg:justify-center gap-2 px-6 py-2 lg:p-0',
|
||||
'text-lg lg:text-sm',
|
||||
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||
</N.Trigger>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for react-router-dom NavLink for consistent styles
|
||||
*/
|
||||
const NavbarLink = ({
|
||||
children,
|
||||
to,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
href: string;
|
||||
to: string;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<ExternalLink href={href}>
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{children}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</span>
|
||||
</ExternalLink>
|
||||
<N.Link asChild={true}>
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames(
|
||||
'block lg:flex lg:h-full flex-col justify-center',
|
||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
const borderClasses = {
|
||||
'border-b-2': true,
|
||||
'border-transparent': !isActive,
|
||||
'border-vega-yellow lg:group-[.navbar-content]:border-transparent':
|
||||
isActive,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classNames('lg:border-0', borderClasses, {
|
||||
'text-vega-clight-50 dark:text-vega-cdark-50': isActive,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
'hidden lg:block absolute left-0 bottom-0 w-full h-0',
|
||||
borderClasses
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
</N.Link>
|
||||
);
|
||||
};
|
||||
|
||||
const NavbarItem = (props: N.NavigationMenuItemProps) => {
|
||||
return <N.Item {...props} className="relative" />;
|
||||
};
|
||||
|
||||
const NavbarSubItem = (props: LiHTMLAttributes<HTMLElement>) => {
|
||||
return <li {...props} className="lg:mb-4 lg:last:mb-0" />;
|
||||
};
|
||||
|
||||
const NavbarList = (props: N.NavigationMenuListProps) => {
|
||||
return <N.List {...props} className="lg:flex lg:h-full gap-6" />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Content that gets rendered when a sub section of the navbar is shown
|
||||
*/
|
||||
const NavbarContent = (props: N.NavigationMenuContentProps) => {
|
||||
return (
|
||||
<N.Content
|
||||
{...props}
|
||||
className={classNames(
|
||||
'group navbar-content',
|
||||
'lg:absolute lg:mt-2 pl-2 lg:pl-0 z-20 lg:min-w-[290px]',
|
||||
'lg:bg-vega-clight-700 lg:dark:bg-vega-cdark-700',
|
||||
'lg:border border-vega-clight-500 dark:border-vega-cdark-500 lg:rounded'
|
||||
)}
|
||||
onPointerEnter={preventHover}
|
||||
onPointerLeave={preventHover}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* NavbarLink with OPEN_EXTERNAL icon
|
||||
*/
|
||||
const NavbarLinkExternal = ({
|
||||
children,
|
||||
to,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
to: string;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<N.Link asChild={true}>
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames(
|
||||
'flex lg:inline-flex gap-2 justify-between items-center relative',
|
||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||
)}
|
||||
onClick={onClick}
|
||||
target="_blank"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</NavLink>
|
||||
</N.Link>
|
||||
);
|
||||
};
|
||||
|
||||
const BurgerIcon = () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 16 16"
|
||||
className="w-full stroke-current"
|
||||
>
|
||||
<line x1={0.5} x2={15.5} y1={3.5} y2={3.5} />
|
||||
<line x1={0.5} x2={15.5} y1={11.5} y2={11.5} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const NavbarListDivider = () => {
|
||||
return (
|
||||
<div className="py-2 px-6 lg:px-0" role="separator">
|
||||
<div className="h-px lg:h-full w-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Button component to avoid repeating styles for buttons shown on small screens
|
||||
*/
|
||||
const NavbarMobileButton = (props: ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(
|
||||
'w-8 h-8 lg:hidden flex items-center p-1 rounded ',
|
||||
'hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500',
|
||||
'hover:text-vega-clight-50 dark:hover:text-vega-cdark-50'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const envNameMapping: Record<Networks, string> = {
|
||||
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
|
||||
[Networks.CUSTOM]: t('Custom'),
|
||||
[Networks.DEVNET]: t('Devnet'),
|
||||
[Networks.STAGNET1]: t('Stagnet'),
|
||||
[Networks.TESTNET]: t('Fairground testnet'),
|
||||
[Networks.MAINNET_MIRROR]: t('Mirror'),
|
||||
[Networks.MAINNET]: t('Mainnet'),
|
||||
};
|
||||
|
||||
// https://github.com/radix-ui/primitives/issues/1630
|
||||
// eslint-disable-next-line
|
||||
const preventHover = (e: any) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { OrderbookManager } from '@vegaprotocol/market-depth';
|
||||
import { useCreateOrderStore } from '@vegaprotocol/orders';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useStopOrderFormValues } from '@vegaprotocol/deal-ticket';
|
||||
|
||||
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
||||
const useOrderStoreRef = useCreateOrderStore();
|
||||
const updateOrder = useOrderStoreRef((store) => store.update);
|
||||
const updateStoredFormValues = useStopOrderFormValues(
|
||||
(state) => state.update
|
||||
);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
return (
|
||||
<OrderbookManager
|
||||
@@ -12,9 +16,11 @@ export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
||||
onClick={({ price, size }) => {
|
||||
if (price) {
|
||||
updateOrder(marketId, { price });
|
||||
updateStoredFormValues(marketId, { price });
|
||||
}
|
||||
if (size) {
|
||||
updateOrder(marketId, { size });
|
||||
updateStoredFormValues(marketId, { size });
|
||||
}
|
||||
setView({ type: ViewType.Order });
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { Filter, OrderListManager } from '@vegaprotocol/orders';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { OrderListManager } from '@vegaprotocol/orders';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
|
||||
@@ -19,6 +19,10 @@ jest.mock('../settings', () => ({
|
||||
Settings: () => <div data-testid="settings" />,
|
||||
}));
|
||||
|
||||
jest.mock('../welcome-dialog', () => ({
|
||||
GetStarted: () => <div data-testid="get-started" />,
|
||||
}));
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Tooltip } from '../../components/tooltip';
|
||||
import { WithdrawContainer } from '../withdraw-container';
|
||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { GetStarted } from '../welcome-dialog';
|
||||
|
||||
const STORAGE_KEY = 'vega_sidebar_store';
|
||||
|
||||
@@ -51,9 +52,10 @@ type SidebarView =
|
||||
};
|
||||
|
||||
export const Sidebar = () => {
|
||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||
return (
|
||||
<div className="flex flex-col gap-2 h-full py-1" data-testid="sidebar">
|
||||
<nav className="flex flex-col items-center gap-4 p-1">
|
||||
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
||||
<nav className={navClasses}>
|
||||
{/* sidebar options that always show */}
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
@@ -102,7 +104,7 @@ export const Sidebar = () => {
|
||||
/>
|
||||
</Routes>
|
||||
</nav>
|
||||
<nav className="mt-auto flex flex-col items-center gap-4 p-1">
|
||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||
<SidebarButton
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
@@ -161,7 +163,7 @@ const SidebarButton = ({
|
||||
const SidebarDivider = () => {
|
||||
return (
|
||||
<div
|
||||
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-4 h-px"
|
||||
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-px h-4 lg:w-4 lg:h-px"
|
||||
role="separator"
|
||||
/>
|
||||
);
|
||||
@@ -183,6 +185,7 @@ export const SidebarContent = () => {
|
||||
setView({ type: ViewType.Deposit, assetId })
|
||||
}
|
||||
/>
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
} else {
|
||||
@@ -206,6 +209,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Deposit')}>
|
||||
<DepositContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -214,6 +218,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Withdraw')}>
|
||||
<WithdrawContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -222,6 +227,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Transfer')}>
|
||||
<TransferContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './stop-orders-container';
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { StopOrdersManager } from '@vegaprotocol/orders';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
|
||||
export const StopOrdersContainer = () => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
|
||||
const gridStore = useStopOrdersStore((store) => store.gridStore);
|
||||
const updateGridStore = useStopOrdersStore((store) => store.updateGridStore);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StopOrdersManager
|
||||
partyId={pubKey}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useStopOrdersStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_fills_store',
|
||||
})
|
||||
);
|
||||
+11
-1
@@ -28,7 +28,17 @@ describe('VegaWalletConnectButton', () => {
|
||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||
|
||||
const button = screen.getByTestId('connect-vega-wallet');
|
||||
expect(button).toHaveTextContent('Connect Vega wallet');
|
||||
expect(button).toHaveTextContent('Get started');
|
||||
fireEvent.click(button);
|
||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render "Connect" when browser wallet is detected', () => {
|
||||
window.vega = window.vega || ({} as Vega);
|
||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||
|
||||
const button = screen.getByTestId('connect-vega-wallet');
|
||||
expect(button).toHaveTextContent('Connect');
|
||||
fireEvent.click(button);
|
||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
+100
-253
@@ -1,148 +1,27 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import classNames from 'classnames';
|
||||
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
Drawer,
|
||||
DropdownMenuSeparator,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
TradingButton as Button,
|
||||
Intent,
|
||||
TradingDropdown,
|
||||
TradingDropdownTrigger,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownSeparator,
|
||||
TradingDropdownItem,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownItemIndicator,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { WalletIcon } from '../icons/wallet';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
const MobileWalletButton = ({
|
||||
isConnected,
|
||||
activeKey,
|
||||
}: {
|
||||
isConnected?: boolean;
|
||||
activeKey?: PubKey;
|
||||
}) => {
|
||||
const { pubKeys, selectPubKey, disconnect, fetchPubKeys } = useVegaWallet();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const isYellow = VEGA_ENV === Networks.TESTNET;
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const mobileDisconnect = useCallback(() => {
|
||||
setDrawerOpen(false);
|
||||
disconnect();
|
||||
}, [disconnect]);
|
||||
const openDrawer = useCallback(() => {
|
||||
if (!isConnected) {
|
||||
openVegaWalletDialog();
|
||||
setDrawerOpen(false);
|
||||
} else {
|
||||
if (fetchPubKeys) {
|
||||
fetchPubKeys();
|
||||
}
|
||||
setDrawerOpen(!drawerOpen);
|
||||
}
|
||||
}, [drawerOpen, fetchPubKeys, isConnected, openVegaWalletDialog]);
|
||||
|
||||
const iconClass = drawerOpen
|
||||
? 'hidden'
|
||||
: isYellow
|
||||
? 'fill-black'
|
||||
: 'fill-white';
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||
|
||||
const walletButton = (
|
||||
<button
|
||||
className="my-2 transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]"
|
||||
onClick={openDrawer}
|
||||
data-testid="connect-vega-wallet-mobile"
|
||||
>
|
||||
<WalletIcon className={iconClass} />
|
||||
</button>
|
||||
);
|
||||
const onSelectItem = useCallback(
|
||||
(pubkey: string) => {
|
||||
setDrawerOpen(false);
|
||||
selectPubKey(pubkey);
|
||||
},
|
||||
[selectPubKey]
|
||||
);
|
||||
return (
|
||||
<div className="lg:hidden overflow-hidden flex" ref={setContainer}>
|
||||
<Drawer
|
||||
dataTestId="wallets-drawer"
|
||||
open={drawerOpen}
|
||||
onChange={setDrawerOpen}
|
||||
container={container}
|
||||
trigger={walletButton}
|
||||
>
|
||||
<div className="border-l border-default p-2 gap-4 flex flex-col w-full h-full bg-white dark:bg-black dark:text-white justify-between">
|
||||
<div className="flex h-5 justify-end">
|
||||
<button
|
||||
className="transition-all flex flex-col justify-around gap-3 p-2 relative h-[34px]"
|
||||
onClick={() => setDrawerOpen(false)}
|
||||
data-testid="connect-vega-wallet-mobile-close"
|
||||
>
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'w-[26px] h-[2px] bg-black dark:bg-white transition-all translate-y-[7.5px] rotate-45',
|
||||
{
|
||||
hidden: !drawerOpen,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'w-[26px] h-[2px] bg-black dark:bg-white transition-all -translate-y-[7.5px] -rotate-45',
|
||||
{
|
||||
hidden: !drawerOpen,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
</button>
|
||||
</div>
|
||||
<div className="grow my-4" role="list">
|
||||
{(pubKeys || []).map((pk) => (
|
||||
<KeypairListItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
isActive={activeKey?.publicKey === pk.publicKey}
|
||||
onSelectItem={onSelectItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 m-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDrawerOpen(false);
|
||||
setView({ type: ViewType.Transfer });
|
||||
}}
|
||||
fill
|
||||
>
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
<Button onClick={mobileDisconnect} fill>
|
||||
{t('Disconnect')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const VegaWalletConnectButton = () => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
@@ -159,103 +38,110 @@ export const VegaWalletConnectButton = () => {
|
||||
fetchPubKeys,
|
||||
} = useVegaWallet();
|
||||
const isConnected = pubKey !== null;
|
||||
|
||||
const walletInstalled = isBrowserWalletInstalled();
|
||||
const activeKey = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
|
||||
if (isConnected && pubKeys) {
|
||||
return (
|
||||
<>
|
||||
<div className="hidden lg:block">
|
||||
<DropdownMenu
|
||||
open={dropdownOpen}
|
||||
trigger={
|
||||
<DropdownMenuTrigger
|
||||
data-testid="manage-vega-wallet"
|
||||
<TradingDropdown
|
||||
open={dropdownOpen}
|
||||
trigger={
|
||||
<TradingDropdownTrigger
|
||||
data-testid="manage-vega-wallet"
|
||||
onClick={() => {
|
||||
if (fetchPubKeys) {
|
||||
fetchPubKeys();
|
||||
}
|
||||
setDropdownOpen(!dropdownOpen);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />}
|
||||
>
|
||||
{activeKey && <span className="uppercase">{activeKey.name}</span>}
|
||||
{' | '}
|
||||
{truncateByChars(pubKey)}
|
||||
</Button>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent
|
||||
onInteractOutside={() => setDropdownOpen(false)}
|
||||
sideOffset={12}
|
||||
side="bottom"
|
||||
align="end"
|
||||
onEscapeKeyDown={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="min-w-[340px]" data-testid="keypair-list">
|
||||
<TradingDropdownRadioGroup
|
||||
value={pubKey}
|
||||
onValueChange={(value) => {
|
||||
selectPubKey(value);
|
||||
}}
|
||||
>
|
||||
{pubKeys.map((pk) => (
|
||||
<KeypairItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
active={pk.publicKey === pubKey}
|
||||
/>
|
||||
))}
|
||||
</TradingDropdownRadioGroup>
|
||||
<TradingDropdownSeparator />
|
||||
{!isReadOnly && (
|
||||
<TradingDropdownItem
|
||||
data-testid="wallet-transfer"
|
||||
onClick={() => {
|
||||
if (fetchPubKeys) {
|
||||
fetchPubKeys();
|
||||
}
|
||||
setDropdownOpen(!dropdownOpen);
|
||||
setView({ type: ViewType.Transfer });
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
{activeKey && (
|
||||
<span className="uppercase">{activeKey.name}</span>
|
||||
)}
|
||||
{': '}
|
||||
{truncateByChars(pubKey)}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent
|
||||
onInteractOutside={() => setDropdownOpen(false)}
|
||||
sideOffset={17}
|
||||
side="bottom"
|
||||
align="end"
|
||||
onEscapeKeyDown={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="min-w-[340px]" data-testid="keypair-list">
|
||||
<DropdownMenuRadioGroup
|
||||
value={pubKey}
|
||||
onValueChange={(value) => {
|
||||
selectPubKey(value);
|
||||
}}
|
||||
>
|
||||
{pubKeys.map((pk) => (
|
||||
<KeypairItem key={pk.publicKey} pk={pk} />
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
<DropdownMenuSeparator />
|
||||
{!isReadOnly && (
|
||||
<DropdownMenuItem
|
||||
data-testid="wallet-transfer"
|
||||
onClick={() => {
|
||||
setView({ type: ViewType.Transfer });
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
{t('Transfer')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
|
||||
{t('Disconnect')}
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<MobileWalletButton isConnected activeKey={activeKey} />
|
||||
</>
|
||||
{t('Transfer')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
<TradingDropdownItem data-testid="disconnect" onClick={disconnect}>
|
||||
{t('Disconnect')}
|
||||
</TradingDropdownItem>
|
||||
</div>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
data-testid="connect-vega-wallet"
|
||||
onClick={openVegaWalletDialog}
|
||||
size="sm"
|
||||
className="hidden lg:block"
|
||||
>
|
||||
<span className="whitespace-nowrap">{t('Connect Vega wallet')}</span>
|
||||
</Button>
|
||||
<MobileWalletButton />
|
||||
</>
|
||||
<Button
|
||||
data-testid="connect-vega-wallet"
|
||||
onClick={openVegaWalletDialog}
|
||||
size="small"
|
||||
intent={Intent.None}
|
||||
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||
>
|
||||
<span className="whitespace-nowrap uppercase">
|
||||
{walletInstalled ? t('Connect') : t('Get started')}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
||||
const KeypairItem = ({ pk, active }: { pk: PubKey; active: boolean }) => {
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
|
||||
return (
|
||||
<DropdownMenuRadioItem value={pk.publicKey}>
|
||||
<div className="flex-1 mr-2" data-testid={`key-${pk.publicKey}`}>
|
||||
<span className="mr-2">
|
||||
<span>
|
||||
<span className="uppercase">{pk.name}</span>:{' '}
|
||||
{truncateByChars(pk.publicKey)}
|
||||
</span>
|
||||
<TradingDropdownRadioItem value={pk.publicKey}>
|
||||
<div
|
||||
className={classNames('flex-1 mr-2', {
|
||||
'text-default': active,
|
||||
'text-muted': !active,
|
||||
})}
|
||||
data-testid={`key-${pk.publicKey}`}
|
||||
>
|
||||
<span className={classNames('mr-2 uppercase')}>
|
||||
{pk.name}
|
||||
{' | '}
|
||||
{truncateByChars(pk.publicKey)}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
@@ -270,46 +156,7 @@ const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||
</span>
|
||||
</div>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairListItem = ({
|
||||
pk,
|
||||
isActive,
|
||||
onSelectItem,
|
||||
}: {
|
||||
pk: PubKey;
|
||||
isActive: boolean;
|
||||
onSelectItem: (pk: string) => void;
|
||||
}) => {
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||
data-testid={`key-${pk.publicKey}-mobile`}
|
||||
>
|
||||
<span className="flex gap-2 items-center mr-2">
|
||||
<button onClick={() => onSelectItem(pk.publicKey)}>
|
||||
<span className="uppercase">{pk.name}</span>
|
||||
</button>
|
||||
{isActive && <VegaIcon name={VegaIconNames.TICK} />}
|
||||
</span>
|
||||
<span className="flex gap-2 items-center">
|
||||
{truncateByChars(pk.publicKey)}{' '}
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
data-testid="copy-vega-public-key"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||
</span>
|
||||
</div>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { VegaWalletMenu } from './vega-wallet-menu';
|
||||
@@ -0,0 +1,106 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
TradingButton as Button,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { useVegaWallet, type PubKey } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const VegaWalletMenu = ({
|
||||
setMenu,
|
||||
}: {
|
||||
setMenu: (open: 'nav' | 'wallet' | null) => void;
|
||||
}) => {
|
||||
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
const activeKey = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
|
||||
const onSelectItem = useCallback(
|
||||
(pubkey: string) => {
|
||||
selectPubKey(pubkey);
|
||||
},
|
||||
[selectPubKey]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grow my-4" role="list">
|
||||
{(pubKeys || []).map((pk) => (
|
||||
<KeypairListItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
isActive={activeKey?.publicKey === pk.publicKey}
|
||||
onSelectItem={onSelectItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 m-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setView({ type: ViewType.Transfer });
|
||||
setMenu(null);
|
||||
}}
|
||||
>
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await disconnect();
|
||||
setMenu(null);
|
||||
}}
|
||||
>
|
||||
{t('Disconnect')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairListItem = ({
|
||||
pk,
|
||||
isActive,
|
||||
onSelectItem,
|
||||
}: {
|
||||
pk: PubKey;
|
||||
isActive: boolean;
|
||||
onSelectItem: (pk: string) => void;
|
||||
}) => {
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||
data-testid={`key-${pk.publicKey}-mobile`}
|
||||
>
|
||||
<span className="flex gap-2 items-center mr-2">
|
||||
<button type="button" onClick={() => onSelectItem(pk.publicKey)}>
|
||||
<span className="uppercase">{pk.name}</span>
|
||||
</button>
|
||||
{isActive && <VegaIcon name={VegaIconNames.TICK} />}
|
||||
</span>
|
||||
<span className="flex gap-2 items-center">
|
||||
{truncateByChars(pk.publicKey)}{' '}
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="copy-vega-public-key"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import classNames from 'classnames';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExternalLink, Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
isBrowserWalletInstalled,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import * as constants from '../constants';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
export const GetStarted = ({ lead }: Props) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const onButtonClick = () => {
|
||||
openVegaWalletDialog();
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
|
||||
if (!pubKey && !isBrowserWalletInstalled()) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-col bg-vega-blue-300 dark:bg-vega-blue-700 border border-vega-blue-350 dark:border-vega-blue-650 px-6 gap-4',
|
||||
{ 'py-4': lead },
|
||||
{ 'mt-8 py-6': !lead }
|
||||
)}
|
||||
data-testid="get-started-banner"
|
||||
>
|
||||
{lead && <h2>{lead}</h2>}
|
||||
<h3>{t('Get started')}</h3>
|
||||
<div>
|
||||
<ul className="list-decimal list-inside">
|
||||
<li>{t('Get a Vega wallet')}</li>
|
||||
<li>{t('Connect')}</li>
|
||||
<li>{t('Deposit funds')}</li>
|
||||
<li>{t('Open a position')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<TradingButton
|
||||
intent={Intent.Info}
|
||||
onClick={onButtonClick}
|
||||
className="block w-full"
|
||||
data-testid="get-started-button"
|
||||
>
|
||||
{t('Get started')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
{VEGA_ENV === Networks.MAINNET && (
|
||||
<p className="text-sm">
|
||||
{t('Experiment for free with virtual assets on')}{' '}
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Fairground Testnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
{VEGA_ENV === Networks.TESTNET && (
|
||||
<p className="text-sm">
|
||||
{t('Ready to trade with real funds?')}{' '}
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Switch to Mainnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './welcome-dialog';
|
||||
export * from './risk-message';
|
||||
export * from './get-started';
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
|
||||
const mockEnvDefinitions = {
|
||||
VEGA_CONFIG_URL: 'https://config.url',
|
||||
VEGA_URL: 'https://test.url',
|
||||
VEGA_NETWORKS: JSON.stringify({}),
|
||||
};
|
||||
|
||||
describe('Risk notice dialog', () => {
|
||||
const mockOnClose = jest.fn();
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it.each`
|
||||
assertion | network
|
||||
${'displays'} | ${Networks.CUSTOM}
|
||||
${'displays'} | ${Networks.DEVNET}
|
||||
${'displays'} | ${Networks.TESTNET}
|
||||
`(
|
||||
'$assertion a generic message on $network',
|
||||
async ({ assertion, network }) => {
|
||||
// @ts-ignore ignore mock implementation
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
...mockEnvDefinitions,
|
||||
VEGA_ENV: network,
|
||||
}));
|
||||
|
||||
render(<RiskNoticeDialog onClose={mockOnClose} network={network} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
new RegExp(
|
||||
`This application for trading on Vega is connected to ${network}`
|
||||
)
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Continue',
|
||||
});
|
||||
fireEvent.click(button);
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
Link,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { RISK_ACCEPTED_KEY } from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import type { Networks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
network: Networks;
|
||||
}
|
||||
export const RiskNoticeDialog = ({ onClose, network }: Props) => {
|
||||
const [, setValue] = useLocalStorage(RISK_ACCEPTED_KEY);
|
||||
|
||||
const handleAcceptRisk = () => {
|
||||
onClose();
|
||||
setValue('true');
|
||||
};
|
||||
|
||||
return (
|
||||
<TestnetContent network={network} handleAcceptRisk={handleAcceptRisk} />
|
||||
);
|
||||
};
|
||||
|
||||
const TestnetContent = ({
|
||||
network,
|
||||
handleAcceptRisk,
|
||||
}: {
|
||||
network: Networks;
|
||||
handleAcceptRisk: () => void;
|
||||
}) => {
|
||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||
return (
|
||||
<>
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'This application for trading on Vega is connected to %s, meaning you are free to try out trading with virtual assets and no risk.',
|
||||
[network]
|
||||
)}
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'Your Vega wallet must also be connected to %s, and your Ethereum wallet must be connected to Sepolia.',
|
||||
[network]
|
||||
)}
|
||||
</p>
|
||||
{GITHUB_FEEDBACK_URL && DocsLinks && (
|
||||
<ul className="list-disc pl-4">
|
||||
<li className="mb-1">
|
||||
<Link href={ExternalLinks.VEGA_WALLET_URL} target="_blank">
|
||||
<span className="underline">{t('Get a Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<Link href={DocsLinks.VEGA_WALLET_TOOLS_URL} target="_blank">
|
||||
<span className="underline">{t('Learn about Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<Link href={GITHUB_FEEDBACK_URL} target="_blank">
|
||||
<span className="underline">{t('Provide feedback')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
<div className="my-4">
|
||||
<TelemetryApproval
|
||||
helpText={t(
|
||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAcceptRisk}>{t('Continue')}</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { GetStarted } from './get-started';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import * as constants from '../constants';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
? t('Start trading on the worlds most advanced decentralised exchange.')
|
||||
: t(
|
||||
'Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.'
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col pb-2">
|
||||
<div className="flex gap-8">
|
||||
<div className="w-1/2 flex flex-col justify-between pt-3">
|
||||
<ul className="ml-0">
|
||||
<li className="my-3 flex gap-3 text-default">
|
||||
<div className="shrink-0 pt-1.5">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 30 30"
|
||||
fill="currentColor"
|
||||
>
|
||||
<rect x="14" y="20" width="2" height="2" />
|
||||
<rect x="12" y="18" width="2" height="2" />
|
||||
<rect x="10" y="8" width="2" height="10" />
|
||||
<rect x="16" y="18" width="2" height="2" />
|
||||
<rect x="20" y="16" width="2" height="2" />
|
||||
<rect x="18" y="8" width="2" height="8" />
|
||||
<rect x="28" y="2" width="2" height="26" />
|
||||
<rect y="2" width="2" height="26" />
|
||||
<rect
|
||||
x="28"
|
||||
width="2"
|
||||
height="26"
|
||||
transform="rotate(90 28 0)"
|
||||
/>
|
||||
<rect
|
||||
x="28"
|
||||
y="28"
|
||||
width="2"
|
||||
height="26"
|
||||
transform="rotate(90 28 28)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg">{t('Trade with no KYC')}</h3>
|
||||
<span className="text-sm text-vega-clight-100 dark:text-vega-cdark-100 leading-4">
|
||||
{t(
|
||||
'Pseudonomously trade Futures markets. Spot and perps comming soon'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li className="my-3 flex gap-3 text-vega-clight-50 dark:text-vega-cdark-50">
|
||||
<div className="shrink-0 pt-1.5">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 30 30"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g clip-path="url(#clip0_5168_49089)">
|
||||
<path d="M22 7H18V9H22V7Z" />
|
||||
<path d="M22 13H18V15H22V13Z" />
|
||||
<path d="M24 17H16V19H24V17Z" />
|
||||
<path d="M24 9H22V13H24V9Z" />
|
||||
<path d="M18 9H16V13H18V9Z" />
|
||||
<path d="M6 19H4V23H6V19Z" />
|
||||
<path d="M16 19H14V23H16V19Z" />
|
||||
<path d="M26 19H24V23H26V19Z" />
|
||||
<path d="M12 7H8V9H12V7Z" />
|
||||
<path d="M12 13H8V15H12V13Z" />
|
||||
<path d="M14 17H6V19H14V17Z" />
|
||||
<path d="M14 9H12V13H14V9Z" />
|
||||
<path d="M8 9H6V13H8V9Z" />
|
||||
<path d="M30 2H28V28H30V2Z" />
|
||||
<path d="M2 2H0V28H2V2Z" />
|
||||
<path d="M28 0H2V2H28V0Z" />
|
||||
<path d="M28 28H2V30H28V28Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5168_49089">
|
||||
<rect width="30" height="30" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg">
|
||||
{t('Community generated trading pairs')}
|
||||
</h3>
|
||||
<span className="text-sm text-vega-clight-100 dark:text-vega-cdark-100 leading-4">
|
||||
{t('All markets are proposed and enacted by the community')}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li className="my-3 flex gap-3 text-vega-clight-50 dark:text-vega-cdark-50">
|
||||
<div className="shrink-0 pt-1.5">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 30 30"
|
||||
fill="currentColor"
|
||||
>
|
||||
<rect x="6" y="16" width="4" height="2" />
|
||||
<rect x="10" y="14" width="2" height="2" />
|
||||
<rect
|
||||
x="12"
|
||||
y="24"
|
||||
width="4"
|
||||
height="2"
|
||||
transform="rotate(-90 12 24)"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="20"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(-90 10 20)"
|
||||
/>
|
||||
<rect
|
||||
x="20"
|
||||
y="18"
|
||||
width="4"
|
||||
height="2"
|
||||
transform="rotate(-180 20 18)"
|
||||
/>
|
||||
<rect
|
||||
x="16"
|
||||
y="20"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(-180 16 20)"
|
||||
/>
|
||||
<rect
|
||||
x="16"
|
||||
y="14"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(90 16 14)"
|
||||
/>
|
||||
<rect
|
||||
x="22"
|
||||
y="10"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(90 22 10)"
|
||||
/>
|
||||
<rect
|
||||
x="20"
|
||||
y="8"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(90 20 8)"
|
||||
/>
|
||||
<rect
|
||||
x="24"
|
||||
y="8"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(90 24 8)"
|
||||
/>
|
||||
<rect
|
||||
x="22"
|
||||
y="6"
|
||||
width="2"
|
||||
height="2"
|
||||
transform="rotate(90 22 6)"
|
||||
/>
|
||||
<rect x="12" y="10" width="2" height="4" />
|
||||
<rect x="28" y="2" width="2" height="26" />
|
||||
<rect y="2" width="2" height="26" />
|
||||
<rect
|
||||
x="28"
|
||||
width="2"
|
||||
height="26"
|
||||
transform="rotate(90 28 0)"
|
||||
/>
|
||||
<rect
|
||||
x="28"
|
||||
y="28"
|
||||
width="2"
|
||||
height="26"
|
||||
transform="rotate(90 28 28)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg">{t('Rewards')}</h3>
|
||||
<span className="text-sm text-vega-clight-100 dark:text-vega-cdark-100 leading-4">
|
||||
{t(
|
||||
'Earn rewards for trading, market making and providing liquidity'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<TradingButton
|
||||
onClick={browseMarkets}
|
||||
className="block w-full"
|
||||
data-testid="browse-markets-button"
|
||||
>
|
||||
{t('Browse the markets')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
<div className="w-1/2 -mr-3 flex flex-grow">
|
||||
<GetStarted lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,68 +1,52 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeMarketsProvider } from '@vegaprotocol/markets';
|
||||
import * as constants from '../constants';
|
||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
||||
import { WelcomeNoticeDialog } from './welcome-notice-dialog';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
import { isTestEnv } from '@vegaprotocol/utils';
|
||||
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||
import * as constants from '../constants';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { getConfig } from '@vegaprotocol/wallet';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { pathname } = useLocation();
|
||||
let dialogContent: React.ReactNode;
|
||||
let title = '';
|
||||
let size: 'small' | 'medium' = 'small';
|
||||
let onClose: ((open: boolean) => void) | undefined = undefined;
|
||||
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: activeMarketsProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
const [onBoardingViewed, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' && !isBrowserWalletInstalled() && !getConfig();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const shouldDisplayWelcomeDialog = useGlobalStore(
|
||||
(store) => store.shouldDisplayWelcomeDialog
|
||||
const onClose = () => {
|
||||
setOnboardingViewed('true');
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
};
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{VEGA_ENV}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
const isRiskDialogNeeded =
|
||||
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
|
||||
|
||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
||||
|
||||
const onCloseDialog = useCallback(() => {
|
||||
update({
|
||||
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
|
||||
});
|
||||
}, [update, isRiskDialogNeeded]);
|
||||
|
||||
if (isRiskDialogNeeded) {
|
||||
dialogContent = (
|
||||
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
|
||||
);
|
||||
title = t('Vega Console');
|
||||
size = 'medium';
|
||||
} else if (isWelcomeDialogNeeded && data?.length === 0) {
|
||||
dialogContent = <WelcomeNoticeDialog />;
|
||||
onClose = onCloseDialog;
|
||||
} else {
|
||||
dialogContent = null as React.ReactNode;
|
||||
}
|
||||
|
||||
return (
|
||||
return isOnboardingDialogNeeded ? (
|
||||
<Dialog
|
||||
open={Boolean(dialogContent)}
|
||||
open
|
||||
title={title}
|
||||
size={size}
|
||||
size="medium"
|
||||
onChange={onClose}
|
||||
intent={Intent.None}
|
||||
dataTestId="welcome-dialog"
|
||||
>
|
||||
{dialogContent}
|
||||
<WelcomeDialogContent />
|
||||
</Dialog>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DApp,
|
||||
Networks,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
TOKEN_PROPOSALS,
|
||||
useEnvironment,
|
||||
useLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposedMarkets } from './proposed-markets';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
export const WelcomeNoticeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const consoleFairgroundLink = useLinks(DApp.Console, Networks.TESTNET);
|
||||
const isMainnet = VEGA_ENV === Networks.MAINNET;
|
||||
return (
|
||||
<>
|
||||
<h1
|
||||
data-testid="welcome-notice-title"
|
||||
className="text-2xl uppercase mb-6 text-center font-alpha calt"
|
||||
>
|
||||
{t('Welcome to Console')}
|
||||
</h1>
|
||||
<p className="leading-6 mb-4">
|
||||
{t(
|
||||
'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:'
|
||||
)}
|
||||
</p>
|
||||
<ul className="list-[square] pl-4 mb-4">
|
||||
{isMainnet && (
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={consoleFairgroundLink()}>
|
||||
{t('Try out Console')}
|
||||
</ExternalLink>
|
||||
{t(' on Fairground, our Testnet')}
|
||||
</li>
|
||||
)}
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}>
|
||||
{t('View and vote for proposed markets')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<ExternalLink
|
||||
target="_blank"
|
||||
href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)}
|
||||
>
|
||||
{t('Propose a market')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={ExternalLinks.BLOG}>
|
||||
{t('Read about the mainnet launch')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
</ul>
|
||||
{isMainnet && (
|
||||
<TelemetryApproval
|
||||
helpText={t(
|
||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ProposedMarkets />
|
||||
</>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user