Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58eeb8201c | ||
|
|
14dbcb534c | ||
|
|
ec3eb0d286 | ||
|
|
faa838fe28 | ||
|
|
a4f42991f1 | ||
|
|
44dc72f3cf |
@@ -21,6 +21,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Cache node modules
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
@@ -38,6 +39,7 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --pure-lockfile
|
||||
@@ -49,11 +51,11 @@ jobs:
|
||||
uses: ./.github/workflows/lint-pr.yml
|
||||
secrets: inherit
|
||||
|
||||
lint-format:
|
||||
timeout-minutes: 20
|
||||
lint-test-build:
|
||||
timeout-minutes: 60
|
||||
needs: node-modules
|
||||
runs-on: ubuntu-22.04
|
||||
name: '(CI) lint + format check'
|
||||
name: '(CI) lint + unit test + build'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -79,76 +81,6 @@ jobs:
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
|
||||
test-affected:
|
||||
timeout-minutes: 30
|
||||
needs: build-sources
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'run unit test of affected apps'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
build-sources:
|
||||
timeout-minutes: 30
|
||||
needs: lint-format
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'Build sources of affected apps'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
@@ -165,8 +97,21 @@ jobs:
|
||||
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
|
||||
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
|
||||
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||
@@ -176,38 +121,38 @@ jobs:
|
||||
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
||||
|
||||
console-e2e:
|
||||
needs: build-sources
|
||||
needs: lint-test-build
|
||||
name: '(CI) console python'
|
||||
uses: ./.github/workflows/console-test-run.yml
|
||||
secrets: inherit
|
||||
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
cypress:
|
||||
needs: build-sources
|
||||
needs: lint-test-build
|
||||
name: '(CI) cypress'
|
||||
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
|
||||
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
|
||||
tags: '@smoke'
|
||||
|
||||
publish-dist:
|
||||
needs: build-sources
|
||||
needs: lint-test-build
|
||||
name: '(CD) publish dist'
|
||||
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
|
||||
uses: ./.github/workflows/publish-dist.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.build-sources.outputs.projects }}
|
||||
projects: ${{ needs.lint-test-build.outputs.projects }}
|
||||
|
||||
dist-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- publish-dist
|
||||
- build-sources
|
||||
- lint-test-build
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
|
||||
timeout-minutes: 60
|
||||
name: '(CD) comment preview links'
|
||||
@@ -223,27 +168,27 @@ jobs:
|
||||
run: |
|
||||
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
|
||||
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
|
||||
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
|
||||
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
|
||||
echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
|
||||
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
|
||||
echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
|
||||
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
|
||||
echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
|
||||
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
|
||||
echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
@@ -255,10 +200,10 @@ jobs:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Previews
|
||||
* governance: ${{ needs.build-sources.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.build-sources.outputs.preview_trading }}
|
||||
* tools: ${{ needs.build-sources.outputs.preview_tools }}
|
||||
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
|
||||
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
cypress-check:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
run-tests:
|
||||
name: run-tests
|
||||
runs-on: console-test
|
||||
runs-on: 8-cores
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
@@ -33,6 +33,15 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-node-modules-
|
||||
#----------------------------------------------
|
||||
# setup node
|
||||
#----------------------------------------------
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
#----------------------------------------------
|
||||
# install deps if cache missing
|
||||
#----------------------------------------------
|
||||
- name: yarn install
|
||||
@@ -67,6 +76,32 @@ jobs:
|
||||
repository: vegaprotocol/console-test
|
||||
path: './console-test'
|
||||
#----------------------------------------------
|
||||
# set-up python
|
||||
#----------------------------------------------
|
||||
- name: Set up python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.11'
|
||||
#----------------------------------------------
|
||||
# ----- install & configure poetry -----
|
||||
#----------------------------------------------
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
virtualenvs-path: console-test/.venv
|
||||
#----------------------------------------------
|
||||
# load cached venv if cache exists
|
||||
#----------------------------------------------
|
||||
- name: Load cached venv
|
||||
id: cached-poetry-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: console-test/.venv
|
||||
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
||||
#----------------------------------------------
|
||||
# install dependencies if cache does not exist
|
||||
#----------------------------------------------
|
||||
- name: Install dependencies
|
||||
@@ -90,7 +125,7 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
working-directory: ./console-test
|
||||
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
|
||||
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
|
||||
- name: Check files
|
||||
run: |
|
||||
ls -al .
|
||||
|
||||
@@ -37,7 +37,6 @@ jobs:
|
||||
|
||||
# Restore node_modules from cache if possible
|
||||
- name: Restore node_modules from cache
|
||||
id: cache-node-modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -47,7 +46,6 @@ jobs:
|
||||
|
||||
# Install frontend dependencies
|
||||
- name: Install root dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: frontend-monorepo
|
||||
|
||||
@@ -69,7 +67,7 @@ jobs:
|
||||
######
|
||||
|
||||
- name: Run Cypress tests
|
||||
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
|
||||
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
|
||||
working-directory: frontend-monorepo
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
|
||||
@@ -3,3 +3,6 @@
|
||||
|
||||
# Lint commit messages to ensure they follow conventional commit standards
|
||||
yarn commitlint --edit "${1}"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
* @vegaprotocol/frontend @vegaprotocol/frontend-qa
|
||||
* @vegaprotocol/frontend
|
||||
*.graphql @vegaprotocol/core
|
||||
|
||||
@@ -11,7 +11,7 @@ recalculate-ipfs:
|
||||
echo "ipfs hash inside the image"
|
||||
docker run --rm ${TAG} cat /ipfs-hash
|
||||
echo "recalculating ipfs hash"
|
||||
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html
|
||||
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
|
||||
|
||||
.PHONY: eject-ipfs-hash
|
||||
unpack:
|
||||
|
||||
@@ -115,31 +115,11 @@ docker run -p 3000:80 [TAG]
|
||||
|
||||
## Build instructions
|
||||
|
||||
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
|
||||
|
||||
### nx build inside the docker
|
||||
|
||||
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
|
||||
|
||||
```bash
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
```
|
||||
|
||||
### Computing ipfs-hash of the build
|
||||
|
||||
At the moment this feature is important only for Console releases.
|
||||
|
||||
Each docker build finishes with hash calculation for ` dist`` directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
|
||||
|
||||
```bash
|
||||
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
|
||||
```
|
||||
|
||||
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
|
||||
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
|
||||
|
||||
### nx build outside the docker
|
||||
|
||||
This Docker image packages a pre-built `dist` folder into an [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image. In this case, the application on docker host machine from source.
|
||||
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
|
||||
|
||||
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
|
||||
|
||||
@@ -153,18 +133,38 @@ You can build any of the containers locally with the following command:
|
||||
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
|
||||
```
|
||||
|
||||
### nx build inside the docker
|
||||
|
||||
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
|
||||
|
||||
```bash
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
```
|
||||
|
||||
### Computing ipfs-hash of the build
|
||||
|
||||
At the moment this feature is important only for `trading` (console) releases.
|
||||
|
||||
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
|
||||
|
||||
```bash
|
||||
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
|
||||
```
|
||||
|
||||
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
|
||||
|
||||
### Verifying ipfs-hash of existing current application version
|
||||
|
||||
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
|
||||
|
||||
1. Show latest release by running: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
|
||||
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
|
||||
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
|
||||
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
|
||||
4. Download docker image with the desired release `docker pull $TAG`.
|
||||
5. Recalculate hash: `make recalculate-ipfs`
|
||||
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
|
||||
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
|
||||
8. Now `dist` directory contains valid application build
|
||||
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
|
||||
|
||||
## Config
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
@@ -13,7 +16,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 0006-NETW-021
|
||||
it('should list each of the network parameters available', function () {
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
const numberOfNetworkParametersInSystem =
|
||||
@@ -196,19 +198,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
// 0006-NETW-022 0006-NETW-023
|
||||
it('governance assets should be correctly grouped', function () {
|
||||
cy.getByTestId('governance')
|
||||
.should('exist')
|
||||
.parent()
|
||||
.should('have.attr', 'href', '/network-parameters#governance');
|
||||
cy.get('[id="governance-proposal-asset"]')
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getByTestId('key-value-table-row').should('have.length', 8);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
context('Oracle page', { tags: '@smoke' }, () => {
|
||||
describe('Verify elements on page', () => {
|
||||
before('create market and navigate to oracle page', () => {
|
||||
cy.createMarket();
|
||||
cy.visit('/oracles');
|
||||
});
|
||||
it('should see oracle data', () => {
|
||||
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
|
||||
cy.getByTestId('oracle-details')
|
||||
.should('exist')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'ID');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/oracles/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Type');
|
||||
cy.get('td').should('have.text', 'External data');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Signer');
|
||||
cy.getByTestId('keytype').should('have.text', 'Vega');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/parties/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Settlement for');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/markets/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(4)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Matched data');
|
||||
cy.get('td').should('have.text', '❌');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.contains('Filter').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.contains('JSON').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1 @@
|
||||
export * from './network-parameters';
|
||||
export * from './structure-network-params';
|
||||
|
||||
@@ -1,105 +1,82 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { NetworkParametersTable } from './network-parameters';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const renderComponent = (data: NetworkParamsQuery | undefined) => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<NetworkParametersTable data={data} loading={false} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const mockData = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'spam.protection.delegation.min.tokens',
|
||||
value: '3',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'spam.protection.voting.min.tokens',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'reward.staking.delegation.minimumValidatorStake',
|
||||
value: '2',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'reward.asset',
|
||||
value:
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe('NetworkParametersTable', () => {
|
||||
it('renders headers correctly', () => {
|
||||
renderComponent(mockData);
|
||||
|
||||
const allHeadings = screen.getAllByRole('heading');
|
||||
expect(
|
||||
allHeadings.map((h) => {
|
||||
return {
|
||||
text: h.textContent,
|
||||
level: h.tagName,
|
||||
testId: h.getAttribute('data-testid'),
|
||||
};
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
text: 'Network Parameters',
|
||||
level: 'H1',
|
||||
testId: 'network-param-header',
|
||||
it('renders correctly when it has network params', () => {
|
||||
const data: NetworkParamsQuery = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.fee.factors.infrastructureFee',
|
||||
value: '0.0005',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ text: 'Spam', level: 'H1', testId: 'spam' },
|
||||
{ text: 'Protection', level: 'H2', testId: 'spam-protection' },
|
||||
{ text: 'Delegation', level: 'H3', testId: 'spam-protection-delegation' },
|
||||
{ text: 'Min', level: 'H4', testId: 'spam-protection-delegation-min' },
|
||||
{ text: 'Voting', level: 'H3', testId: 'spam-protection-voting' },
|
||||
{ text: 'Min', level: 'H4', testId: 'spam-protection-voting-min' },
|
||||
{ text: 'Reward', level: 'H1', testId: 'reward' },
|
||||
{ text: 'Staking', level: 'H2', testId: 'reward-staking' },
|
||||
{ text: 'Delegation', level: 'H3', testId: 'reward-staking-delegation' },
|
||||
]);
|
||||
};
|
||||
render(<NetworkParametersTable data={data} loading={false} />);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
);
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'market.fee.factors.infrastructureFee'
|
||||
);
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
it('renders network params correctly', () => {
|
||||
renderComponent(mockData);
|
||||
|
||||
const delegationMinTokensRow = screen.getByTestId(
|
||||
'spam-protection-delegation-min-tokens'
|
||||
it('renders the rows in ascending order', () => {
|
||||
const data: NetworkParamsQuery = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.fee.factors.infrastructureFee',
|
||||
value: '0.0005',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(<NetworkParametersTable data={data} loading={false} />);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
);
|
||||
expect(delegationMinTokensRow).toHaveTextContent('0.000000000000000003');
|
||||
|
||||
const votingMinTokensRow = screen.getByTestId(
|
||||
'spam-protection-voting-min-tokens'
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'market.fee.factors.infrastructureFee'
|
||||
);
|
||||
expect(votingMinTokensRow).toHaveTextContent('0.000000000000000001');
|
||||
|
||||
const minimumValidatorStakeRow = screen.getByTestId(
|
||||
'reward-staking-delegation-minimumValidatorStake'
|
||||
);
|
||||
expect(minimumValidatorStakeRow).toHaveTextContent('2');
|
||||
|
||||
const assetRow = screen.getByTestId('reward-asset');
|
||||
expect(assetRow).toHaveTextContent(
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55'
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
it('does not render rows when is loading', () => {
|
||||
renderComponent(undefined);
|
||||
render(<NetworkParametersTable data={undefined} loading={true} />);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import startCase from 'lodash/startCase';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
SyntaxHighlighter,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -14,12 +12,11 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { structureNetworkParams } from './structure-network-params';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { GroupedParams } from './structure-network-params';
|
||||
|
||||
const PERCENTAGE_PARAMS = [
|
||||
'governance.proposal.asset.requiredMajority',
|
||||
@@ -61,54 +58,6 @@ const BIG_NUMBER_PARAMS = [
|
||||
'governance.proposal.updateAsset.minVoterBalance',
|
||||
];
|
||||
|
||||
export const renderGroupedParams = (
|
||||
group: GroupedParams,
|
||||
level: number,
|
||||
parentKeys: string[] = []
|
||||
) => {
|
||||
const Header = `h${level}` as keyof JSX.IntrinsicElements;
|
||||
const headerStyles = classNames('uppercase font-semibold', {
|
||||
'pt-6 text-3xl underline': level === 1,
|
||||
'pt-3 text-2xl': level === 2,
|
||||
'pt-2 text-lg': level === 3,
|
||||
'pt-2 text-default': level === 4,
|
||||
});
|
||||
|
||||
return Object.entries(group).map(([key, value]) => {
|
||||
const fullPath = [...parentKeys, key].join('.');
|
||||
const isLeafNode = typeof value !== 'object';
|
||||
const id = parentKeys.concat([key]).join('-');
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
{!isLeafNode && (
|
||||
<div id={id}>
|
||||
<Link to={`#${id}`}>
|
||||
<Header className={headerStyles} data-testid={id}>
|
||||
{startCase(key)}
|
||||
</Header>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{isLeafNode ? (
|
||||
typeof value === 'string' ? (
|
||||
<div data-testid={id}>
|
||||
<NetworkParameterRow
|
||||
key={fullPath}
|
||||
row={{ key: fullPath, value: value }}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<div className="pb-1">
|
||||
{renderGroupedParams(value, level + 1, [...parentKeys, key])}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const NetworkParameterRow = ({
|
||||
row: { key, value },
|
||||
}: {
|
||||
@@ -128,9 +77,7 @@ export const NetworkParameterRow = ({
|
||||
>
|
||||
{key}
|
||||
{isSyntaxRow ? (
|
||||
<div className="pb-2">
|
||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||
</div>
|
||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||
) : isNaN(Number(value)) ? (
|
||||
value
|
||||
) : BIG_NUMBER_PARAMS.includes(key) ? (
|
||||
@@ -166,12 +113,17 @@ export const NetworkParametersTable = ({
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const flatParams = removePaginationWrapper(
|
||||
data.networkParametersConnection.edges
|
||||
const ascParams = orderBy(
|
||||
removePaginationWrapper(data.networkParametersConnection.edges),
|
||||
(param) => param.key,
|
||||
'asc'
|
||||
);
|
||||
const groupedParams = structureNetworkParams(flatParams);
|
||||
return (
|
||||
<div className="-mt-6">{renderGroupedParams(groupedParams, 1)}</div>
|
||||
<KeyValueTable data-testid="parameters">
|
||||
{(ascParams || []).map((row) => (
|
||||
<NetworkParameterRow key={row.key} row={row} />
|
||||
))}
|
||||
</KeyValueTable>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import type { GroupedParams } from './structure-network-params';
|
||||
import {
|
||||
structureParams,
|
||||
sortGroupedParams,
|
||||
structureNetworkParams,
|
||||
} from './structure-network-params';
|
||||
|
||||
describe('structureParams', () => {
|
||||
it('should correctly structure params', () => {
|
||||
const input = [
|
||||
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
{ key: 'spam.protection.voting.min.tokens', value: '5' },
|
||||
];
|
||||
const output: GroupedParams = {
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
voting: {
|
||||
min: {
|
||||
tokens: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(structureParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should handle top-level keys correctly', () => {
|
||||
const input = [{ key: 'levelOne', value: '10' }];
|
||||
const output = {
|
||||
levelOne: '10',
|
||||
};
|
||||
expect(structureParams(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortGroupedParams', () => {
|
||||
it('should correctly sort grouped params', () => {
|
||||
const input: GroupedParams = {
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reward: '50',
|
||||
};
|
||||
const output: GroupedParams = {
|
||||
reward: '50',
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(sortGroupedParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should handle already sorted keys', () => {
|
||||
const input = {
|
||||
a: '10',
|
||||
b: {
|
||||
c: '5',
|
||||
d: '6',
|
||||
},
|
||||
};
|
||||
expect(sortGroupedParams(input)).toEqual(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('structureNetworkParams', () => {
|
||||
it('should structure and sort network params correctly', () => {
|
||||
const input = [
|
||||
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
{ key: 'reward.asset', value: '50' },
|
||||
];
|
||||
const output: GroupedParams = {
|
||||
reward: {
|
||||
asset: '50',
|
||||
},
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(structureNetworkParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should return an empty object if no params are provided', () => {
|
||||
expect(structureNetworkParams([])).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* Categorizes and sorts an array of key-value pairs of network params into a nested object structure.
|
||||
*
|
||||
* The function takes an array of network params where keys are dot-delimited
|
||||
* strings representing nested categories (e.g., 'spam.protection.delegation.min.tokens').
|
||||
*
|
||||
* Why this is necessary:
|
||||
* A flat key-value structure wouldn't provide the hierarchical information needed.
|
||||
* Organizing network parameters like this allows the rendering of nested headers
|
||||
* and their corresponding network params.
|
||||
*
|
||||
* It also ensures that items with the minimum amount of nesting are ordered first. This
|
||||
* allows us to render these items first, before more deeply nested items are rendered with
|
||||
* subheaders. This creates a more intuitive UI.
|
||||
*
|
||||
* For example, given the input:
|
||||
* [
|
||||
* { key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
* { key: 'spam.protection.voting.min.tokens', value: '5' },
|
||||
* { key: 'reward.staking.delegation.minimumValidatorStake', value: '2' }
|
||||
* { key: 'reward.asset', value: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55' }
|
||||
* ]
|
||||
*
|
||||
* The output will be:
|
||||
* {
|
||||
* spam: {
|
||||
* protection: {
|
||||
* delegation: {
|
||||
* min: {
|
||||
* tokens: '10'
|
||||
* }
|
||||
* },
|
||||
* voting: {
|
||||
* min: {
|
||||
* tokens: '5'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* },
|
||||
* reward: {
|
||||
* asset: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
|
||||
* staking: {
|
||||
* delegation: {
|
||||
* minimumValidatorStake: '2'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param {Array} params - An array of key-value pairs to categorize and sort.
|
||||
* @returns {GroupedParams} - A nested object that groups the key-value pairs.
|
||||
*/
|
||||
|
||||
export type GroupedParams = {
|
||||
[key: string]: string | GroupedParams;
|
||||
};
|
||||
|
||||
export const structureParams = (
|
||||
params: { key: string; value: string }[]
|
||||
): GroupedParams => {
|
||||
const grouped: GroupedParams = {};
|
||||
|
||||
params.forEach(({ key, value }) => {
|
||||
const parts = key.split('.');
|
||||
let node: GroupedParams = grouped;
|
||||
|
||||
parts.forEach((part, i) => {
|
||||
if (typeof node[part] === 'undefined') {
|
||||
node[part] = i === parts.length - 1 ? value : {};
|
||||
}
|
||||
|
||||
if (typeof node[part] === 'object') {
|
||||
node = node[part] as GroupedParams;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return grouped;
|
||||
};
|
||||
|
||||
export const sortGroupedParams = (
|
||||
groupedParams: GroupedParams
|
||||
): GroupedParams => {
|
||||
const sorted: GroupedParams = {};
|
||||
|
||||
// Sort top-level keys first
|
||||
Object.entries(groupedParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
sorted[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
Object.entries(groupedParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'object') {
|
||||
sorted[key] = sortGroupedParams(value);
|
||||
}
|
||||
});
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const structureNetworkParams = (
|
||||
params: { key: string; value: string }[]
|
||||
) => {
|
||||
const grouped = structureParams(params);
|
||||
return sortGroupedParams(grouped);
|
||||
};
|
||||
@@ -38,12 +38,7 @@ const Oracles = () => {
|
||||
const dataConnection = o?.node.dataConnection;
|
||||
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
key={id}
|
||||
className="mb-10"
|
||||
data-testid="oracle-details"
|
||||
>
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
|
||||
@@ -34,20 +34,19 @@ import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const participationNotMet = 'token-participation-not-met';
|
||||
const voteStatus = 'vote-status';
|
||||
const voteMajorityNotMet = 'token-majority-not-met';
|
||||
const numberOfVotesFor = 'num-votes-for';
|
||||
const votesForPercentage = 'votes-for-percentage';
|
||||
const numberOfVotesAgainst = 'num-votes-against';
|
||||
const votesAgainstPercentage = 'votes-against-percentage';
|
||||
const totalVotedNumber = 'total-voted';
|
||||
const totalVotedPercentage = 'total-voted-percentage';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const proposalVoteProgressAgainstPercentage =
|
||||
'vote-progress-indicator-percentage-against';
|
||||
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
|
||||
const proposalVoteProgressAgainstTokens =
|
||||
'vote-progress-indicator-tokens-against';
|
||||
const changeVoteButton = 'change-vote-button';
|
||||
const proposalDetailsTitle = 'proposal-title';
|
||||
const proposalDetailsDescription = 'proposal-description';
|
||||
const openProposals = 'open-proposals';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const voteBreakdownToggle = 'vote-breakdown-toggle';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
|
||||
@@ -151,32 +150,29 @@ describe(
|
||||
// 3001-VOTE-037
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-067
|
||||
// 3001-VOTE-023
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.getByTestId(participationNotMet).should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold not met'
|
||||
);
|
||||
cy.getByTestId(voteMajorityNotMet).should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
);
|
||||
cy.getByTestId(voteStatus).should(
|
||||
'have.text',
|
||||
'Currently expected to fail'
|
||||
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-062
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-070
|
||||
getProposalInformationFromTable('Token majority met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-068
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(numberOfVotesAgainst).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
|
||||
@@ -201,16 +197,43 @@ describe(
|
||||
.contains(votedDate)
|
||||
.should('be.visible');
|
||||
});
|
||||
cy.getByTestId(votesForPercentage) // 3001-VOTE-072
|
||||
.should('have.text', '100%');
|
||||
cy.getByTestId(votesAgainstPercentage).should('have.text', '0%');
|
||||
cy.getByTestId('token-majority-progress')
|
||||
.should('have.attr', 'style')
|
||||
.and('eq', 'width: 100%;'); // 3001-VOTE-024
|
||||
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('0.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains('1.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstTokens)
|
||||
.contains('0.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', '0.00')
|
||||
.and('be.visible');
|
||||
// 3001-VOTE-061
|
||||
getProposalInformationFromTable('Participation required')
|
||||
.contains('0.00%')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-066
|
||||
getProposalInformationFromTable('Majority Required') // 3001-VOTE-073
|
||||
.contains(`${(66).toFixed(2)}%`)
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
// 3001-VOTE-064
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
@@ -221,7 +244,16 @@ describe(
|
||||
});
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('against');
|
||||
cy.getByTestId(votesAgainstPercentage).should('have.text', '100%');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
@@ -233,7 +265,6 @@ describe(
|
||||
|
||||
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
|
||||
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
@@ -244,28 +275,69 @@ describe(
|
||||
voteForProposal('for');
|
||||
// 3001-VOTE-079
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||
ethereumWalletConnect();
|
||||
stakingPageAssociateTokens('1000000', { approve: true });
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||
// 3001-VOTE-065
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '1.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '1.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(1.54%)');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains('1')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Total Supply')
|
||||
.invoke('text')
|
||||
.then((totalSupply) => {
|
||||
const tokensRequiredToAchieveResult = (
|
||||
(Number(totalSupply.replace(/,/g, '')) * 0.001) /
|
||||
100
|
||||
).toFixed(2);
|
||||
ethereumWalletConnect();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
tokensRequiredToAchieveResult
|
||||
);
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('0.00%')
|
||||
.and('be.visible');
|
||||
// 3001-VOTE-065
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains(tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstTokens)
|
||||
.contains('0.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Total tokens voted percentage')
|
||||
.should('have.text', '0.00%')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', '0.00')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-062
|
||||
getProposalInformationFromTable('Token majority met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.contains(tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to vote for proposal twice by switching public key', function () {
|
||||
@@ -288,6 +360,10 @@ describe(
|
||||
voteForProposal('against');
|
||||
cy.contains('You voted: Against').should('be.visible');
|
||||
switchVegaWalletPubKey();
|
||||
cy.getByTestId(proposalVoteProgressForTokens).should(
|
||||
'contain.text',
|
||||
'1.00'
|
||||
);
|
||||
// Checking vote status for different public keys is displayed correctly
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
});
|
||||
|
||||
@@ -22,10 +22,12 @@ const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const closedProposals = 'closed-proposals';
|
||||
const proposalStatus = 'proposal-status';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const votesTable = 'user-vote';
|
||||
const votesTable = 'votes-table';
|
||||
const openProposals = 'open-proposals';
|
||||
const majorityVoteReached = 'token-majority-met';
|
||||
const minParticipationReached = 'token-participation-met';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const majorityVoteReached = 'majority-reached';
|
||||
const minParticipationReached = 'participation-reached';
|
||||
const proposalTimeout = { timeout: 8000 };
|
||||
|
||||
context(
|
||||
@@ -68,6 +70,7 @@ context(
|
||||
cy.getByTestId('proposal-type').should('have.text', 'New market');
|
||||
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
|
||||
cy.getByTestId(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -89,7 +92,7 @@ context(
|
||||
// 3001-VOTE-019 time to vote is highlighted red
|
||||
cy.getByTestId('vote-details')
|
||||
.find('span')
|
||||
.should('have.class', 'text-vega-orange');
|
||||
.should('have.class', 'text-vega-pink');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
@@ -106,9 +109,12 @@ context(
|
||||
);
|
||||
});
|
||||
cy.getByTestId(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
cy.getByTestId('votes-for-percentage').should('have.text', '100%');
|
||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
|
||||
@@ -298,6 +298,9 @@ context(
|
||||
getProposalFromTitle(proposalTitle).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
cy.contains('Vote breakdown').should('be.visible', {
|
||||
timeout: 10000,
|
||||
});
|
||||
cy.getByTestId(voteButtons).should('not.exist');
|
||||
cy.getByTestId('min-proposal-requirements').should(
|
||||
'have.text',
|
||||
|
||||
@@ -52,6 +52,8 @@ const enactmentDeadlineError = 'enactment-before-voting-deadline';
|
||||
const proposalDownloadBtn = 'proposal-download-json';
|
||||
const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalJsonToggle = 'proposal-json-toggle';
|
||||
const proposalJsonSection = 'proposal-json';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
@@ -419,28 +421,27 @@ context(
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId('lp-majority-not-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
|
||||
cy.getByTestId('token-majority-not-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
voteForProposal('for');
|
||||
cy.getByTestId('lp-majority-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold met'
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId('token-majority-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold met'
|
||||
);
|
||||
cy.getByTestId('vote-status').should(
|
||||
'have.text',
|
||||
'Currently expected to pass by token vote'
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId('vote-breakdown-toggle').click();
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👍 by token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
enterRawProposalBody,
|
||||
generateFreeFormProposalTitle,
|
||||
getProposalFromTitle,
|
||||
getProposalInformationFromTable,
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
submitUniqueRawProposal,
|
||||
@@ -29,9 +30,7 @@ const proposalType = 'proposal-type';
|
||||
const proposalStatus = 'proposal-status';
|
||||
const proposalClosingDate = 'vote-details';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const voteMajorityNotMet = 'token-majority-not-met';
|
||||
const voteMajorityMet = 'token-majority-met';
|
||||
const votesForPercentage = 'votes-for-percentage';
|
||||
const voteBreakDownToggle = 'vote-breakdown-toggle';
|
||||
|
||||
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
@@ -122,15 +121,10 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
submitUniqueRawProposal({ proposalTitle: proposalTitle });
|
||||
getProposalFromTitle(proposalTitle).within(() => {
|
||||
// 3001-VOTE-039
|
||||
cy.getByTestId(voteMajorityNotMet).should(
|
||||
cy.getByTestId('participation-not-reached').should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
'Min. participation not reached'
|
||||
);
|
||||
cy.getByTestId('token-participation-not-met').should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold not met'
|
||||
);
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '0%');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
voteForProposal('for');
|
||||
@@ -140,15 +134,16 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
'have.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId(voteMajorityMet).should(
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
cy.getByTestId('participation-reached').should(
|
||||
'have.text',
|
||||
'66% majority threshold met'
|
||||
);
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId('token-participation-met').should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold met'
|
||||
'Min. participation reached'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(voteBreakDownToggle).click();
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,39 +37,11 @@ context(
|
||||
});
|
||||
|
||||
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
|
||||
it('new proposal page should have button for link to more information on proposals', function () {
|
||||
cy.getByTestId('new-proposal-link').click();
|
||||
cy.url().should('include', '/proposals/propose/raw');
|
||||
cy.contains('To see Explorer data on proposals visit').within(() => {
|
||||
cy.getByTestId('external-link').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://explorer.fairground.wtf/governance'
|
||||
);
|
||||
});
|
||||
cy.contains(
|
||||
'1. Sense check your proposal with the community on the forum:'
|
||||
).within(() => {
|
||||
cy.getByTestId('external-link').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://community.vega.xyz/c/governance/25'
|
||||
);
|
||||
});
|
||||
cy.contains(
|
||||
'2. Use the appropriate proposal template in the docs:'
|
||||
).within(() => {
|
||||
cy.getByTestId('external-link').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/tutorials/proposals'
|
||||
);
|
||||
});
|
||||
cy.contains('Connect your wallet to submit a proposal').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.getByTestId('connect-to-vega-wallet-btn').should('exist');
|
||||
navigateTo(navigation.proposals);
|
||||
it('should have button for link to more information on proposals', function () {
|
||||
const proposalsUrl = 'https://docs.vega.xyz/mainnet/tutorials/proposals';
|
||||
cy.getByTestId('new-proposal-link')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', proposalsUrl);
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
@@ -211,7 +183,7 @@ context(
|
||||
.first()
|
||||
.find('[data-testid="view-proposal-btn"]')
|
||||
.click();
|
||||
cy.url().should('contain', '/protocol-upgrades/v1/2015942');
|
||||
cy.url().should('contain', '/protocol-upgrades/v1');
|
||||
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
||||
cy.get('h1').should('have.text', 'Vega Release v1');
|
||||
cy.getByTestId('protocol-upgrade-block-height').should(
|
||||
@@ -271,7 +243,7 @@ context(
|
||||
);
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/proposals/protocol-upgrade/v1/2015942');
|
||||
.and('contain', '/proposals/protocol-upgrade/v1');
|
||||
});
|
||||
|
||||
// estimate does not display possibly due to mocks or Cypress unless the proposal is clicked on several times
|
||||
|
||||
@@ -146,7 +146,7 @@ export function getProposalInformationFromTable(heading: string) {
|
||||
}
|
||||
|
||||
export function voteForProposal(vote: string) {
|
||||
cy.get(voteButtons).should('be.visible', { timeout: 10000 });
|
||||
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
|
||||
cy.get(voteButtons).contains(vote).click();
|
||||
cy.get(dialogTitle, proposalTimeout).should(
|
||||
'have.text',
|
||||
|
||||
@@ -219,9 +219,7 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
.eq(1)
|
||||
.invoke('text')
|
||||
.then((unstakedBalance) => {
|
||||
const tokenFloat = parseFloat(tokenAmount);
|
||||
const unstakedFloat = parseFloat(unstakedBalance.replace(/,/g, ''));
|
||||
if (tokenFloat != unstakedFloat) {
|
||||
if (parseFloat(unstakedBalance) != parseFloat(tokenAmount)) {
|
||||
vegaWalletTeardown();
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
|
||||
'0.00',
|
||||
|
||||
@@ -184,7 +184,7 @@ const Web3Container = ({
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 border-t border-neutral-700 break-all">
|
||||
<footer className="p-4 border-t border-neutral-700">
|
||||
<NetworkInfo />
|
||||
</footer>
|
||||
</AppLayout>
|
||||
@@ -308,7 +308,7 @@ const AppContainer = () => {
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="min-h-full text-white">
|
||||
<div className="min-h-full text-white grid">
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
|
||||
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
|
||||
}) => {
|
||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||
useAnimateValue(ref, value);
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
|
||||
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -75,10 +75,7 @@ export const WalletCardRow = ({
|
||||
className="font-mono flex-1 text-right"
|
||||
data-testid="associated-amount"
|
||||
>
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span>{integers}.</span>
|
||||
<span>{decimalsPlaces}</span>
|
||||
</span>
|
||||
)}
|
||||
@@ -113,10 +110,7 @@ export const WalletCardAsset = ({
|
||||
border,
|
||||
subheading,
|
||||
}: WalletCardAssetProps) => {
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(
|
||||
balance,
|
||||
decimals
|
||||
);
|
||||
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap mt-2 mb-4">
|
||||
@@ -138,10 +132,7 @@ export const WalletCardAsset = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span>{integers}.</span>
|
||||
<span className="text-neutral-400">{decimalsPlaces}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -474,7 +474,7 @@
|
||||
"rewardsColLiquidityProvisionHeader": "LIQUIDITY PROVISION",
|
||||
"rewardsColLiquidityProvisionTooltip": "Liquidity provision rewards are distributed based on how much you have earned in liquidity fees, funded by a liquidity reward pool for that market",
|
||||
"rewardsColMarketCreationHeader": "MARKET CREATION",
|
||||
"rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently {{marketCreationQuantumMultiple}}",
|
||||
"rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently [rewards.marketCreationQuantumMultiple]",
|
||||
"rewardsColTotalHeader": "TOTAL",
|
||||
"ofTotalDistributed": "of total distributed",
|
||||
"checkBackSoon": "Check back soon",
|
||||
@@ -708,7 +708,7 @@
|
||||
"NewAssetProposal": "New asset proposal",
|
||||
"UpdateAssetProposal": "Update asset proposal",
|
||||
"NewFreeformProposal": "New freeform proposal",
|
||||
"NewRawProposal": "New proposal",
|
||||
"NewRawProposal": "New raw proposal",
|
||||
"MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a proposal",
|
||||
"MinProposalVoteRequirements": "You must have at least {{value}} VEGA associated to vote on this proposal",
|
||||
"totalSupply": "Total Supply",
|
||||
@@ -870,9 +870,5 @@
|
||||
"Upgraded at": "Upgraded at",
|
||||
"dataIsIdentical": "Data is identical",
|
||||
"updatesToMarket": "Updates to market",
|
||||
"viewAsParty": "View as party",
|
||||
"HowToPropose": "How to make a proposal",
|
||||
"HowToProposeRawStep1": "1. Sense check your proposal with the community on the forum:",
|
||||
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
|
||||
"HowToProposeRawStep3": "3. Submit on-chain below"
|
||||
"viewAsParty": "View as party"
|
||||
}
|
||||
|
||||
@@ -279,8 +279,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
trimmedActiveNodes={trimmedActiveNodes}
|
||||
/>
|
||||
|
||||
<section className="flex justify-between flex-wrap gap-12 mb-16">
|
||||
<div className="min-w-[360px] flex-1" data-testid="home-rewards">
|
||||
<section className="grid grid-cols-2 gap-12 mb-16">
|
||||
<div data-testid="home-rewards">
|
||||
<Heading title={t('Rewards')} marginTop={false} />
|
||||
<h3 className="mb-6">{t('homeRewardsIntro')}</h3>
|
||||
<div className="flex items-center mb-8 gap-4">
|
||||
@@ -290,7 +290,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-[360px] flex-1" data-testid="home-vega-token">
|
||||
<div data-testid="home-vega-token">
|
||||
<Heading title={t('vegaToken')} marginTop={false} />
|
||||
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
|
||||
<div className="flex items-center mb-8 gap-4">
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ export const ProposalHeader = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-testid="proposal-title" className="break-all">
|
||||
<div data-testid="proposal-title">
|
||||
{isListItem ? (
|
||||
<header>
|
||||
<SubHeading
|
||||
|
||||
@@ -7,13 +7,18 @@ import { ProposalsListItem } from '../proposals-list-item';
|
||||
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||
import Routes from '../../../routes';
|
||||
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Button,
|
||||
Toggle,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
@@ -50,10 +55,7 @@ export const orderByUpgradeBlockHeight = (
|
||||
) =>
|
||||
orderBy(
|
||||
arr,
|
||||
[
|
||||
(p) => (p?.upgradeBlockHeight ? parseInt(p.upgradeBlockHeight, 10) : 0),
|
||||
(p) => p.vegaReleaseTag,
|
||||
],
|
||||
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
|
||||
['desc', 'desc']
|
||||
);
|
||||
|
||||
@@ -140,13 +142,18 @@ export const ProposalsList = ({
|
||||
title={t('pageTitleProposals')}
|
||||
/>
|
||||
|
||||
<div className="xs:justify-self-end" data-testid="new-proposal-link">
|
||||
<Link to={`${Routes.PROPOSALS}/propose/raw`}>
|
||||
<Button variant="primary" size="sm">
|
||||
<div className="flex items-center gap-1">{t('NewProposal')}</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{DocsLinks && (
|
||||
<div className="xs:justify-self-end" data-testid="new-proposal-link">
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
|
||||
<Button variant="primary" size="sm">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('NewProposal')}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
|
||||
</div>
|
||||
</Button>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mb-8">
|
||||
@@ -217,10 +224,10 @@ export const ProposalsList = ({
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 &&
|
||||
filterString.length < 1 && (
|
||||
<div
|
||||
className="flex justify-end xl:-mt-12 pb-6"
|
||||
className="grid w-full justify-end xl:-mt-12 pb-6"
|
||||
data-testid="toggle-closed-proposals"
|
||||
>
|
||||
<div className="w-full max-w-[420px]">
|
||||
<div className="w-[440px]">
|
||||
<Toggle
|
||||
name="closed-proposals-toggle"
|
||||
toggles={[
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export const ProtocolUpgradeProposalsListItem = ({
|
||||
<Link
|
||||
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
|
||||
proposal.vegaReleaseTag
|
||||
)}/${proposal.upgradeBlockHeight}`}
|
||||
)}`}
|
||||
>
|
||||
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
||||
</Link>
|
||||
|
||||
@@ -130,10 +130,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
? t('byTokenVote')
|
||||
: t('byLiquidityVote');
|
||||
|
||||
const sectionWrapperClasses = classNames(
|
||||
'flex justify-between flex-wrap gap-6'
|
||||
);
|
||||
const sectionClasses = classNames('min-w-[300px] flex-1 flex-grow');
|
||||
const sectionWrapperClasses = classNames('grid sm:grid-cols-2 gap-6');
|
||||
const headingClasses = classNames('mb-2 text-vega-dark-400');
|
||||
const progressDetailsClasses = classNames(
|
||||
'flex justify-between flex-wrap mt-2 text-sm'
|
||||
@@ -169,10 +166,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="mb-4">
|
||||
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-majority-breakdown"
|
||||
>
|
||||
<section data-testid="lp-majority-breakdown">
|
||||
<VoteProgress
|
||||
percentageFor={yesLPPercentage}
|
||||
colourfulBg={true}
|
||||
@@ -247,10 +241,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-participation-breakdown"
|
||||
>
|
||||
<section data-testid="lp-participation-breakdown">
|
||||
<VoteProgress
|
||||
percentageFor={
|
||||
lpParticipationThresholdProgress || new BigNumber(0)
|
||||
@@ -297,10 +288,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
|
||||
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="token-majority-breakdown"
|
||||
>
|
||||
<section data-testid="token-majority-breakdown">
|
||||
<VoteProgress
|
||||
percentageFor={yesPercentage}
|
||||
colourfulBg={true}
|
||||
@@ -320,7 +308,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('tokenVotesFor')}:</span>
|
||||
<Tooltip description={formatNumber(yesTokens, defaultDP)}>
|
||||
<button data-testid="num-votes-for">
|
||||
<button>
|
||||
{yesTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -329,9 +317,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<Tooltip
|
||||
description={<span>{yesPercentage.toFixed(defaultDP)}%</span>}
|
||||
>
|
||||
<button data-testid="votes-for-percentage">
|
||||
{yesPercentage.toFixed(0)}%
|
||||
</button>
|
||||
<button>{yesPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
@@ -340,7 +326,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('tokenVotesAgainst')}:</span>
|
||||
<Tooltip description={formatNumber(noTokens, defaultDP)}>
|
||||
<button data-testid="num-votes-against">
|
||||
<button>
|
||||
{noTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -349,9 +335,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<Tooltip
|
||||
description={<span>{noPercentage.toFixed(defaultDP)}%</span>}
|
||||
>
|
||||
<button data-testid="votes-against-percentage">
|
||||
{noPercentage.toFixed(0)}%
|
||||
</button>
|
||||
<button>{noPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
@@ -359,10 +343,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="token-participation-breakdown"
|
||||
>
|
||||
<section data-testid="token-participation-breakdown">
|
||||
<VoteProgress
|
||||
percentageFor={participationThresholdProgress}
|
||||
testId="token-participation-progress"
|
||||
@@ -383,13 +364,11 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('totalTokensVoted')}:</span>
|
||||
<Tooltip description={formatNumber(totalTokensVoted, defaultDP)}>
|
||||
<button data-testid="total-voted">
|
||||
<button>
|
||||
{totalTokensVoted.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span data-testid="total-voted-percentage">
|
||||
({totalTokensPercentage.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
<span>({totalTokensPercentage.toFixed(defaultDP)}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { Heading } from '../../../../components/heading';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
ExternalLink,
|
||||
FormGroup,
|
||||
InputError,
|
||||
RoundedWrapper,
|
||||
TextArea,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
@@ -84,6 +79,15 @@ export const ProposeRaw = () => {
|
||||
spamProtectionMin={params.spam_protection_proposal_min_tokens}
|
||||
/>
|
||||
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
|
||||
{DocsLinks.PROPOSALS_GUIDE}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{VEGA_EXPLORER_URL && (
|
||||
<p className="text-sm">
|
||||
{t('MoreProposalsInfo')}{' '}
|
||||
@@ -94,44 +98,12 @@ export const ProposeRaw = () => {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<section data-testid="how-to" className="my-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<SubHeading title={t('HowToPropose')} />
|
||||
<ul>
|
||||
<li className="p-1">
|
||||
{t('HowToProposeRawStep1')}{' '}
|
||||
{ExternalLinks && (
|
||||
<span data-testid="proposal-docs-link">
|
||||
<ExternalLink
|
||||
href={ExternalLinks.PROPOSALS_FORUM}
|
||||
target="_blank"
|
||||
>
|
||||
{ExternalLinks.PROPOSALS_FORUM}
|
||||
</ExternalLink>
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
<li className="p-1">
|
||||
{t('HowToProposeRawStep2')}{' '}
|
||||
{DocsLinks && (
|
||||
<span data-testid="proposal-docs-link">
|
||||
<ExternalLink
|
||||
href={DocsLinks.PROPOSALS_GUIDE}
|
||||
target="_blank"
|
||||
>
|
||||
{DocsLinks.PROPOSALS_GUIDE}
|
||||
</ExternalLink>
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
<li className="p-1">{t('HowToProposeRawStep3')}</li>
|
||||
</ul>
|
||||
</RoundedWrapper>
|
||||
</section>
|
||||
|
||||
<div data-testid="raw-proposal-form">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormGroup label="Valid JSON required" labelFor="proposal-data">
|
||||
<FormGroup
|
||||
label="Make a proposal by submitting JSON"
|
||||
labelFor="proposal-data"
|
||||
>
|
||||
<TextArea
|
||||
id="proposal-data"
|
||||
className="min-h-[200px]"
|
||||
|
||||
+1
-4
@@ -43,10 +43,7 @@ describe('EpochIndividualRewardsTable', () => {
|
||||
it('should render correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<AppStateProvider>
|
||||
<EpochIndividualRewardsTable
|
||||
data={mockData}
|
||||
marketCreationQuantumMultiple={'1000'}
|
||||
/>
|
||||
<EpochIndividualRewardsTable data={mockData} />
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
|
||||
|
||||
-3
@@ -8,7 +8,6 @@ import type { EpochIndividualReward } from './generate-epoch-individual-rewards-
|
||||
|
||||
interface EpochIndividualRewardsGridProps {
|
||||
data: EpochIndividualReward;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
interface RewardItemProps {
|
||||
@@ -70,11 +69,9 @@ const RewardItem = ({
|
||||
|
||||
export const EpochIndividualRewardsTable = ({
|
||||
data,
|
||||
marketCreationQuantumMultiple,
|
||||
}: EpochIndividualRewardsGridProps) => {
|
||||
return (
|
||||
<RewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
dataTestId="epoch-individual-rewards-table"
|
||||
epoch={Number(data.epoch)}
|
||||
>
|
||||
|
||||
-5
@@ -9,7 +9,6 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -27,9 +26,6 @@ export const EpochIndividualRewards = ({
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { delegationsPagination } = ENV;
|
||||
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||
'rewards_marketCreationQuantumMultiple'
|
||||
);
|
||||
|
||||
const { data, loading, error, refetch } = useRewardsQuery({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
@@ -107,7 +103,6 @@ export const EpochIndividualRewards = ({
|
||||
{epochIndividualRewardSummaries.map(
|
||||
(epochIndividualRewardSummary) => (
|
||||
<EpochIndividualRewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
data={epochIndividualRewardSummary}
|
||||
/>
|
||||
)
|
||||
|
||||
+1
-4
@@ -66,10 +66,7 @@ describe('EpochTotalRewardsTable', () => {
|
||||
it('should render correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<AppStateProvider>
|
||||
<EpochTotalRewardsTable
|
||||
data={mockData}
|
||||
marketCreationQuantumMultiple={'1000'}
|
||||
/>
|
||||
<EpochTotalRewardsTable data={mockData} />
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
|
||||
|
||||
+1
-7
@@ -8,7 +8,6 @@ import type { EpochTotalSummary } from './generate-epoch-total-rewards-list';
|
||||
|
||||
interface EpochTotalRewardsGridProps {
|
||||
data: EpochTotalSummary;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
interface RewardItemProps {
|
||||
@@ -53,14 +52,9 @@ const RewardItem = ({
|
||||
|
||||
export const EpochTotalRewardsTable = ({
|
||||
data,
|
||||
marketCreationQuantumMultiple,
|
||||
}: EpochTotalRewardsGridProps) => {
|
||||
return (
|
||||
<RewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
dataTestId="epoch-total-rewards-table"
|
||||
epoch={data.epoch}
|
||||
>
|
||||
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
|
||||
{Array.from(data.assetRewards.values()).map(
|
||||
({ name, rewards, totalAmount, decimals }, i) => (
|
||||
<div className="contents" key={i}>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
|
||||
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
||||
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -19,9 +18,6 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
||||
const epochId = Number(currentEpoch.id) - 1;
|
||||
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
||||
const { t } = useTranslation();
|
||||
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||
'rewards_marketCreationQuantumMultiple'
|
||||
);
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
@@ -74,11 +70,7 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
||||
>
|
||||
{Array.from(epochTotalRewardSummaries.values()).map(
|
||||
(epochTotalSummary, index) => (
|
||||
<EpochTotalRewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
data={epochTotalSummary}
|
||||
key={index}
|
||||
/>
|
||||
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
|
||||
)
|
||||
)}
|
||||
<Pagination
|
||||
|
||||
@@ -133,26 +133,24 @@ export const RewardsPage = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<div className="w-full max-w-[360px]">
|
||||
<Toggle
|
||||
name="epoch-reward-view-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t('totalDistributed'),
|
||||
value: 'total',
|
||||
},
|
||||
{
|
||||
label: t('earnedByMe'),
|
||||
value: 'individual',
|
||||
},
|
||||
]}
|
||||
checkedValue={toggleRewardsView}
|
||||
onChange={(e) =>
|
||||
setToggleRewardsView(e.target.value as RewardsView)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[360px]">
|
||||
<Toggle
|
||||
name="epoch-reward-view-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t('totalDistributed'),
|
||||
value: 'total',
|
||||
},
|
||||
{
|
||||
label: t('earnedByMe'),
|
||||
value: 'individual',
|
||||
},
|
||||
]}
|
||||
checkedValue={toggleRewardsView}
|
||||
onChange={(e) =>
|
||||
setToggleRewardsView(e.target.value as RewardsView)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+3
-13
@@ -77,11 +77,7 @@ const ColumnHeader = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const ColumnHeaders = ({
|
||||
marketCreationQuantumMultiple,
|
||||
}: {
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}) => {
|
||||
const ColumnHeaders = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="contents">
|
||||
@@ -93,9 +89,7 @@ const ColumnHeaders = ({
|
||||
<ColumnHeader
|
||||
key={columnTitle}
|
||||
title={t(columnTitle)}
|
||||
tooltipContent={t(description, {
|
||||
marketCreationQuantumMultiple,
|
||||
})}
|
||||
tooltipContent={t(description)}
|
||||
className={headerGridItemStyles()}
|
||||
/>
|
||||
))}
|
||||
@@ -111,7 +105,6 @@ export interface RewardTableProps {
|
||||
dataTestId: string;
|
||||
epoch: number;
|
||||
children: ReactNode;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
// Rewards table children will be the row items. Make sure they contain
|
||||
@@ -120,15 +113,12 @@ export const RewardsTable = ({
|
||||
dataTestId,
|
||||
epoch,
|
||||
children,
|
||||
marketCreationQuantumMultiple,
|
||||
}: RewardTableProps) => (
|
||||
<div data-testid={dataTestId} className="mb-12">
|
||||
<SubHeading title={`EPOCH ${epoch}`} />
|
||||
|
||||
<div className={gridStyles}>
|
||||
<ColumnHeaders
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
/>
|
||||
<ColumnHeaders />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -279,7 +279,7 @@ const routerConfig = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag/:proposalBlockHeight`,
|
||||
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
|
||||
element: <LazyProtocolUpgradeProposal />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,8 +100,8 @@ const Home = ({ name }: RouteChildProps) => {
|
||||
</Link>
|
||||
</p>
|
||||
</HomeSection>
|
||||
<div className="flex justify-between flex-wrap gap-x-12 gap-y-4">
|
||||
<div className="flex-1 min-w-[360px]">
|
||||
<div className="flex gap-12">
|
||||
<div className="flex-1">
|
||||
<HomeSection>
|
||||
<SubHeading title={t('Staking')} />
|
||||
<p>
|
||||
@@ -118,7 +118,7 @@ const Home = ({ name }: RouteChildProps) => {
|
||||
</p>
|
||||
</HomeSection>
|
||||
</div>
|
||||
<div className="flex-1 min-w-[360px]">
|
||||
<div className="flex-1">
|
||||
<HomeSection>
|
||||
<SubHeading title={t('Governance')} />
|
||||
<p>
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-2xl text-white uppercase mb-4;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
const orderbookTab = 'Orderbook';
|
||||
const orderbookTable = 'tab-orderbook';
|
||||
const askPrice = 'price-9894185';
|
||||
const bidPrice = 'price-9889001';
|
||||
const askVolume = 'ask-vol-9894185';
|
||||
const bidVolume = 'bid-vol-9889001';
|
||||
const askCumulative = 'cumulative-vol-9894185';
|
||||
const bidCumulative = 'cumulative-vol-9889001';
|
||||
const midPrice = 'last-traded-4612690000';
|
||||
const priceResolution = 'resolution';
|
||||
const dealTicketPrice = 'order-price';
|
||||
const dealTicketSize = 'order-size';
|
||||
const resPrice = 'price-990';
|
||||
|
||||
describe('order book', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
});
|
||||
|
||||
it('show order book', () => {
|
||||
// 6003-ORDB-001
|
||||
// 6003-ORDB-002
|
||||
cy.getByTestId(orderbookTab).click();
|
||||
cy.getByTestId(orderbookTable).should('be.visible');
|
||||
cy.getByTestId(orderbookTable).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('show orders prices', () => {
|
||||
// 6003-ORDB-003
|
||||
cy.getByTestId(askPrice).should('have.text', '98.94185');
|
||||
cy.getByTestId(bidPrice).should('have.text', '98.89001');
|
||||
});
|
||||
|
||||
it('show prices volumes', () => {
|
||||
// 6003-ORDB-004
|
||||
cy.getByTestId(askVolume).should('have.text', '1');
|
||||
cy.getByTestId(bidVolume).should('have.text', '1');
|
||||
});
|
||||
|
||||
it('show prices cumulative volumes', () => {
|
||||
// 6003-ORDB-005
|
||||
cy.getByTestId(askCumulative).should('have.text', '38');
|
||||
cy.getByTestId(bidCumulative).should('have.text', '7');
|
||||
});
|
||||
|
||||
it('show mid price', () => {
|
||||
// 6003-ORDB-006
|
||||
cy.getByTestId(midPrice).should('have.text', '46,126.90');
|
||||
});
|
||||
|
||||
it('sort prices descending', () => {
|
||||
// 6003-ORDB-007
|
||||
const prices: number[] = [];
|
||||
cy.getByTestId(orderbookTable).within(() => {
|
||||
cy.get('[data-testid*=price]')
|
||||
.each(($el) => {
|
||||
prices.push(Number($el.text()));
|
||||
})
|
||||
.then(() => {
|
||||
expect(prices).to.deep.equal(prices.sort((a, b) => b - a));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('copy price to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(askPrice).click();
|
||||
cy.getByTestId(dealTicketPrice).should('have.value', '98.94185');
|
||||
});
|
||||
|
||||
it('copy size to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(bidCumulative).click();
|
||||
cy.getByTestId(dealTicketSize).should('have.value', '7');
|
||||
});
|
||||
|
||||
it('copy size to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(bidVolume).click();
|
||||
cy.getByTestId(dealTicketSize).should('have.value', '1');
|
||||
});
|
||||
|
||||
it('change price resolution', () => {
|
||||
// 6003-ORDB-008
|
||||
const resolutions = [
|
||||
'0.00000',
|
||||
'0.0000',
|
||||
'0.000',
|
||||
'0.00',
|
||||
'0.0',
|
||||
'0',
|
||||
'10',
|
||||
'100',
|
||||
'1,000',
|
||||
'10,000',
|
||||
];
|
||||
cy.getByTestId(priceResolution).click();
|
||||
cy.get('[role="menu"]')
|
||||
.find('[role="menuitem"]')
|
||||
.each(($el, index) => {
|
||||
expect($el.text()).to.equal(resolutions[index]);
|
||||
});
|
||||
|
||||
cy.get('[role="menuitem"]').eq(4).click();
|
||||
cy.getByTestId(resPrice).should('have.text', '99.0');
|
||||
cy.getByTestId(askPrice).should('not.exist');
|
||||
cy.getByTestId(bidPrice).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -24,9 +24,6 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.getByTestId('deal-ticket-fee-margin-required').within(() => {
|
||||
cy.get('button').click();
|
||||
});
|
||||
});
|
||||
|
||||
describe('limit order', () => {
|
||||
|
||||
@@ -16,12 +16,12 @@ NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.21.0-core-0.72.14
|
||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-ma
|
||||
NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
|
||||
@@ -19,6 +19,6 @@ NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fa
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -19,9 +19,9 @@ NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fa
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
|
||||
@@ -1,53 +1,27 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
LocalStoragePersistTabs as Tabs,
|
||||
Tab,
|
||||
TradingAnchorButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets } from './markets';
|
||||
import { Proposed } from './proposed';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { Closed } from './closed';
|
||||
import {
|
||||
DApp,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
|
||||
export const MarketsPage = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
|
||||
useEffect(() => {
|
||||
updateTitle(titlefy(['Markets']));
|
||||
}, [updateTitle]);
|
||||
|
||||
return (
|
||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||
<div className="h-full my-1 border rounded-sm border-default">
|
||||
<div className="h-full my-1 border border-default rounded-sm">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
name={t('Proposed markets')}
|
||||
menu={
|
||||
<TradingAnchorButton
|
||||
size="extra-small"
|
||||
data-testid="propose-new-market"
|
||||
href={externalLink}
|
||||
>
|
||||
{t('Propose a new market')}
|
||||
</TradingAnchorButton>
|
||||
}
|
||||
>
|
||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||
<Proposed />
|
||||
</Tab>
|
||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DApp,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Proposed = () => {
|
||||
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
return (
|
||||
<>
|
||||
<div className="h-[400px]">
|
||||
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
|
||||
</div>
|
||||
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
||||
{t('Propose a new market')}
|
||||
</ExternalLink>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import compact from 'lodash/compact';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
||||
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -12,13 +12,12 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Splash,
|
||||
Toggle,
|
||||
TradingButton,
|
||||
TradingDropdown,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItem,
|
||||
TradingDropdownTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { PriceChart } from 'pennant';
|
||||
@@ -151,7 +150,6 @@ const AccountHistoryManager = ({
|
||||
)
|
||||
: null;
|
||||
}, [accounts, marketFilterCb]);
|
||||
|
||||
const resolveMarket = useCallback(
|
||||
(m: Market) => {
|
||||
setMarket(m);
|
||||
@@ -176,114 +174,111 @@ const AccountHistoryManager = ({
|
||||
}),
|
||||
[pubKey, asset, accountType, range, market?.id]
|
||||
);
|
||||
|
||||
const { data } = useAccountHistoryQuery({
|
||||
variables,
|
||||
skip: !asset || !pubKey,
|
||||
});
|
||||
|
||||
const accountTypeMenu = useMemo(() => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{accountType
|
||||
? `${
|
||||
AccountTypeMapping[
|
||||
accountType as keyof typeof Schema.AccountType
|
||||
]
|
||||
} Account`
|
||||
: t('Select account type')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{[
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_BOND,
|
||||
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
].map((type) => (
|
||||
<DropdownMenuItem
|
||||
key={type}
|
||||
onClick={() => setAccountType(type as Schema.AccountType)}
|
||||
>
|
||||
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}, [accountType]);
|
||||
const assetsMenu = useMemo(() => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{asset ? asset.symbol : t('Select asset')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{assets.map((a) => (
|
||||
<DropdownMenuItem key={a.id} onClick={() => setAssetId(a.id)}>
|
||||
{a.symbol}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}, [asset, assets, setAssetId]);
|
||||
const marketsMenu = useMemo(() => {
|
||||
return accountType === Schema.AccountType.ACCOUNT_TYPE_MARGIN &&
|
||||
markets?.length ? (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{market
|
||||
? market.tradableInstrument.instrument.code
|
||||
: t('Select market')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{market && (
|
||||
<DropdownMenuItem key="0" onClick={() => setMarket(null)}>
|
||||
{t('All markets')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{markets?.map((m) => (
|
||||
<DropdownMenuItem key={m.id} onClick={() => resolveMarket(m)}>
|
||||
{m.tradableInstrument.instrument.code}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null;
|
||||
}, [markets, market, accountType, resolveMarket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
accountType !== Schema.AccountType.ACCOUNT_TYPE_MARGIN ||
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.id !==
|
||||
asset?.id
|
||||
) {
|
||||
setMarket(null);
|
||||
}
|
||||
}, [accountType, asset?.id, market]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-2">
|
||||
<div className="flex flex-wrap justify-between px-1 pt-2 gap-2">
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{accountType
|
||||
? `${
|
||||
AccountTypeMapping[
|
||||
accountType as keyof typeof Schema.AccountType
|
||||
]
|
||||
} Account`
|
||||
: t('Select account type')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{[
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_BOND,
|
||||
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
].map((type) => (
|
||||
<TradingDropdownItem
|
||||
key={type}
|
||||
onClick={() => {
|
||||
setAccountType(type as Schema.AccountType);
|
||||
|
||||
// if not a margin account clear any market selection
|
||||
if (type !== Schema.AccountType.ACCOUNT_TYPE_MARGIN) {
|
||||
setMarket(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{asset ? asset.symbol : t('Select asset')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{assets.map((a) => (
|
||||
<TradingDropdownItem
|
||||
key={a.id}
|
||||
onClick={() => {
|
||||
setAssetId(a.id);
|
||||
|
||||
// if the selected asset is different to the selected market clear the market
|
||||
if (
|
||||
a.id !==
|
||||
market?.tradableInstrument.instrument.product
|
||||
.settlementAsset.id
|
||||
) {
|
||||
setMarket(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{a.symbol}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{market
|
||||
? market.tradableInstrument.instrument.code
|
||||
: t('Select market')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{market && (
|
||||
<TradingDropdownItem key="0" onClick={() => setMarket(null)}>
|
||||
{t('All markets')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
{markets?.map((m) => (
|
||||
<TradingDropdownItem
|
||||
key={m.id}
|
||||
onClick={() => resolveMarket(m)}
|
||||
>
|
||||
{m.tradableInstrument.instrument.code}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<div className="h-full w-full flex flex-col gap-8">
|
||||
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
|
||||
<div className="flex items-center gap-4 shrink-0">
|
||||
<>
|
||||
{accountTypeMenu}
|
||||
{assetsMenu}
|
||||
{marketsMenu}
|
||||
</>
|
||||
</div>
|
||||
<div className="justify-items-end">
|
||||
<div className="pt-1 justify-items-end">
|
||||
<Toggle
|
||||
id="account-history-date-range"
|
||||
name="account-history-date-range"
|
||||
@@ -292,19 +287,16 @@ const AccountHistoryManager = ({
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setRange(e.target.value as keyof typeof DateRange)
|
||||
}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="h-5/6 px-4">
|
||||
{asset && (
|
||||
<div className="h-full">
|
||||
<AccountHistoryChart
|
||||
data={data}
|
||||
accountType={accountType}
|
||||
asset={asset}
|
||||
/>
|
||||
</div>
|
||||
<AccountHistoryChart
|
||||
data={data}
|
||||
accountType={accountType}
|
||||
asset={asset}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const AccountsMenu = () => {
|
||||
@@ -8,6 +8,7 @@ export const AccountsMenu = () => {
|
||||
return (
|
||||
<>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
@@ -15,6 +16,7 @@ export const AccountsMenu = () => {
|
||||
{t('Transfer')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const DepositsMenu = () => {
|
||||
@@ -7,6 +7,7 @@ export const DepositsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
TradingDropdown,
|
||||
TradingDropdownCheckboxItem,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuTrigger,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelectorButton } from './market-selector-button';
|
||||
|
||||
type Assets = Array<{ id: string; symbol: string }>;
|
||||
|
||||
@@ -24,19 +25,17 @@ export const AssetDropdown = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TradingDropdown
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<TradingDropdownTrigger data-testid="asset-trigger">
|
||||
<MarketSelectorButton>
|
||||
{triggerText({ assets, checkedAssets })}
|
||||
</MarketSelectorButton>
|
||||
</TradingDropdownTrigger>
|
||||
<DropdownMenuTrigger data-testid="asset-trigger">
|
||||
<TriggerText assets={assets} checkedAssets={checkedAssets} />
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
<DropdownMenuContent>
|
||||
{assets?.map((a) => {
|
||||
return (
|
||||
<TradingDropdownCheckboxItem
|
||||
<DropdownMenuCheckboxItem
|
||||
key={a.id}
|
||||
checked={checkedAssets.includes(a.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
@@ -47,16 +46,16 @@ export const AssetDropdown = ({
|
||||
data-testid={`asset-id-${a.id}`}
|
||||
>
|
||||
{a.symbol}
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
const triggerText = ({
|
||||
const TriggerText = ({
|
||||
assets,
|
||||
checkedAssets,
|
||||
}: {
|
||||
@@ -73,5 +72,9 @@ const triggerText = ({
|
||||
text = t(`${checkedAssets.length} Assets`);
|
||||
}
|
||||
|
||||
return text;
|
||||
return (
|
||||
<span className="flex justify-between items-center">
|
||||
{text} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './market-selector';
|
||||
export * from './market-selector-item';
|
||||
export * from './market-selector-button';
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
export const MarketSelectorButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonHTMLAttributes<HTMLButtonElement>
|
||||
>((props, ref) => (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(
|
||||
'flex items-center justify-between px-2 border rounded gap-1',
|
||||
'border-vega-clight-600 dark:border-vega-cdark-600 bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||
'text-secondary data-[state=open]:text-vega-clight-50 dark:data-[state=open]:text-vega-cdark-50'
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{props.children}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</button>
|
||||
));
|
||||
MarketSelectorButton.displayName = 'MarketSelectorButton';
|
||||
@@ -31,7 +31,7 @@ export const MarketSelectorItem = ({
|
||||
<div style={style} role="row">
|
||||
<Link
|
||||
to={`/markets/${market.id}`}
|
||||
className={classNames('h-full flex items-center gap-2 mx-2 px-2', {
|
||||
className={classNames('h-full flex items-center gap-2 px-4', {
|
||||
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
|
||||
market.id !== currentMarketId,
|
||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||
@@ -94,7 +94,7 @@ const MarketData = ({
|
||||
return (
|
||||
<>
|
||||
<div className="w-2/5" role="gridcell">
|
||||
<h3 className="overflow-hidden text-sm text-ellipsis lg:text-base whitespace-nowrap">
|
||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||
{market.tradableInstrument.instrument.code}{' '}
|
||||
{allProducts && productType && (
|
||||
<MarketProductPill productType={productType} />
|
||||
@@ -107,7 +107,7 @@ const MarketData = ({
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
|
||||
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"
|
||||
@@ -115,14 +115,14 @@ const MarketData = ({
|
||||
{price} {instrument.product.settlementAsset.symbol}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
|
||||
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"
|
||||
>
|
||||
{volume}
|
||||
</div>
|
||||
<div className="flex justify-end w-1/5" role="gridcell">
|
||||
<div className="w-1/5 flex justify-end" role="gridcell">
|
||||
{oneDayCandles && (
|
||||
<Sparkline
|
||||
width={64}
|
||||
|
||||
@@ -262,7 +262,9 @@ describe('MarketSelector', () => {
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
const options = screen.getAllByTestId(/sort-item/);
|
||||
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
||||
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
|
||||
Object.entries(Sort)
|
||||
.filter(([key]) => key !== Sort.None)
|
||||
.map(([key]) => SortTypeMapping[key as SortType])
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||
expect(
|
||||
|
||||
@@ -40,7 +40,7 @@ export const MarketSelector = ({
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
const allProducts = filter.product === Product.All;
|
||||
@@ -52,8 +52,8 @@ export const MarketSelector = ({
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector" className="md:w-[580px]">
|
||||
<div className="px-2 pt-2 mb-2">
|
||||
<div data-testid="market-selector">
|
||||
<div className="pt-2 px-2 mb-2">
|
||||
<ProductSelector
|
||||
product={filter.product}
|
||||
onSelect={(product) => {
|
||||
@@ -106,6 +106,9 @@ export const MarketSelector = ({
|
||||
currentSort={filter.sort}
|
||||
onSelect={(sort) => {
|
||||
setFilter((curr) => {
|
||||
if (curr.sort === sort) {
|
||||
return { ...curr, sort: Sort.None };
|
||||
}
|
||||
return {
|
||||
...curr,
|
||||
sort,
|
||||
@@ -291,9 +294,9 @@ const List = ({
|
||||
|
||||
const Skeleton = () => {
|
||||
return (
|
||||
<div className="px-2 mb-2">
|
||||
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
|
||||
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
<div className="mb-2 px-2">
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
|
||||
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
|
||||
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,14 +33,11 @@ export const ProductSelector = ({
|
||||
return (
|
||||
<div className="flex mb-2">
|
||||
{Object.keys(Product).map((t) => {
|
||||
const classes = classNames(
|
||||
'text-sm px-3 py-1.5 rounded hover:text-vega-clight-50 dark:hover:text-vega-cdark-50',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
||||
t === product,
|
||||
'text-secondary': t !== product,
|
||||
}
|
||||
);
|
||||
const classes = classNames('px-3 py-1.5 rounded', {
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
||||
t === product,
|
||||
'text-secondary': t !== product,
|
||||
});
|
||||
return (
|
||||
<button
|
||||
key={t}
|
||||
@@ -56,7 +53,7 @@ export const ProductSelector = ({
|
||||
})}
|
||||
<Link
|
||||
to={Routes.MARKETS}
|
||||
className="flex items-center ml-auto text-sm gap-2"
|
||||
className="flex items-center gap-2 ml-auto"
|
||||
title={t('See all markets')}
|
||||
>
|
||||
<span className="underline underline-offset-4">{t('Browse')}</span>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
TradingDropdown,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelectorButton } from './market-selector-button';
|
||||
|
||||
export const Sort = {
|
||||
None: 'None',
|
||||
Gained: 'Gained',
|
||||
Lost: 'Lost',
|
||||
New: 'New',
|
||||
@@ -22,15 +23,17 @@ export type SortType = keyof typeof Sort;
|
||||
export const SortTypeMapping: {
|
||||
[key in SortType]: string;
|
||||
} = {
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
[Sort.None]: 'None',
|
||||
[Sort.Gained]: 'Top gaining',
|
||||
[Sort.Lost]: 'Top losing',
|
||||
[Sort.New]: 'New markets',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
};
|
||||
|
||||
const SortIconMapping: {
|
||||
[key in SortType]: VegaIconNames;
|
||||
} = {
|
||||
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
|
||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||
[Sort.New]: VegaIconNames.STAR,
|
||||
@@ -45,38 +48,43 @@ export const SortDropdown = ({
|
||||
onSelect: (sort: SortType) => void;
|
||||
}) => {
|
||||
return (
|
||||
<TradingDropdown
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<TradingDropdownTrigger data-testid="sort-trigger">
|
||||
<MarketSelectorButton>
|
||||
{SortTypeMapping[currentSort]}
|
||||
</MarketSelectorButton>
|
||||
</TradingDropdownTrigger>
|
||||
<DropdownMenuTrigger data-testid="sort-trigger">
|
||||
<span className="flex justify-between items-center">
|
||||
{currentSort === SortTypeMapping.None
|
||||
? t('Sort')
|
||||
: SortTypeMapping[currentSort]}{' '}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
<TradingDropdownRadioGroup
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={currentSort}
|
||||
onValueChange={(value) => onSelect(value as SortType)}
|
||||
>
|
||||
{Object.keys(Sort).map((key) => {
|
||||
return (
|
||||
<TradingDropdownRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
);
|
||||
})}
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
{Object.keys(Sort)
|
||||
.filter((s) => s !== Sort.None)
|
||||
.map((key) => {
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,10 +12,7 @@ import { useMarketList } from '@vegaprotocol/markets';
|
||||
import type { Filter } from '../../components/market-selector';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
...jest.requireActual('@vegaprotocol/markets'),
|
||||
useMarketList: jest.fn(),
|
||||
}));
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
describe('useMarketSelectorList', () => {
|
||||
@@ -23,7 +20,7 @@ describe('useMarketSelectorList', () => {
|
||||
const defaultArgs: Filter = {
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
};
|
||||
return renderHook((args) => useMarketSelectorList(args), {
|
||||
@@ -112,21 +109,21 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Spot as 'Future',
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Perpetual as 'Future',
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual(markets);
|
||||
@@ -192,7 +189,7 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: ['asset-0'],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0], markets[1]]);
|
||||
@@ -200,7 +197,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: ['asset-0', 'asset-1'],
|
||||
});
|
||||
|
||||
@@ -213,7 +210,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: ['asset-0', 'asset-1', 'asset-2'],
|
||||
});
|
||||
|
||||
@@ -223,7 +220,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: ['asset-invalid'],
|
||||
});
|
||||
|
||||
@@ -278,28 +275,28 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: 'abc',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'def',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1], markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'defg',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'zzz',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([]);
|
||||
@@ -308,14 +305,14 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: 'aaa',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'ggg',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([
|
||||
@@ -325,15 +322,11 @@ describe('useMarketSelectorList', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('sorts by top traded by default', () => {
|
||||
it('sorts by state and volume by default', () => {
|
||||
const markets = [
|
||||
createMarketFragment({
|
||||
id: 'market-0',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
state: MarketState.STATE_PENDING,
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
@@ -344,10 +337,16 @@ describe('useMarketSelectorList', () => {
|
||||
createMarketFragment({
|
||||
id: 'market-1',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '200',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-2',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
@@ -356,30 +355,12 @@ describe('useMarketSelectorList', () => {
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-2',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '300',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
state: MarketState.STATE_PENDING,
|
||||
id: 'market-3',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '400',
|
||||
volume: '100',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -394,15 +375,14 @@ describe('useMarketSelectorList', () => {
|
||||
const { result } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.TopTraded,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
expect(result.current.markets).toEqual([
|
||||
markets[3],
|
||||
markets[1],
|
||||
markets[2],
|
||||
markets[0],
|
||||
markets[1],
|
||||
markets[3],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import {
|
||||
calcCandleVolume,
|
||||
calcTradedFactor,
|
||||
useMarketList,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
@@ -56,6 +60,22 @@ export const useMarketSelectorList = ({
|
||||
return false;
|
||||
});
|
||||
|
||||
if (sort === Sort.None) {
|
||||
// Sort by market state primarily and AtoZ secondarily
|
||||
return orderBy(
|
||||
markets,
|
||||
[
|
||||
(m) => MARKET_TEMPLATE.indexOf(m.state),
|
||||
(m) => {
|
||||
if (!m.candles?.length) return 0;
|
||||
const vol = calcCandleVolume(m.candles);
|
||||
return Number(vol || 0);
|
||||
},
|
||||
],
|
||||
['asc', 'desc']
|
||||
);
|
||||
}
|
||||
|
||||
if (sort === Sort.Gained || sort === Sort.Lost) {
|
||||
const dir = sort === Sort.Gained ? 'desc' : 'asc';
|
||||
return orderBy(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePositionsStore } from '../positions-container';
|
||||
|
||||
export const PositionsMenu = () => {
|
||||
@@ -7,6 +7,7 @@ export const PositionsMenu = () => {
|
||||
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={toggle}
|
||||
|
||||
@@ -24,8 +24,8 @@ export const Settings = () => {
|
||||
>
|
||||
<Switch
|
||||
name="settings-telemetry-switch"
|
||||
onCheckedChange={(isOn) => setIsApproved(isOn ? 'true' : 'false')}
|
||||
checked={isApproved === 'true'}
|
||||
onCheckedChange={(isOn) => setIsApproved(isOn)}
|
||||
checked={isApproved}
|
||||
/>
|
||||
</SettingsGroup>
|
||||
<SettingsGroup label={t('Toast location')}>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './vega-wallet-container';
|
||||
@@ -0,0 +1,27 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContainer } from './vega-wallet-container';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
const generateJsx = (context: PartialDeep<VegaWalletContextShape>) => {
|
||||
return (
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<VegaWalletContainer>
|
||||
<div data-testid="child" />
|
||||
</VegaWalletContainer>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('VegaWalletContainer', () => {
|
||||
it('doesnt render children if not connected', () => {
|
||||
render(generateJsx({ pubKey: null }));
|
||||
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children if connected', () => {
|
||||
render(generateJsx({ pubKey: '0x123' }));
|
||||
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
|
||||
interface VegaWalletContainerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<div className="text-center">
|
||||
<p className="mb-4" data-testid="connect-vega-wallet-text">
|
||||
{t('Connect your Vega wallet')}
|
||||
</p>
|
||||
<Button
|
||||
onClick={openVegaWalletDialog}
|
||||
data-testid="vega-wallet-connect"
|
||||
>
|
||||
{t('Connect')}
|
||||
</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -22,7 +22,7 @@ describe('GetStarted', () => {
|
||||
};
|
||||
const checkTicks = (elements: Element[]) => {
|
||||
elements.forEach((item, i) => {
|
||||
if (i + 1 < mockStep - 1) {
|
||||
if (i + 1 < mockStep) {
|
||||
expect(item.querySelector('[data-testid="icon-tick"]')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
@@ -57,9 +57,19 @@ describe('GetStarted', () => {
|
||||
'get'
|
||||
);
|
||||
navigatorGetter.mockReturnValue('Chrome');
|
||||
mockStep = 1;
|
||||
const { rerender, container } = renderComponent();
|
||||
expect(screen.queryByTestId('icon-tick')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('get-wallet-button')).toBeInTheDocument();
|
||||
|
||||
mockStep = 2;
|
||||
const { rerender, container } = renderComponent();
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
|
||||
|
||||
@@ -83,9 +93,7 @@ describe('GetStarted', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Ready to trade' })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
||||
|
||||
mockStep = 5;
|
||||
rerender(
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
GetWalletButton,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -19,19 +23,19 @@ import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useSidebar, ViewType } from '../sidebar';
|
||||
import * as constants from '../constants';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
@@ -42,17 +46,25 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
let onClickHandle = () => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return (
|
||||
<GetWalletButton
|
||||
className="justify-between"
|
||||
chromeExtensionUrl={CHROME_EXTENSION_URL}
|
||||
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
|
||||
/>
|
||||
);
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
buttonText = t('Deposit');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Deposit });
|
||||
dismiss();
|
||||
update({ onBoardingDismissed: true });
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Ready to trade');
|
||||
buttonText = t('Dismiss');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Order });
|
||||
@@ -103,18 +115,23 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
<ul className="list-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Get a Vega wallet')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_WALLET_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
step={3}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
step={4}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
|
||||
@@ -2,35 +2,29 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
describe('TelemetryApproval', () => {
|
||||
it('click on buttons should be properly handled', async () => {
|
||||
const mockSetTelemetryValue = jest.fn();
|
||||
render(
|
||||
<TelemetryApproval
|
||||
telemetryValue="false"
|
||||
setTelemetryValue={mockSetTelemetryValue}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'No thanks' })
|
||||
).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'No thanks' }));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('false');
|
||||
expect(screen.getByText('Share data')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Share data'));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
|
||||
});
|
||||
jest.mock('@vegaprotocol/logger', () => ({
|
||||
SentryInit: () => undefined,
|
||||
SentryClose: () => undefined,
|
||||
}));
|
||||
|
||||
it('confirm button should have proper text', async () => {
|
||||
const mockSetTelemetryValue = jest.fn();
|
||||
render(
|
||||
<TelemetryApproval
|
||||
telemetryValue="true"
|
||||
setTelemetryValue={mockSetTelemetryValue}
|
||||
/>
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
}));
|
||||
|
||||
describe('TelemetryApproval', () => {
|
||||
it('click on checkbox should be properly handled', async () => {
|
||||
const helpText = 'My help text';
|
||||
render(<TelemetryApproval helpText={helpText} />);
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
expect(screen.getByText('Continue sharing data')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Continue sharing data'));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
|
||||
await userEvent.click(screen.getByRole('checkbox'));
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByText('Share usage data')).toBeInTheDocument();
|
||||
expect(screen.getByText(helpText)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,69 +1,21 @@
|
||||
import {
|
||||
Intent,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingCheckbox } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
|
||||
interface Props {
|
||||
telemetryValue: string;
|
||||
setTelemetryValue: (value: string) => void;
|
||||
}
|
||||
|
||||
export const TelemetryApproval = ({
|
||||
telemetryValue,
|
||||
setTelemetryValue,
|
||||
}: Props) => {
|
||||
export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
|
||||
const [isApproved, setIsApproved] = useTelemetryApproval();
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col py-3">
|
||||
<div className="mr-4" role="form">
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'Help us identify bugs and improve Vega Governance by sharing anonymous usage data.'
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-start mb-2 gap-3">
|
||||
<VegaIcon name={VegaIconNames.EYE_OFF} size={18} />
|
||||
<div className="flex flex-col gap-1">
|
||||
<h6 className="font-semibold">{t('Anonymous')}</h6>
|
||||
<p className="text-muted">
|
||||
{t('Your identity is always anonymous on Vega')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start mb-4 gap-3">
|
||||
<VegaIcon name={VegaIconNames.COG} size={18} />
|
||||
<div className="flex flex-col gap-1">
|
||||
<h6 className="font-semibold">{t('Optional')}</h6>
|
||||
<p className="text-muted">
|
||||
{t('You can opt out any time via settings')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-around gap-2">
|
||||
<TradingButton
|
||||
onClick={() => setTelemetryValue('false')}
|
||||
size="small"
|
||||
intent={Intent.None}
|
||||
data-testid="do-not-share-data-button"
|
||||
fill
|
||||
>
|
||||
{t('No thanks')}
|
||||
</TradingButton>
|
||||
|
||||
<TradingButton
|
||||
onClick={() => setTelemetryValue('true')}
|
||||
intent={Intent.Info}
|
||||
data-testid="share-data-button"
|
||||
size="small"
|
||||
fill
|
||||
>
|
||||
{telemetryValue === 'true'
|
||||
? t('Continue sharing data')
|
||||
: t('Share data')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
<TradingCheckbox
|
||||
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
|
||||
checked={isApproved}
|
||||
name="telemetry-approval"
|
||||
onCheckedChange={() => setIsApproved(!isApproved)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-vega-light-300 dark:text-vega-dark-300 ml-6">
|
||||
<span>{helpText}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
|
||||
@@ -21,7 +23,7 @@ let mockContext: Partial<VegaWalletContextShape> = { pubKey: 'test-pubkey' };
|
||||
describe('useGetOnboardingStep', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockData = [];
|
||||
mockData = [{ id: 'item-id' }];
|
||||
mockContext = { pubKey: 'test-pubkey' };
|
||||
globalThis.window.vega = {} as Vega;
|
||||
});
|
||||
@@ -40,6 +42,13 @@ describe('useGetOnboardingStep', () => {
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_UNKNOWN_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_WALLET_STEP', () => {
|
||||
// @ts-ignore test only purpose
|
||||
globalThis.window.vega = undefined;
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_WALLET_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_CONNECT_STEP', () => {
|
||||
mockContext = { pubKey: null };
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
@@ -47,6 +56,15 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_DEPOSIT_STEP', async () => {
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === depositsProvider ||
|
||||
args.dataProvider === aggregatedAccountsDataProvider
|
||||
) {
|
||||
return { data: [] };
|
||||
}
|
||||
return { data: mockData };
|
||||
});
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
await expect(result.current).toEqual(
|
||||
OnboardingStep.ONBOARDING_DEPOSIT_STEP
|
||||
@@ -54,7 +72,6 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_ORDER_STEP', async () => {
|
||||
mockData = [{ id: 'item-id' }];
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === ordersWithMarketProvider ||
|
||||
@@ -69,7 +86,6 @@ describe('useGetOnboardingStep', () => {
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_COMPLETE_STEP', async () => {
|
||||
mockData = [{ id: 'item-id' }];
|
||||
(useDataProvider as jest.Mock).mockImplementation(() => {
|
||||
return { data: mockData };
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { isBrowserWalletInstalled, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
@@ -63,21 +63,20 @@ export const useGetOnboardingStep = () => {
|
||||
collateralData === null ||
|
||||
positionsData === null)
|
||||
);
|
||||
let step = OnboardingStep.ONBOARDING_UNKNOWN_STEP;
|
||||
if (isLoading) {
|
||||
return step;
|
||||
return OnboardingStep.ONBOARDING_UNKNOWN_STEP;
|
||||
}
|
||||
if (!isBrowserWalletInstalled()) {
|
||||
return OnboardingStep.ONBOARDING_WALLET_STEP;
|
||||
}
|
||||
if (!pubKey) {
|
||||
step = OnboardingStep.ONBOARDING_CONNECT_STEP;
|
||||
return OnboardingStep.ONBOARDING_CONNECT_STEP;
|
||||
}
|
||||
if (pubKey) {
|
||||
step = OnboardingStep.ONBOARDING_DEPOSIT_STEP;
|
||||
if (!deposits && !collaterals) {
|
||||
return OnboardingStep.ONBOARDING_DEPOSIT_STEP;
|
||||
}
|
||||
if (pubKey && (deposits || collaterals)) {
|
||||
step = OnboardingStep.ONBOARDING_ORDER_STEP;
|
||||
if (!orders && !positions) {
|
||||
return OnboardingStep.ONBOARDING_ORDER_STEP;
|
||||
}
|
||||
if (pubKey && (orders || positions)) {
|
||||
step = OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
}
|
||||
return step;
|
||||
return OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
};
|
||||
|
||||
@@ -5,17 +5,17 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
update({ onBoardingDismissed: true });
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
@@ -14,41 +12,15 @@ import {
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import * as constants from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
|
||||
export const useOnboardingStore = create<{
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dismissed: false,
|
||||
dismiss: () => set(() => ({ dismissed: true })),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const dismissed = useGlobalStore((store) => store.onBoardingDismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const isTelemetryPopupNeeded =
|
||||
isTelemetryNeeded &&
|
||||
(onBoardingViewed === 'true' ||
|
||||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
@@ -57,58 +29,12 @@ export const WelcomeDialog = () => {
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onClose = () => {
|
||||
if (isTelemetryPopupNeeded) {
|
||||
closeTelemetry();
|
||||
} else {
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
}
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
update({ onBoardingDismissed: true });
|
||||
};
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [removeToast, closeTelemetry]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[setTelemetryValue, onApprovalClose]
|
||||
);
|
||||
|
||||
if (isTelemetryPopupNeeded) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const WithdrawalsMenu = () => {
|
||||
@@ -7,6 +7,7 @@ export const WithdrawalsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
|
||||
@@ -1,40 +1,19 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import {
|
||||
STORAGE_KEY,
|
||||
STORAGE_SECOND_KEY,
|
||||
useTelemetryApproval,
|
||||
} from './use-telemetry-approval';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
|
||||
|
||||
const mockSetValue = jest.fn();
|
||||
let mockStorageHookApprovalResult: [string | null, jest.Mock] = [
|
||||
null,
|
||||
mockSetValue,
|
||||
];
|
||||
const mockSetSecondValue = jest.fn();
|
||||
let mockStorageHookViewedResult: [string | null, jest.Mock] = [
|
||||
null,
|
||||
mockSetSecondValue,
|
||||
];
|
||||
const mockRemoveValue = jest.fn();
|
||||
jest.mock('@vegaprotocol/logger');
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useLocalStorage: jest.fn((key: string) => {
|
||||
if (key === 'vega_telemetry_approval') {
|
||||
return mockStorageHookApprovalResult;
|
||||
}
|
||||
return mockStorageHookViewedResult;
|
||||
}),
|
||||
useLocalStorage: jest
|
||||
.fn()
|
||||
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
|
||||
}));
|
||||
let mockVegaEnv = 'test';
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
useEnvironment: jest.fn(() => ({
|
||||
VEGA_ENV: mockVegaEnv,
|
||||
SENTRY_DSN: 'sentry-dsn',
|
||||
})),
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
}));
|
||||
|
||||
describe('useTelemetryApproval', () => {
|
||||
@@ -42,71 +21,32 @@ describe('useTelemetryApproval', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('when empty hook should return proper array', () => {
|
||||
it('hook should return proper array', () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual('');
|
||||
expect(result.current[0]).toEqual(false);
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(true);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
|
||||
expect(mockSetValue).toHaveBeenCalledWith('true');
|
||||
expect(mockSetSecondValue).not.toHaveBeenCalledWith('true');
|
||||
});
|
||||
|
||||
it('when approval not empty but viewed is empty should return proper array', () => {
|
||||
mockStorageHookApprovalResult = ['false', mockSetValue];
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual('false');
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(true);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
|
||||
expect(mockSetValue).not.toHaveBeenCalled();
|
||||
expect(mockSetSecondValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('when NOT empty hook should return proper array', () => {
|
||||
mockStorageHookApprovalResult = ['false', mockSetValue];
|
||||
mockStorageHookViewedResult = ['true', mockSetSecondValue];
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual('false');
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(false);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(mockSetValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('on mainnet hook should init properly', () => {
|
||||
mockStorageHookApprovalResult = [null, mockSetValue];
|
||||
mockVegaEnv = Networks.MAINNET;
|
||||
renderHook(() => useTelemetryApproval());
|
||||
expect(mockSetValue).toHaveBeenCalledWith('false');
|
||||
});
|
||||
|
||||
it('hook should init stuff properly', async () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
result.current[1]('true');
|
||||
result.current[1](true);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryInit).toHaveBeenCalled();
|
||||
expect(mockSetValue).toHaveBeenCalledWith('true');
|
||||
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
|
||||
expect(mockSetValue).toHaveBeenCalledWith('1');
|
||||
});
|
||||
});
|
||||
|
||||
it('hook should close stuff properly', async () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
result.current[1]('false');
|
||||
result.current[1](false);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryClose).toHaveBeenCalled();
|
||||
expect(mockSetValue).toHaveBeenCalledWith('false');
|
||||
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
|
||||
expect(mockRemoveValue).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,51 +1,25 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
export const STORAGE_KEY = 'vega_telemetry_approval';
|
||||
export const STORAGE_SECOND_KEY = 'vega_telemetry_viewed';
|
||||
|
||||
export const useTelemetryApproval = (): [
|
||||
value: string,
|
||||
setValue: (value: string) => void,
|
||||
shouldOpen: boolean,
|
||||
close: () => void
|
||||
value: boolean,
|
||||
setValue: (value: boolean) => void
|
||||
] => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const defaultTelemetryValue =
|
||||
VEGA_ENV === Networks.MAINNET ? 'false' : 'true';
|
||||
const [value, setValue] = useLocalStorage(STORAGE_KEY);
|
||||
const [viewedValue, setViewedValue] = useLocalStorage(STORAGE_SECOND_KEY);
|
||||
const [shouldOpen, setShouldOpen] = useState(!value || !viewedValue);
|
||||
const close = useCallback(() => {
|
||||
setShouldOpen(false);
|
||||
setViewedValue('true');
|
||||
}, [setViewedValue]);
|
||||
const manageValue = useCallback(
|
||||
(value: string) => {
|
||||
if (value === 'true' && SENTRY_DSN) {
|
||||
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
|
||||
const setApprove = useCallback(
|
||||
(value: boolean) => {
|
||||
if (value && SENTRY_DSN) {
|
||||
SentryInit(SENTRY_DSN, VEGA_ENV);
|
||||
return setValue('true');
|
||||
return setValue('1');
|
||||
}
|
||||
SentryClose();
|
||||
setValue('false');
|
||||
removeValue();
|
||||
},
|
||||
[setValue, SENTRY_DSN, VEGA_ENV]
|
||||
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
|
||||
);
|
||||
const setTelemetryValue = useCallback(
|
||||
(value: string) => {
|
||||
setShouldOpen(false);
|
||||
setViewedValue('true');
|
||||
manageValue(value);
|
||||
},
|
||||
[manageValue, setViewedValue]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
manageValue(defaultTelemetryValue);
|
||||
}
|
||||
}, [value, manageValue, defaultTelemetryValue]);
|
||||
|
||||
return [value || '', setTelemetryValue, shouldOpen, close];
|
||||
return [Boolean(value), setApprove];
|
||||
};
|
||||
|
||||
@@ -4,37 +4,22 @@ export default function Document() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{/*
|
||||
meta tags
|
||||
- next advised against using _document for this, so they exist in our
|
||||
- single page index.page.tsx
|
||||
*/}
|
||||
|
||||
{/* icons */}
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
|
||||
{/* fonts */}
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
|
||||
{/* styles */}
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
|
||||
{/* scripts */}
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<Html>
|
||||
|
||||
@@ -10,16 +10,23 @@ export default function Index() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charSet="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Vega Protocol - Console" />
|
||||
<meta name="description" content="Vega Protocol - VEGA Console" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:url" content="https://console.vega.xyz/" />
|
||||
<meta name="og:title" content="Vega Protocol - Console" />
|
||||
<meta name="og:site_name" content="Vega Protocol - Console" />
|
||||
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
|
||||
<meta
|
||||
name="twitter:card"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
@@ -32,6 +39,26 @@ export default function Index() {
|
||||
/>
|
||||
<meta name="twitter:image:alt" content="VEGA logo" />
|
||||
<meta name="twitter:site" content="@vegaprotocol" />
|
||||
|
||||
<meta name="description" content="Vega Protocol - Console" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<ClientRouter />
|
||||
</>
|
||||
|
||||
@@ -36,7 +36,7 @@ body,
|
||||
}
|
||||
|
||||
/**
|
||||
* ALLOTMENT
|
||||
* ALLOTMENT
|
||||
*/
|
||||
|
||||
html {
|
||||
@@ -81,8 +81,6 @@ html [data-theme='light'] {
|
||||
--pennant-color-macd-macd: theme(colors.vega.yellow.500);
|
||||
|
||||
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
|
||||
|
||||
--pennant-candlestick-inner-padding: 0.1;
|
||||
}
|
||||
|
||||
html [data-theme='light'] {
|
||||
|
||||
@@ -4,6 +4,7 @@ import produce from 'immer';
|
||||
|
||||
interface GlobalStore {
|
||||
marketId: string | null;
|
||||
onBoardingDismissed: boolean;
|
||||
eagerConnecting: boolean;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
}
|
||||
@@ -15,6 +16,7 @@ interface PageTitleStore {
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
onBoardingDismissed: false,
|
||||
eagerConnecting: false,
|
||||
update: (newState) => {
|
||||
set(
|
||||
|
||||
@@ -7,7 +7,7 @@ envCmd=""
|
||||
|
||||
if [[ ! -z "${ENV_NAME}" ]]; then
|
||||
if [[ "${ENV_NAME}" != "ops-vega" ]]; then
|
||||
envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
|
||||
envCmd="envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@ WORKDIR /app
|
||||
ARG APP
|
||||
ARG ENV_NAME=""
|
||||
RUN apk add --update --no-cache \
|
||||
python3==3.10.11-r0 \
|
||||
make==4.3-r0 \
|
||||
gcc==11.2.1_git20220219-r2 \
|
||||
g++==11.2.1_git20220219-r2
|
||||
COPY . ./
|
||||
RUN yarn --pure-lockfile
|
||||
RUN yarn --network-timeout 100000 --pure-lockfile
|
||||
# work around for different build process in trading
|
||||
RUN sh docker/docker-build.sh
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/accounts",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,6 +5,26 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/accounts",
|
||||
"tsConfig": "libs/accounts/tsconfig.lib.json",
|
||||
"project": "libs/accounts/package.json",
|
||||
"entryFile": "libs/accounts/src/index.ts",
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/accounts/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ETHERSCAN_ADDRESS, useEtherscanLink } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ActionsDropdown,
|
||||
TradingDropdownCopyItem,
|
||||
TradingDropdownItem,
|
||||
DropdownMenuCopyItem,
|
||||
DropdownMenuItem,
|
||||
Link,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
@@ -30,49 +30,49 @@ export const AccountsActionsDropdown = ({
|
||||
|
||||
return (
|
||||
<ActionsDropdown>
|
||||
<TradingDropdownItem
|
||||
<DropdownMenuItem
|
||||
key={'deposit'}
|
||||
data-testid="deposit"
|
||||
onClick={onClickDeposit}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} size={16} />
|
||||
{t('Deposit')}
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key={'withdraw'}
|
||||
data-testid="withdraw"
|
||||
onClick={onClickWithdraw}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.WITHDRAW} size={16} />
|
||||
{t('Withdraw')}
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key={'transfer'}
|
||||
data-testid="transfer"
|
||||
onClick={onClickTransfer}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.TRANSFER} size={16} />
|
||||
{t('Transfer')}
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key={'breakdown'}
|
||||
data-testid="breakdown"
|
||||
onClick={onClickBreakdown}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
|
||||
{t('View usage breakdown')}
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
openAssetDialog(assetId, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View asset details')}
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownCopyItem value={assetId} text={t('Copy asset ID')} />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuCopyItem value={assetId} text={t('Copy asset ID')} />
|
||||
{assetContractAddress && (
|
||||
<TradingDropdownItem>
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
href={etherscanLink(
|
||||
ETHERSCAN_ADDRESS.replace(':hash', assetContractAddress)
|
||||
@@ -84,7 +84,7 @@ export const AccountsActionsDropdown = ({
|
||||
{t('View on Etherscan')}
|
||||
</span>
|
||||
</Link>
|
||||
</TradingDropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ActionsDropdown>
|
||||
);
|
||||
|
||||
@@ -11,13 +11,9 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
Intent,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
TooltipCellComponent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
IGetRowsParams,
|
||||
@@ -190,7 +186,7 @@ export const AccountTable = ({
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span className="inline-block ml-2 w-14 text-muted">
|
||||
<span className="ml-2 inline-block w-14 text-muted">
|
||||
{t('0.00%')}
|
||||
</span>
|
||||
</>
|
||||
@@ -252,26 +248,26 @@ export const AccountTable = ({
|
||||
colId: 'accounts-actions',
|
||||
field: 'asset.id',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showDepositButton ? 105 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 105 : COL_DEFS.actions.maxWidth,
|
||||
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
||||
cellRenderer: ({
|
||||
value: assetId,
|
||||
node,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||
if (!assetId) return null;
|
||||
if (node.rowPinned && node.data?.balance === '0') {
|
||||
if (node.rowPinned && node.data?.total === '0') {
|
||||
return (
|
||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
intent={Intent.Primary}
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
</TradingButton>
|
||||
</Button>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
TradingSelect,
|
||||
Tooltip,
|
||||
TradingCheckbox,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { normalizeTransfer } from '@vegaprotocol/wallet';
|
||||
@@ -276,9 +276,9 @@ export const TransferForm = ({
|
||||
decimals={asset?.decimals}
|
||||
/>
|
||||
)}
|
||||
<TradingButton type="submit" fill={true}>
|
||||
<Button type="submit" variant="primary" fill={true}>
|
||||
{t('Confirm transfer')}
|
||||
</TradingButton>
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -309,8 +309,8 @@ export const TransferFee = ({
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col mb-4 text-xs gap-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<div className="mb-4 flex flex-col gap-2 text-xs">
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
|
||||
@@ -324,7 +324,7 @@ export const TransferFee = ({
|
||||
{formatNumber(fee, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The total amount to be transferred (without the fee)`
|
||||
@@ -337,7 +337,7 @@ export const TransferFee = ({
|
||||
{formatNumber(amount, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The total amount taken from your account. The amount to be transferred plus the fee.`
|
||||
@@ -384,7 +384,7 @@ export const AddressField = ({
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
className="ml-auto text-sm absolute top-0 right-0 underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"name": "@vegaprotocol/announcements",
|
||||
"version": "0.0.3"
|
||||
"version": "0.0.2",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,9 @@
|
||||
"tsConfig": "libs/announcements/tsconfig.lib.json",
|
||||
"project": "libs/announcements/package.json",
|
||||
"entryFile": "libs/announcements/src/index.ts",
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"@vegaprotocol/react-helpers",
|
||||
"@vegaprotocol/ui-toolkit"
|
||||
],
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "swc",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/announcements/README.md",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/apollo-client",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -4,6 +4,16 @@
|
||||
"sourceRoot": "libs/apollo-client/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/apollo-client",
|
||||
"main": "libs/apollo-client/src/index.ts",
|
||||
"tsConfig": "libs/apollo-client/tsconfig.lib.json",
|
||||
"assets": ["libs/apollo-client/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/assets",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,6 +5,26 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/assets",
|
||||
"tsConfig": "libs/assets/tsconfig.lib.json",
|
||||
"project": "libs/assets/package.json",
|
||||
"entryFile": "libs/assets/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/assets/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user