Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3984f5a73b | ||
|
|
2453d7841a | ||
|
|
a0945be721 | ||
|
|
84e40ccb2b | ||
|
|
a868f6d2f5 | ||
|
|
8b83c51014 | ||
|
|
bf7aacf984 | ||
|
|
5183f2591b | ||
|
|
b3308439bd | ||
|
|
719d210219 | ||
|
|
f810a3a0b1 | ||
|
|
db19ee80ac | ||
|
|
45623f33c5 | ||
|
|
08e204fe31 | ||
|
|
81f7b0595e | ||
|
|
4ed0e3a056 | ||
|
|
47a84b4dac | ||
|
|
c540d4b17f | ||
|
|
f1fa3250a4 | ||
|
|
707c00def2 | ||
|
|
78aff54360 | ||
|
|
6160fd7638 | ||
|
|
c345623a06 | ||
|
|
e9173796f5 | ||
|
|
2a0811863f | ||
|
|
4f610bbd1b | ||
|
|
104b2d145d | ||
|
|
f75787562c | ||
|
|
678c9090c7 | ||
|
|
21bb6a69c6 | ||
|
|
467ed5d53d | ||
|
|
d268088e60 | ||
|
|
1208d3f2a8 | ||
|
|
b5ba4f99d9 | ||
|
|
66f25603d3 | ||
|
|
4afd469404 | ||
|
|
9e5bc9c8d1 | ||
|
|
e41aff88b1 | ||
|
|
9209074332 | ||
|
|
39e5836edb | ||
|
|
85a4981700 | ||
|
|
247927e939 | ||
|
|
6523490d96 | ||
|
|
105a758e8d | ||
|
|
559ef48d6d | ||
|
|
255c3752f2 | ||
|
|
2bbf1e2b81 | ||
|
|
06d3ef8d73 | ||
|
|
de4c7926c2 | ||
|
|
50959b4c50 |
@@ -21,7 +21,6 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
|
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
id: cache
|
id: cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
@@ -39,7 +38,6 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
||||||
- name: yarn install
|
- name: yarn install
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: yarn install --pure-lockfile
|
run: yarn install --pure-lockfile
|
||||||
@@ -51,11 +49,11 @@ jobs:
|
|||||||
uses: ./.github/workflows/lint-pr.yml
|
uses: ./.github/workflows/lint-pr.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
lint-test-build:
|
lint-format:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 20
|
||||||
needs: node-modules
|
needs: node-modules
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
name: '(CI) lint + unit test + build'
|
name: '(CI) lint + format check'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -81,6 +79,76 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
main-branch-name: develop
|
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
|
# See affected apps
|
||||||
- name: See affected apps
|
- name: See affected apps
|
||||||
run: |
|
run: |
|
||||||
@@ -97,21 +165,8 @@ jobs:
|
|||||||
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
|
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
|
||||||
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
|
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
|
- name: Build affected
|
||||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
projects: ${{ env.PROJECTS }}
|
projects: ${{ env.PROJECTS }}
|
||||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||||
@@ -121,38 +176,38 @@ jobs:
|
|||||||
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
||||||
|
|
||||||
console-e2e:
|
console-e2e:
|
||||||
needs: lint-test-build
|
needs: build-sources
|
||||||
name: '(CI) console python'
|
name: '(CI) console python'
|
||||||
uses: ./.github/workflows/console-test-run.yml
|
uses: ./.github/workflows/console-test-run.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
|
|
||||||
cypress:
|
cypress:
|
||||||
needs: lint-test-build
|
needs: build-sources
|
||||||
name: '(CI) cypress'
|
name: '(CI) cypress'
|
||||||
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
|
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
|
||||||
uses: ./.github/workflows/cypress-run.yml
|
uses: ./.github/workflows/cypress-run.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
|
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||||
tags: '@smoke'
|
tags: '@smoke'
|
||||||
|
|
||||||
publish-dist:
|
publish-dist:
|
||||||
needs: lint-test-build
|
needs: build-sources
|
||||||
name: '(CD) publish dist'
|
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' }}
|
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
|
uses: ./.github/workflows/publish-dist.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
projects: ${{ needs.lint-test-build.outputs.projects }}
|
projects: ${{ needs.build-sources.outputs.projects }}
|
||||||
|
|
||||||
dist-check:
|
dist-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- publish-dist
|
- publish-dist
|
||||||
- lint-test-build
|
- build-sources
|
||||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
|
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: '(CD) comment preview links'
|
name: '(CD) comment preview links'
|
||||||
@@ -168,27 +223,27 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
|
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
|
||||||
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
|
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
|
||||||
if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
|
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
|
||||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
|
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
|
||||||
echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}"
|
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
|
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
|
||||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
|
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
|
||||||
echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}"
|
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
|
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
|
||||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
|
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
|
||||||
echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}"
|
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
|
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
|
||||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
|
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
|
||||||
echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}"
|
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -200,10 +255,10 @@ jobs:
|
|||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
body: |
|
body: |
|
||||||
Previews
|
Previews
|
||||||
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
|
* governance: ${{ needs.build-sources.outputs.preview_governance }}
|
||||||
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
|
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
|
||||||
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
|
* trading: ${{ needs.build-sources.outputs.preview_trading }}
|
||||||
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
|
* tools: ${{ needs.build-sources.outputs.preview_tools }}
|
||||||
|
|
||||||
# Report single result at the end, to avoid mess with required checks in PR
|
# Report single result at the end, to avoid mess with required checks in PR
|
||||||
cypress-check:
|
cypress-check:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
run-tests:
|
run-tests:
|
||||||
name: run-tests
|
name: run-tests
|
||||||
runs-on: 8-cores
|
runs-on: console-test
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
@@ -33,15 +33,6 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cache-node-modules-
|
${{ 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
|
# install deps if cache missing
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: yarn install
|
- name: yarn install
|
||||||
@@ -76,32 +67,6 @@ jobs:
|
|||||||
repository: vegaprotocol/console-test
|
repository: vegaprotocol/console-test
|
||||||
path: './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
|
# install dependencies if cache does not exist
|
||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -125,7 +90,7 @@ jobs:
|
|||||||
#----------------------------------------------
|
#----------------------------------------------
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: ./console-test
|
working-directory: ./console-test
|
||||||
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
|
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
|
||||||
- name: Check files
|
- name: Check files
|
||||||
run: |
|
run: |
|
||||||
ls -al .
|
ls -al .
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ jobs:
|
|||||||
|
|
||||||
# Restore node_modules from cache if possible
|
# Restore node_modules from cache if possible
|
||||||
- name: Restore node_modules from cache
|
- name: Restore node_modules from cache
|
||||||
|
id: cache-node-modules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -46,6 +47,7 @@ jobs:
|
|||||||
|
|
||||||
# Install frontend dependencies
|
# Install frontend dependencies
|
||||||
- name: Install root dependencies
|
- name: Install root dependencies
|
||||||
|
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
working-directory: frontend-monorepo
|
working-directory: frontend-monorepo
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ jobs:
|
|||||||
######
|
######
|
||||||
|
|
||||||
- name: Run Cypress tests
|
- 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
|
working-directory: frontend-monorepo
|
||||||
env:
|
env:
|
||||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||||
|
|||||||
@@ -3,6 +3,3 @@
|
|||||||
|
|
||||||
# Lint commit messages to ensure they follow conventional commit standards
|
# Lint commit messages to ensure they follow conventional commit standards
|
||||||
yarn commitlint --edit "${1}"
|
yarn commitlint --edit "${1}"
|
||||||
|
|
||||||
# Lint all staged files
|
|
||||||
yarn lint-staged
|
|
||||||
|
|||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
# Lint all staged files
|
||||||
|
yarn lint-staged
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/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 @vegaprotocol/frontend-qa
|
||||||
*.graphql @vegaprotocol/core
|
*.graphql @vegaprotocol/core
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ recalculate-ipfs:
|
|||||||
echo "ipfs hash inside the image"
|
echo "ipfs hash inside the image"
|
||||||
docker run --rm ${TAG} cat /ipfs-hash
|
docker run --rm ${TAG} cat /ipfs-hash
|
||||||
echo "recalculating ipfs hash"
|
echo "recalculating ipfs hash"
|
||||||
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
|
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html
|
||||||
|
|
||||||
.PHONY: eject-ipfs-hash
|
.PHONY: eject-ipfs-hash
|
||||||
unpack:
|
unpack:
|
||||||
|
|||||||
@@ -115,11 +115,31 @@ docker run -p 3000:80 [TAG]
|
|||||||
|
|
||||||
## Build instructions
|
## 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
|
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.
|
||||||
|
|
||||||
### nx build outside the docker
|
### nx build outside the docker
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
@@ -133,38 +153,18 @@ You can build any of the containers locally with the following command:
|
|||||||
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
|
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
|
### 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:
|
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 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)
|
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)
|
||||||
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
|
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`
|
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`.
|
4. Download docker image with the desired release `docker pull $TAG`.
|
||||||
5. Recalculate hash: `make recalculate-ipfs`
|
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`
|
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`
|
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. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
|
8. Now `dist` directory contains valid application build
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
context('Network parameters page', { tags: '@smoke' }, function () {
|
context('Network parameters page', { tags: '@smoke' }, function () {
|
||||||
before('navigate to network parameter page', function () {
|
before('navigate to network parameter page', function () {
|
||||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||||
|
cy.visit('/network-parameters');
|
||||||
});
|
});
|
||||||
describe('Verify elements on page', function () {
|
describe('Verify elements on page', function () {
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('/network-parameters');
|
|
||||||
});
|
|
||||||
|
|
||||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||||
const tableRows = '[data-testid="key-value-table-row"]';
|
const tableRows = '[data-testid="key-value-table-row"]';
|
||||||
|
|
||||||
@@ -16,6 +13,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
|||||||
.and('be.visible');
|
.and('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 0006-NETW-021
|
||||||
it('should list each of the network parameters available', function () {
|
it('should list each of the network parameters available', function () {
|
||||||
cy.get_network_parameters().then((network_parameters) => {
|
cy.get_network_parameters().then((network_parameters) => {
|
||||||
const numberOfNetworkParametersInSystem =
|
const numberOfNetworkParametersInSystem =
|
||||||
@@ -198,6 +196,19 @@ 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 () {
|
it('should be able to see network parameters - on mobile', function () {
|
||||||
cy.switchToMobile();
|
cy.switchToMobile();
|
||||||
cy.get_network_parameters().then((network_parameters) => {
|
cy.get_network_parameters().then((network_parameters) => {
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
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 +1,2 @@
|
|||||||
export * from './network-parameters';
|
export * from './network-parameters';
|
||||||
|
export * from './structure-network-params';
|
||||||
|
|||||||
@@ -1,82 +1,105 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||||
import { NetworkParametersTable } from './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', () => {
|
describe('NetworkParametersTable', () => {
|
||||||
it('renders correctly when it has network params', () => {
|
it('renders headers correctly', () => {
|
||||||
const data: NetworkParamsQuery = {
|
renderComponent(mockData);
|
||||||
networkParametersConnection: {
|
|
||||||
edges: [
|
const allHeadings = screen.getAllByRole('heading');
|
||||||
{
|
expect(
|
||||||
node: {
|
allHeadings.map((h) => {
|
||||||
__typename: 'NetworkParameter',
|
return {
|
||||||
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
|
text: h.textContent,
|
||||||
value: '1',
|
level: h.tagName,
|
||||||
},
|
testId: h.getAttribute('data-testid'),
|
||||||
},
|
};
|
||||||
{
|
})
|
||||||
node: {
|
).toEqual([
|
||||||
__typename: 'NetworkParameter',
|
{
|
||||||
key: 'market.fee.factors.infrastructureFee',
|
text: 'Network Parameters',
|
||||||
value: '0.0005',
|
level: 'H1',
|
||||||
},
|
testId: 'network-param-header',
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
{ text: 'Spam', level: 'H1', testId: 'spam' },
|
||||||
render(<NetworkParametersTable data={data} loading={false} />);
|
{ text: 'Protection', level: 'H2', testId: 'spam-protection' },
|
||||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
{ text: 'Delegation', level: 'H3', testId: 'spam-protection-delegation' },
|
||||||
'Network Parameters'
|
{ text: 'Min', level: 'H4', testId: 'spam-protection-delegation-min' },
|
||||||
);
|
{ text: 'Voting', level: 'H3', testId: 'spam-protection-voting' },
|
||||||
const rows = screen.getAllByTestId('key-value-table-row');
|
{ text: 'Min', level: 'H4', testId: 'spam-protection-voting-min' },
|
||||||
expect(rows[0].children[0]).toHaveTextContent(
|
{ text: 'Reward', level: 'H1', testId: 'reward' },
|
||||||
'market.fee.factors.infrastructureFee'
|
{ text: 'Staking', level: 'H2', testId: 'reward-staking' },
|
||||||
);
|
{ text: 'Delegation', level: 'H3', testId: 'reward-staking-delegation' },
|
||||||
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 the rows in ascending order', () => {
|
it('renders network params correctly', () => {
|
||||||
const data: NetworkParamsQuery = {
|
renderComponent(mockData);
|
||||||
networkParametersConnection: {
|
|
||||||
edges: [
|
const delegationMinTokensRow = screen.getByTestId(
|
||||||
{
|
'spam-protection-delegation-min-tokens'
|
||||||
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'
|
|
||||||
);
|
);
|
||||||
const rows = screen.getAllByTestId('key-value-table-row');
|
expect(delegationMinTokensRow).toHaveTextContent('0.000000000000000003');
|
||||||
expect(rows[0].children[0]).toHaveTextContent(
|
|
||||||
'market.fee.factors.infrastructureFee'
|
const votingMinTokensRow = screen.getByTestId(
|
||||||
|
'spam-protection-voting-min-tokens'
|
||||||
);
|
);
|
||||||
expect(rows[1].children[0]).toHaveTextContent(
|
expect(votingMinTokensRow).toHaveTextContent('0.000000000000000001');
|
||||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
|
||||||
|
const minimumValidatorStakeRow = screen.getByTestId(
|
||||||
|
'reward-staking-delegation-minimumValidatorStake'
|
||||||
|
);
|
||||||
|
expect(minimumValidatorStakeRow).toHaveTextContent('2');
|
||||||
|
|
||||||
|
const assetRow = screen.getByTestId('reward-asset');
|
||||||
|
expect(assetRow).toHaveTextContent(
|
||||||
|
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55'
|
||||||
);
|
);
|
||||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
|
||||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not render rows when is loading', () => {
|
it('does not render rows when is loading', () => {
|
||||||
render(<NetworkParametersTable data={undefined} loading={true} />);
|
renderComponent(undefined);
|
||||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||||
'Network Parameters'
|
'Network Parameters'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import startCase from 'lodash/startCase';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
AsyncRenderer,
|
AsyncRenderer,
|
||||||
KeyValueTable,
|
|
||||||
KeyValueTableRow,
|
KeyValueTableRow,
|
||||||
SyntaxHighlighter,
|
SyntaxHighlighter,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@@ -12,11 +14,12 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { RouteTitle } from '../../components/route-title';
|
import { RouteTitle } from '../../components/route-title';
|
||||||
import orderBy from 'lodash/orderBy';
|
|
||||||
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
|
||||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
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 = [
|
const PERCENTAGE_PARAMS = [
|
||||||
'governance.proposal.asset.requiredMajority',
|
'governance.proposal.asset.requiredMajority',
|
||||||
@@ -58,6 +61,54 @@ const BIG_NUMBER_PARAMS = [
|
|||||||
'governance.proposal.updateAsset.minVoterBalance',
|
'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 = ({
|
export const NetworkParameterRow = ({
|
||||||
row: { key, value },
|
row: { key, value },
|
||||||
}: {
|
}: {
|
||||||
@@ -77,7 +128,9 @@ export const NetworkParameterRow = ({
|
|||||||
>
|
>
|
||||||
{key}
|
{key}
|
||||||
{isSyntaxRow ? (
|
{isSyntaxRow ? (
|
||||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
<div className="pb-2">
|
||||||
|
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||||
|
</div>
|
||||||
) : isNaN(Number(value)) ? (
|
) : isNaN(Number(value)) ? (
|
||||||
value
|
value
|
||||||
) : BIG_NUMBER_PARAMS.includes(key) ? (
|
) : BIG_NUMBER_PARAMS.includes(key) ? (
|
||||||
@@ -113,17 +166,12 @@ export const NetworkParametersTable = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
render={(data) => {
|
render={(data) => {
|
||||||
const ascParams = orderBy(
|
const flatParams = removePaginationWrapper(
|
||||||
removePaginationWrapper(data.networkParametersConnection.edges),
|
data.networkParametersConnection.edges
|
||||||
(param) => param.key,
|
|
||||||
'asc'
|
|
||||||
);
|
);
|
||||||
|
const groupedParams = structureNetworkParams(flatParams);
|
||||||
return (
|
return (
|
||||||
<KeyValueTable data-testid="parameters">
|
<div className="-mt-6">{renderGroupedParams(groupedParams, 1)}</div>
|
||||||
{(ascParams || []).map((row) => (
|
|
||||||
<NetworkParameterRow key={row.key} row={row} />
|
|
||||||
))}
|
|
||||||
</KeyValueTable>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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({});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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,7 +38,12 @@ const Oracles = () => {
|
|||||||
const dataConnection = o?.node.dataConnection;
|
const dataConnection = o?.node.dataConnection;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={id} key={id} className="mb-10">
|
<div
|
||||||
|
id={id}
|
||||||
|
key={id}
|
||||||
|
className="mb-10"
|
||||||
|
data-testid="oracle-details"
|
||||||
|
>
|
||||||
<OracleDetails
|
<OracleDetails
|
||||||
id={id}
|
id={id}
|
||||||
dataSource={o?.node}
|
dataSource={o?.node}
|
||||||
|
|||||||
@@ -34,19 +34,20 @@ import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
|||||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||||
|
|
||||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||||
const proposalVoteProgressForPercentage =
|
const participationNotMet = 'token-participation-not-met';
|
||||||
'vote-progress-indicator-percentage-for';
|
const voteStatus = 'vote-status';
|
||||||
const proposalVoteProgressAgainstPercentage =
|
const voteMajorityNotMet = 'token-majority-not-met';
|
||||||
'vote-progress-indicator-percentage-against';
|
const numberOfVotesFor = 'num-votes-for';
|
||||||
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
|
const votesForPercentage = 'votes-for-percentage';
|
||||||
const proposalVoteProgressAgainstTokens =
|
const numberOfVotesAgainst = 'num-votes-against';
|
||||||
'vote-progress-indicator-tokens-against';
|
const votesAgainstPercentage = 'votes-against-percentage';
|
||||||
|
const totalVotedNumber = 'total-voted';
|
||||||
|
const totalVotedPercentage = 'total-voted-percentage';
|
||||||
const changeVoteButton = 'change-vote-button';
|
const changeVoteButton = 'change-vote-button';
|
||||||
const proposalDetailsTitle = 'proposal-title';
|
const proposalDetailsTitle = 'proposal-title';
|
||||||
const proposalDetailsDescription = 'proposal-description';
|
const proposalDetailsDescription = 'proposal-description';
|
||||||
const openProposals = 'open-proposals';
|
const openProposals = 'open-proposals';
|
||||||
const viewProposalButton = 'view-proposal-btn';
|
const viewProposalButton = 'view-proposal-btn';
|
||||||
const voteBreakdownToggle = 'vote-breakdown-toggle';
|
|
||||||
const proposalTermsToggle = 'proposal-json-toggle';
|
const proposalTermsToggle = 'proposal-json-toggle';
|
||||||
const marketDataToggle = 'proposal-market-data-toggle';
|
const marketDataToggle = 'proposal-market-data-toggle';
|
||||||
|
|
||||||
@@ -150,29 +151,32 @@ describe(
|
|||||||
// 3001-VOTE-037
|
// 3001-VOTE-037
|
||||||
// 3001-VOTE-040
|
// 3001-VOTE-040
|
||||||
// 3001-VOTE-067
|
// 3001-VOTE-067
|
||||||
|
// 3001-VOTE-023
|
||||||
createRawProposal();
|
createRawProposal();
|
||||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||||
cy.getByTestId(viewProposalButton).click()
|
cy.getByTestId(viewProposalButton).click()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
|
cy.getByTestId(participationNotMet).should(
|
||||||
'be.visible'
|
'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.getByTestId(voteBreakdownToggle).click();
|
|
||||||
getProposalInformationFromTable('Expected to pass')
|
|
||||||
.contains('👎')
|
|
||||||
.should('be.visible');
|
|
||||||
// 3001-VOTE-062
|
// 3001-VOTE-062
|
||||||
// 3001-VOTE-040
|
// 3001-VOTE-040
|
||||||
// 3001-VOTE-070
|
// 3001-VOTE-070
|
||||||
getProposalInformationFromTable('Token majority met')
|
|
||||||
.contains('👎')
|
|
||||||
.should('be.visible');
|
|
||||||
// 3001-VOTE-068
|
// 3001-VOTE-068
|
||||||
getProposalInformationFromTable('Token participation met')
|
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||||
.contains('👎')
|
cy.getByTestId(numberOfVotesAgainst).should('have.text', '0.0M');
|
||||||
.should('be.visible');
|
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
|
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
|
||||||
@@ -197,43 +201,16 @@ describe(
|
|||||||
.contains(votedDate)
|
.contains(votedDate)
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
});
|
});
|
||||||
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
|
cy.getByTestId(votesForPercentage) // 3001-VOTE-072
|
||||||
.contains('100.00%')
|
.should('have.text', '100%');
|
||||||
.and('be.visible');
|
cy.getByTestId(votesAgainstPercentage).should('have.text', '0%');
|
||||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
cy.getByTestId('token-majority-progress')
|
||||||
.contains('0.00%')
|
.should('have.attr', 'style')
|
||||||
.and('be.visible');
|
.and('eq', 'width: 100%;'); // 3001-VOTE-024
|
||||||
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();
|
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||||
voteForProposal('for');
|
voteForProposal('for');
|
||||||
// 3001-VOTE-064
|
// 3001-VOTE-064
|
||||||
cy.getByTestId('user-voted-yes').should('exist');
|
cy.getByTestId('user-voted-yes').should('exist');
|
||||||
getProposalInformationFromTable('Tokens for proposal')
|
|
||||||
.should('have.text', (1).toFixed(2))
|
|
||||||
.and('be.visible');
|
|
||||||
navigateTo(navigation.proposals);
|
navigateTo(navigation.proposals);
|
||||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||||
@@ -244,16 +221,7 @@ describe(
|
|||||||
});
|
});
|
||||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||||
voteForProposal('against');
|
voteForProposal('against');
|
||||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
cy.getByTestId(votesAgainstPercentage).should('have.text', '100%');
|
||||||
.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);
|
navigateTo(navigation.proposals);
|
||||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||||
@@ -265,6 +233,7 @@ describe(
|
|||||||
|
|
||||||
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
|
// 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 () {
|
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
|
||||||
|
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||||
createRawProposal();
|
createRawProposal();
|
||||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||||
@@ -275,69 +244,28 @@ describe(
|
|||||||
voteForProposal('for');
|
voteForProposal('for');
|
||||||
// 3001-VOTE-079
|
// 3001-VOTE-079
|
||||||
cy.contains('You voted: For').should('be.visible');
|
cy.contains('You voted: For').should('be.visible');
|
||||||
cy.getByTestId(proposalVoteProgressForTokens)
|
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||||
.contains('1')
|
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||||
.and('be.visible');
|
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||||
cy.getByTestId(voteBreakdownToggle).click();
|
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||||
getProposalInformationFromTable('Total Supply')
|
ethereumWalletConnect();
|
||||||
.invoke('text')
|
stakingPageAssociateTokens('1000000', { approve: true });
|
||||||
.then((totalSupply) => {
|
navigateTo(navigation.proposals);
|
||||||
const tokensRequiredToAchieveResult = (
|
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||||
(Number(totalSupply.replace(/,/g, '')) * 0.001) /
|
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||||
100
|
cy.getByTestId(viewProposalButton).click()
|
||||||
).toFixed(2);
|
);
|
||||||
ethereumWalletConnect();
|
});
|
||||||
ensureSpecifiedUnstakedTokensAreAssociated(
|
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||||
tokensRequiredToAchieveResult
|
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||||
);
|
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||||
navigateTo(navigation.proposals);
|
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
// 3001-VOTE-065
|
||||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||||
cy.getByTestId(viewProposalButton).click()
|
voteForProposal('for');
|
||||||
);
|
cy.getByTestId(numberOfVotesFor).should('have.text', '1.0M');
|
||||||
});
|
cy.getByTestId(totalVotedNumber).should('have.text', '1.0M');
|
||||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
cy.getByTestId(totalVotedPercentage).should('have.text', '(1.54%)');
|
||||||
.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 () {
|
it('Able to vote for proposal twice by switching public key', function () {
|
||||||
@@ -360,10 +288,6 @@ describe(
|
|||||||
voteForProposal('against');
|
voteForProposal('against');
|
||||||
cy.contains('You voted: Against').should('be.visible');
|
cy.contains('You voted: Against').should('be.visible');
|
||||||
switchVegaWalletPubKey();
|
switchVegaWalletPubKey();
|
||||||
cy.getByTestId(proposalVoteProgressForTokens).should(
|
|
||||||
'contain.text',
|
|
||||||
'1.00'
|
|
||||||
);
|
|
||||||
// Checking vote status for different public keys is displayed correctly
|
// Checking vote status for different public keys is displayed correctly
|
||||||
cy.contains('You voted: For').should('be.visible');
|
cy.contains('You voted: For').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,12 +22,10 @@ const proposalListItem = '[data-testid="proposals-list-item"]';
|
|||||||
const closedProposals = 'closed-proposals';
|
const closedProposals = 'closed-proposals';
|
||||||
const proposalStatus = 'proposal-status';
|
const proposalStatus = 'proposal-status';
|
||||||
const viewProposalButton = 'view-proposal-btn';
|
const viewProposalButton = 'view-proposal-btn';
|
||||||
const votesTable = 'votes-table';
|
const votesTable = 'user-vote';
|
||||||
const openProposals = 'open-proposals';
|
const openProposals = 'open-proposals';
|
||||||
const proposalVoteProgressForPercentage =
|
const majorityVoteReached = 'token-majority-met';
|
||||||
'vote-progress-indicator-percentage-for';
|
const minParticipationReached = 'token-participation-met';
|
||||||
const majorityVoteReached = 'majority-reached';
|
|
||||||
const minParticipationReached = 'participation-reached';
|
|
||||||
const proposalTimeout = { timeout: 8000 };
|
const proposalTimeout = { timeout: 8000 };
|
||||||
|
|
||||||
context(
|
context(
|
||||||
@@ -70,7 +68,6 @@ context(
|
|||||||
cy.getByTestId('proposal-type').should('have.text', 'New market');
|
cy.getByTestId('proposal-type').should('have.text', 'New market');
|
||||||
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
|
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
|
||||||
cy.getByTestId(votesTable).within(() => {
|
cy.getByTestId(votesTable).within(() => {
|
||||||
cy.contains('Vote passed.').should('be.visible');
|
|
||||||
cy.contains('Voting has ended.').should('be.visible');
|
cy.contains('Voting has ended.').should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -92,7 +89,7 @@ context(
|
|||||||
// 3001-VOTE-019 time to vote is highlighted red
|
// 3001-VOTE-019 time to vote is highlighted red
|
||||||
cy.getByTestId('vote-details')
|
cy.getByTestId('vote-details')
|
||||||
.find('span')
|
.find('span')
|
||||||
.should('have.class', 'text-vega-pink');
|
.should('have.class', 'text-vega-orange');
|
||||||
cy.getByTestId(viewProposalButton).click();
|
cy.getByTestId(viewProposalButton).click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -109,12 +106,9 @@ context(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
cy.getByTestId(votesTable).within(() => {
|
cy.getByTestId(votesTable).within(() => {
|
||||||
cy.contains('Vote passed.').should('be.visible');
|
|
||||||
cy.contains('Voting has ended.').should('be.visible');
|
cy.contains('Voting has ended.').should('be.visible');
|
||||||
});
|
});
|
||||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
cy.getByTestId('votes-for-percentage').should('have.text', '100%');
|
||||||
.contains('100.00%')
|
|
||||||
.and('be.visible');
|
|
||||||
navigateTo(navigation.proposals);
|
navigateTo(navigation.proposals);
|
||||||
cy.contains(proposalTitle)
|
cy.contains(proposalTitle)
|
||||||
.parentsUntil(proposalListItem)
|
.parentsUntil(proposalListItem)
|
||||||
|
|||||||
@@ -298,9 +298,6 @@ context(
|
|||||||
getProposalFromTitle(proposalTitle).within(() =>
|
getProposalFromTitle(proposalTitle).within(() =>
|
||||||
cy.getByTestId(viewProposalButton).click()
|
cy.getByTestId(viewProposalButton).click()
|
||||||
);
|
);
|
||||||
cy.contains('Vote breakdown').should('be.visible', {
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
cy.getByTestId(voteButtons).should('not.exist');
|
cy.getByTestId(voteButtons).should('not.exist');
|
||||||
cy.getByTestId('min-proposal-requirements').should(
|
cy.getByTestId('min-proposal-requirements').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ const enactmentDeadlineError = 'enactment-before-voting-deadline';
|
|||||||
const proposalDownloadBtn = 'proposal-download-json';
|
const proposalDownloadBtn = 'proposal-download-json';
|
||||||
const feedbackError = '[data-testid="Error"]';
|
const feedbackError = '[data-testid="Error"]';
|
||||||
const viewProposalBtn = 'view-proposal-btn';
|
const viewProposalBtn = 'view-proposal-btn';
|
||||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
|
||||||
const tokenVoteStatus = 'token-votes-status';
|
|
||||||
const proposalJsonToggle = 'proposal-json-toggle';
|
const proposalJsonToggle = 'proposal-json-toggle';
|
||||||
const proposalJsonSection = 'proposal-json';
|
const proposalJsonSection = 'proposal-json';
|
||||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||||
@@ -421,27 +419,28 @@ context(
|
|||||||
cy.getByTestId(viewProposalBtn).click();
|
cy.getByTestId(viewProposalBtn).click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cy.getByTestId(liquidityVoteStatus).should(
|
cy.getByTestId('lp-majority-not-met').should(
|
||||||
'contain.text',
|
'have.text',
|
||||||
'Currently expected to fail'
|
'66% majority threshold not met'
|
||||||
);
|
);
|
||||||
cy.getByTestId(tokenVoteStatus).should(
|
|
||||||
'contain.text',
|
cy.getByTestId('token-majority-not-met').should(
|
||||||
'Currently expected to fail'
|
'have.text',
|
||||||
|
'66% majority threshold not met'
|
||||||
);
|
);
|
||||||
voteForProposal('for');
|
voteForProposal('for');
|
||||||
cy.getByTestId(liquidityVoteStatus).should(
|
cy.getByTestId('lp-majority-met').should(
|
||||||
'contain.text',
|
'have.text',
|
||||||
'Currently expected to pass'
|
'66% majority threshold met'
|
||||||
);
|
);
|
||||||
cy.getByTestId(tokenVoteStatus).should(
|
cy.getByTestId('token-majority-met').should(
|
||||||
'contain.text',
|
'have.text',
|
||||||
'Currently expected to pass'
|
'66% majority threshold met'
|
||||||
|
);
|
||||||
|
cy.getByTestId('vote-status').should(
|
||||||
|
'have.text',
|
||||||
|
'Currently expected to pass by token vote'
|
||||||
);
|
);
|
||||||
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
|
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
enterRawProposalBody,
|
enterRawProposalBody,
|
||||||
generateFreeFormProposalTitle,
|
generateFreeFormProposalTitle,
|
||||||
getProposalFromTitle,
|
getProposalFromTitle,
|
||||||
getProposalInformationFromTable,
|
|
||||||
goToMakeNewProposal,
|
goToMakeNewProposal,
|
||||||
governanceProposalType,
|
governanceProposalType,
|
||||||
submitUniqueRawProposal,
|
submitUniqueRawProposal,
|
||||||
@@ -30,7 +29,9 @@ const proposalType = 'proposal-type';
|
|||||||
const proposalStatus = 'proposal-status';
|
const proposalStatus = 'proposal-status';
|
||||||
const proposalClosingDate = 'vote-details';
|
const proposalClosingDate = 'vote-details';
|
||||||
const viewProposalButton = 'view-proposal-btn';
|
const viewProposalButton = 'view-proposal-btn';
|
||||||
const voteBreakDownToggle = 'vote-breakdown-toggle';
|
const voteMajorityNotMet = 'token-majority-not-met';
|
||||||
|
const voteMajorityMet = 'token-majority-met';
|
||||||
|
const votesForPercentage = 'votes-for-percentage';
|
||||||
|
|
||||||
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||||
before('connect wallets and set approval limit', function () {
|
before('connect wallets and set approval limit', function () {
|
||||||
@@ -121,10 +122,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
|||||||
submitUniqueRawProposal({ proposalTitle: proposalTitle });
|
submitUniqueRawProposal({ proposalTitle: proposalTitle });
|
||||||
getProposalFromTitle(proposalTitle).within(() => {
|
getProposalFromTitle(proposalTitle).within(() => {
|
||||||
// 3001-VOTE-039
|
// 3001-VOTE-039
|
||||||
cy.getByTestId('participation-not-reached').should(
|
cy.getByTestId(voteMajorityNotMet).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Min. participation not reached'
|
'66% majority threshold not met'
|
||||||
);
|
);
|
||||||
|
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();
|
cy.getByTestId(viewProposalButton).click();
|
||||||
});
|
});
|
||||||
voteForProposal('for');
|
voteForProposal('for');
|
||||||
@@ -134,16 +140,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
|||||||
'have.text',
|
'have.text',
|
||||||
'Currently expected to pass'
|
'Currently expected to pass'
|
||||||
);
|
);
|
||||||
cy.getByTestId('user-voted-yes').should('exist');
|
cy.getByTestId(voteMajorityMet).should(
|
||||||
cy.getByTestId('participation-reached').should(
|
|
||||||
'have.text',
|
'have.text',
|
||||||
'Min. participation reached'
|
'66% majority threshold met'
|
||||||
|
);
|
||||||
|
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||||
|
cy.getByTestId('token-participation-met').should(
|
||||||
|
'have.text',
|
||||||
|
'0.000000000000000000000015% participation threshold met'
|
||||||
);
|
);
|
||||||
cy.getByTestId(viewProposalButton).click();
|
|
||||||
});
|
});
|
||||||
cy.getByTestId(voteBreakDownToggle).click();
|
|
||||||
getProposalInformationFromTable('Token participation met')
|
|
||||||
.contains('👍')
|
|
||||||
.should('be.visible');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,11 +37,39 @@ context(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
|
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
|
||||||
it('should have button for link to more information on proposals', function () {
|
it('new proposal page 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').click();
|
||||||
cy.getByTestId('new-proposal-link')
|
cy.url().should('include', '/proposals/propose/raw');
|
||||||
.find('a')
|
cy.contains('To see Explorer data on proposals visit').within(() => {
|
||||||
.should('have.attr', 'href', proposalsUrl);
|
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 be able to see a working link for - find out more about Vega governance', function () {
|
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||||
@@ -183,7 +211,7 @@ context(
|
|||||||
.first()
|
.first()
|
||||||
.find('[data-testid="view-proposal-btn"]')
|
.find('[data-testid="view-proposal-btn"]')
|
||||||
.click();
|
.click();
|
||||||
cy.url().should('contain', '/protocol-upgrades/v1');
|
cy.url().should('contain', '/protocol-upgrades/v1/2015942');
|
||||||
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
||||||
cy.get('h1').should('have.text', 'Vega Release v1');
|
cy.get('h1').should('have.text', 'Vega Release v1');
|
||||||
cy.getByTestId('protocol-upgrade-block-height').should(
|
cy.getByTestId('protocol-upgrade-block-height').should(
|
||||||
@@ -243,7 +271,7 @@ context(
|
|||||||
);
|
);
|
||||||
cy.getByTestId('external-link')
|
cy.getByTestId('external-link')
|
||||||
.should('have.attr', 'href')
|
.should('have.attr', 'href')
|
||||||
.and('contain', '/proposals/protocol-upgrade/v1');
|
.and('contain', '/proposals/protocol-upgrade/v1/2015942');
|
||||||
});
|
});
|
||||||
|
|
||||||
// estimate does not display possibly due to mocks or Cypress unless the proposal is clicked on several times
|
// 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) {
|
export function voteForProposal(vote: string) {
|
||||||
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
|
cy.get(voteButtons).should('be.visible', { timeout: 10000 });
|
||||||
cy.get(voteButtons).contains(vote).click();
|
cy.get(voteButtons).contains(vote).click();
|
||||||
cy.get(dialogTitle, proposalTimeout).should(
|
cy.get(dialogTitle, proposalTimeout).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
|
|||||||
@@ -219,7 +219,9 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
|
|||||||
.eq(1)
|
.eq(1)
|
||||||
.invoke('text')
|
.invoke('text')
|
||||||
.then((unstakedBalance) => {
|
.then((unstakedBalance) => {
|
||||||
if (parseFloat(unstakedBalance) != parseFloat(tokenAmount)) {
|
const tokenFloat = parseFloat(tokenAmount);
|
||||||
|
const unstakedFloat = parseFloat(unstakedBalance.replace(/,/g, ''));
|
||||||
|
if (tokenFloat != unstakedFloat) {
|
||||||
vegaWalletTeardown();
|
vegaWalletTeardown();
|
||||||
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
|
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
|
||||||
'0.00',
|
'0.00',
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
|||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
#Test configuration variables
|
#Test configuration variables
|
||||||
CYPRESS_FAIRGROUND=false
|
CYPRESS_FAIRGROUND=false
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
|||||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_TENDERMINT_URL=http://localhost:26617
|
NX_TENDERMINT_URL=http://localhost:26617
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
|
|||||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
|||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||||
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://be.vega.community
|
NX_TENDERMINT_URL=https://be.vega.community
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
|
|||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||||
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
|||||||
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
|
|||||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
|
|||||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ const Web3Container = ({
|
|||||||
<TemplateSidebar sidebar={sideBar}>
|
<TemplateSidebar sidebar={sideBar}>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</TemplateSidebar>
|
</TemplateSidebar>
|
||||||
<footer className="p-4 border-t border-neutral-700">
|
<footer className="p-4 border-t border-neutral-700 break-all">
|
||||||
<NetworkInfo />
|
<NetworkInfo />
|
||||||
</footer>
|
</footer>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
@@ -308,7 +308,7 @@ const AppContainer = () => {
|
|||||||
<Router>
|
<Router>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<div className="min-h-full text-white grid">
|
<div className="min-h-full text-white">
|
||||||
<NodeGuard
|
<NodeGuard
|
||||||
skeleton={<div>{t('Loading')}</div>}
|
skeleton={<div>{t('Loading')}</div>}
|
||||||
failure={
|
failure={
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||||
useAnimateValue(ref, value);
|
useAnimateValue(ref, value);
|
||||||
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
|
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -75,7 +75,10 @@ export const WalletCardRow = ({
|
|||||||
className="font-mono flex-1 text-right"
|
className="font-mono flex-1 text-right"
|
||||||
data-testid="associated-amount"
|
data-testid="associated-amount"
|
||||||
>
|
>
|
||||||
<span>{integers}.</span>
|
<span>
|
||||||
|
{integers}
|
||||||
|
{separator}
|
||||||
|
</span>
|
||||||
<span>{decimalsPlaces}</span>
|
<span>{decimalsPlaces}</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -110,7 +113,10 @@ export const WalletCardAsset = ({
|
|||||||
border,
|
border,
|
||||||
subheading,
|
subheading,
|
||||||
}: WalletCardAssetProps) => {
|
}: WalletCardAssetProps) => {
|
||||||
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
|
const [integers, decimalsPlaces, separator] = useNumberParts(
|
||||||
|
balance,
|
||||||
|
decimals
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-nowrap mt-2 mb-4">
|
<div className="flex flex-nowrap mt-2 mb-4">
|
||||||
@@ -132,7 +138,10 @@ export const WalletCardAsset = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
||||||
<span>{integers}.</span>
|
<span>
|
||||||
|
{integers}
|
||||||
|
{separator}
|
||||||
|
</span>
|
||||||
<span className="text-neutral-400">{decimalsPlaces}</span>
|
<span className="text-neutral-400">{decimalsPlaces}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -201,6 +201,8 @@
|
|||||||
"STATE_WAITING_FOR_NODE_VOTE": "Waiting for node vote",
|
"STATE_WAITING_FOR_NODE_VOTE": "Waiting for node vote",
|
||||||
"UpdateNetworkParameter": "Network parameter",
|
"UpdateNetworkParameter": "Network parameter",
|
||||||
"NewFreeform": "Freeform",
|
"NewFreeform": "Freeform",
|
||||||
|
"setToPass": "Set to pass",
|
||||||
|
"setToFail": "Set to fail",
|
||||||
"tokenVotes": "Token votes",
|
"tokenVotes": "Token votes",
|
||||||
"liquidityVotes": "Liquidity votes",
|
"liquidityVotes": "Liquidity votes",
|
||||||
"castYourVote": "Cast your vote",
|
"castYourVote": "Cast your vote",
|
||||||
@@ -209,13 +211,23 @@
|
|||||||
"against": "Against",
|
"against": "Against",
|
||||||
"majorityRequired": "Majority Required",
|
"majorityRequired": "Majority Required",
|
||||||
"participation": "Participation",
|
"participation": "Participation",
|
||||||
"met": "Met",
|
"majorityThreshold": "majority threshold",
|
||||||
"notMet": "Not Met",
|
"participationThreshold": "participation threshold",
|
||||||
|
"met": "met",
|
||||||
|
"notMet": "not met",
|
||||||
"governanceRequired": "Required",
|
"governanceRequired": "Required",
|
||||||
"daysLeft": "{{daysLeft}} left to vote.",
|
"daysLeft": "{{daysLeft}} left to vote.",
|
||||||
"toVote": "to vote",
|
"toVote": "to vote",
|
||||||
"voteFor": "Vote for",
|
"voteFor": "Vote for",
|
||||||
"voteAgainst": "Vote against",
|
"voteAgainst": "Vote against",
|
||||||
|
"tokenVote": "Token vote",
|
||||||
|
"tokenVotesFor": "Token votes for",
|
||||||
|
"tokenVotesAgainst": "Token votes against",
|
||||||
|
"totalTokensVoted": "Total tokens voted",
|
||||||
|
"liquidityProviderVote": "Liquidity provider vote",
|
||||||
|
"liquidityProviderVotesFor": "LP votes for",
|
||||||
|
"liquidityProviderVotesAgainst": "LP votes against",
|
||||||
|
"totalLiquidityProviderTokensVoted": "Total LP tokens voted",
|
||||||
"votingThresholdInfo": "If the token vote passes the participation threshold it will be the deciding vote. If not, the outcome will be determined by liquidity providers on this market.",
|
"votingThresholdInfo": "If the token vote passes the participation threshold it will be the deciding vote. If not, the outcome will be determined by liquidity providers on this market.",
|
||||||
"noGovernanceTokens": "You need some VEGA tokens to participate in governance",
|
"noGovernanceTokens": "You need some VEGA tokens to participate in governance",
|
||||||
"youVoted": "You voted",
|
"youVoted": "You voted",
|
||||||
@@ -462,7 +474,7 @@
|
|||||||
"rewardsColLiquidityProvisionHeader": "LIQUIDITY PROVISION",
|
"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",
|
"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",
|
"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 [rewards.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 {{marketCreationQuantumMultiple}}",
|
||||||
"rewardsColTotalHeader": "TOTAL",
|
"rewardsColTotalHeader": "TOTAL",
|
||||||
"ofTotalDistributed": "of total distributed",
|
"ofTotalDistributed": "of total distributed",
|
||||||
"checkBackSoon": "Check back soon",
|
"checkBackSoon": "Check back soon",
|
||||||
@@ -696,7 +708,7 @@
|
|||||||
"NewAssetProposal": "New asset proposal",
|
"NewAssetProposal": "New asset proposal",
|
||||||
"UpdateAssetProposal": "Update asset proposal",
|
"UpdateAssetProposal": "Update asset proposal",
|
||||||
"NewFreeformProposal": "New freeform proposal",
|
"NewFreeformProposal": "New freeform proposal",
|
||||||
"NewRawProposal": "New raw proposal",
|
"NewRawProposal": "New proposal",
|
||||||
"MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a 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",
|
"MinProposalVoteRequirements": "You must have at least {{value}} VEGA associated to vote on this proposal",
|
||||||
"totalSupply": "Total Supply",
|
"totalSupply": "Total Supply",
|
||||||
@@ -858,5 +870,9 @@
|
|||||||
"Upgraded at": "Upgraded at",
|
"Upgraded at": "Upgraded at",
|
||||||
"dataIsIdentical": "Data is identical",
|
"dataIsIdentical": "Data is identical",
|
||||||
"updatesToMarket": "Updates to market",
|
"updatesToMarket": "Updates to market",
|
||||||
"viewAsParty": "View as party"
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ import {
|
|||||||
orderByDate,
|
orderByDate,
|
||||||
orderByUpgradeBlockHeight,
|
orderByUpgradeBlockHeight,
|
||||||
} from '../proposals/components/proposals-list/proposals-list';
|
} from '../proposals/components/proposals-list/proposals-list';
|
||||||
import {
|
|
||||||
NetworkParams,
|
|
||||||
useNetworkParams,
|
|
||||||
} from '@vegaprotocol/network-parameters';
|
|
||||||
import { BigNumber } from '../../lib/bignumber';
|
import { BigNumber } from '../../lib/bignumber';
|
||||||
|
|
||||||
const nodesToShow = 6;
|
const nodesToShow = 6;
|
||||||
@@ -47,57 +43,34 @@ const HomeProposals = ({
|
|||||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
|
||||||
params: networkParams,
|
|
||||||
loading: networkParamsLoading,
|
|
||||||
error: networkParamsError,
|
|
||||||
} = useNetworkParams([
|
|
||||||
NetworkParams.governance_proposal_market_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
|
||||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<section className="mb-16" data-testid="home-proposals">
|
||||||
loading={networkParamsLoading}
|
<Heading title={t('vegaGovernance')} />
|
||||||
error={networkParamsError}
|
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
|
||||||
data={networkParams}
|
<div className="mb-8">
|
||||||
>
|
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
|
||||||
<section className="mb-16" data-testid="home-proposals">
|
{t(`readMoreGovernance`)}
|
||||||
<Heading title={t('vegaGovernance')} />
|
</ExternalLink>
|
||||||
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
|
</div>
|
||||||
<div className="mb-8">
|
|
||||||
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
|
|
||||||
{t(`readMoreGovernance`)}
|
|
||||||
</ExternalLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SubHeading title={t('latestProposals')} />
|
<SubHeading title={t('latestProposals')} />
|
||||||
<ul data-testid="home-proposal-list" className="grid gap-6">
|
<ul data-testid="home-proposal-list" className="grid gap-6">
|
||||||
{protocolUpgradeProposals.map((proposal, index) => (
|
{protocolUpgradeProposals.map((proposal, index) => (
|
||||||
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{proposals.map((proposal) => (
|
{proposals.map((proposal) => (
|
||||||
<ProposalsListItem
|
<ProposalsListItem key={proposal.id} proposal={proposal} />
|
||||||
key={proposal.id}
|
))}
|
||||||
proposal={proposal}
|
</ul>
|
||||||
networkParams={networkParams}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link to={`${Routes.PROPOSALS}`}>
|
<Link to={`${Routes.PROPOSALS}`}>
|
||||||
<Button size="md">{t('homeProposalsButtonText')}</Button>
|
<Button size="md">{t('homeProposalsButtonText')}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</AsyncRenderer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,8 +279,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
|||||||
trimmedActiveNodes={trimmedActiveNodes}
|
trimmedActiveNodes={trimmedActiveNodes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section className="grid grid-cols-2 gap-12 mb-16">
|
<section className="flex justify-between flex-wrap gap-12 mb-16">
|
||||||
<div data-testid="home-rewards">
|
<div className="min-w-[360px] flex-1" data-testid="home-rewards">
|
||||||
<Heading title={t('Rewards')} marginTop={false} />
|
<Heading title={t('Rewards')} marginTop={false} />
|
||||||
<h3 className="mb-6">{t('homeRewardsIntro')}</h3>
|
<h3 className="mb-6">{t('homeRewardsIntro')}</h3>
|
||||||
<div className="flex items-center mb-8 gap-4">
|
<div className="flex items-center mb-8 gap-4">
|
||||||
@@ -317,7 +290,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-testid="home-vega-token">
|
<div className="min-w-[360px] flex-1" data-testid="home-vega-token">
|
||||||
<Heading title={t('vegaToken')} marginTop={false} />
|
<Heading title={t('vegaToken')} marginTop={false} />
|
||||||
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
|
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
|
||||||
<div className="flex items-center mb-8 gap-4">
|
<div className="flex items-center mb-8 gap-4">
|
||||||
|
|||||||
-2
@@ -16,7 +16,6 @@ import { ProposalHeader } from './proposal-header';
|
|||||||
import {
|
import {
|
||||||
lastWeek,
|
lastWeek,
|
||||||
nextWeek,
|
nextWeek,
|
||||||
mockNetworkParams,
|
|
||||||
mockWalletContext,
|
mockWalletContext,
|
||||||
createUserVoteQueryMock,
|
createUserVoteQueryMock,
|
||||||
} from '../../test-helpers/mocks';
|
} from '../../test-helpers/mocks';
|
||||||
@@ -48,7 +47,6 @@ const renderComponent = (
|
|||||||
<ProposalHeader
|
<ProposalHeader
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
isListItem={isListItem}
|
isListItem={isListItem}
|
||||||
networkParams={mockNetworkParams}
|
|
||||||
voteState={voteState}
|
voteState={voteState}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
|
|||||||
+4
-7
@@ -8,22 +8,19 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|||||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||||
import { CurrentProposalState } from '../current-proposal-state';
|
import { CurrentProposalState } from '../current-proposal-state';
|
||||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||||
import { ProposalVotingStatus } from '../proposal-voting-status';
|
|
||||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
|
||||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { VoteState } from '../vote-details/use-user-vote';
|
import type { VoteState } from '../vote-details/use-user-vote';
|
||||||
|
import { VoteBreakdown } from '../vote-breakdown';
|
||||||
|
|
||||||
export const ProposalHeader = ({
|
export const ProposalHeader = ({
|
||||||
proposal,
|
proposal,
|
||||||
networkParams,
|
|
||||||
isListItem = true,
|
isListItem = true,
|
||||||
voteState,
|
voteState,
|
||||||
}: {
|
}: {
|
||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||||
networkParams: Partial<NetworkParamsResult>;
|
|
||||||
isListItem?: boolean;
|
isListItem?: boolean;
|
||||||
voteState?: VoteState | null;
|
voteState?: VoteState | null;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -146,7 +143,7 @@ export const ProposalHeader = ({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
data-testid={`user-voted-${voteState.toLowerCase()}`}
|
data-testid={`user-voted-${voteState.toLowerCase()}`}
|
||||||
>
|
>
|
||||||
<div className="text-vega-green" data-testid="you-voted-icon">
|
<div data-testid="you-voted-icon">
|
||||||
<VegaIcon name={VegaIconNames.VOTE} size={24} />
|
<VegaIcon name={VegaIconNames.VOTE} size={24} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -162,7 +159,7 @@ export const ProposalHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-testid="proposal-title">
|
<div data-testid="proposal-title" className="break-all">
|
||||||
{isListItem ? (
|
{isListItem ? (
|
||||||
<header>
|
<header>
|
||||||
<SubHeading
|
<SubHeading
|
||||||
@@ -185,7 +182,7 @@ export const ProposalHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ProposalVotingStatus proposal={proposal} networkParams={networkParams} />
|
<VoteBreakdown proposal={proposal} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { ProposalVotesTable } from './proposal-votes-table';
|
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
|
||||||
import { ProposalVotesTable } from './proposal-votes-table';
|
|
||||||
import { ProposalType } from '../proposal/proposal';
|
|
||||||
import {
|
|
||||||
generateNoVotes,
|
|
||||||
generateProposal,
|
|
||||||
generateYesVotes,
|
|
||||||
} from '../../test-helpers/generate-proposals';
|
|
||||||
|
|
||||||
const defaultProposal = generateProposal();
|
|
||||||
const defaultProposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
|
|
||||||
const updateMarketProposal = generateProposal({
|
|
||||||
terms: {
|
|
||||||
change: {
|
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
marketId: '12345',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(10),
|
|
||||||
no: generateNoVotes(0),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const updateMarketProposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
|
|
||||||
|
|
||||||
const renderComponent = (
|
|
||||||
proposal = defaultProposal,
|
|
||||||
proposalType = defaultProposalType
|
|
||||||
) =>
|
|
||||||
render(
|
|
||||||
<MockedProvider>
|
|
||||||
<AppStateProvider>
|
|
||||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
|
||||||
</AppStateProvider>
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('Proposal Votes Table', () => {
|
|
||||||
it('should render successfully', () => {
|
|
||||||
const { baseElement } = renderComponent();
|
|
||||||
expect(baseElement).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show vote breakdown fields, excluding custom update market fields', () => {
|
|
||||||
renderComponent();
|
|
||||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
|
||||||
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Token majority met')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Token participation met')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Tokens for proposal')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Total Supply')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Tokens against proposal')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Participation required')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Majority Required')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Total tokens voted')).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByText('Total tokens voted percentage')
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Number of votes for')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Number of votes against')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Yes percentage')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('No percentage')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('Liquidity majority met')).toBeNull();
|
|
||||||
expect(screen.queryByText('Liquidity participation met')).toBeNull();
|
|
||||||
expect(screen.queryByText('Liquidity shares for proposal')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays different breakdown fields for update market proposal', () => {
|
|
||||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
|
||||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
|
||||||
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByText('Liquidity shares for proposal')
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('Number of voting parties')).toBeNull();
|
|
||||||
expect(screen.queryByText('Total tokens voted')).toBeNull();
|
|
||||||
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
|
|
||||||
expect(screen.queryByText('Number of votes for')).toBeNull();
|
|
||||||
expect(screen.queryByText('Number of votes against')).toBeNull();
|
|
||||||
expect(screen.queryByText('Yes percentage')).toBeNull();
|
|
||||||
expect(screen.queryByText('No percentage')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays if an update market proposal will pass by token vote', () => {
|
|
||||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
|
||||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
|
||||||
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays if an update market proposal will pass by LP vote', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
terms: {
|
|
||||||
change: {
|
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
marketId: '12345',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: {
|
|
||||||
...generateYesVotes(0, 1, '10'),
|
|
||||||
},
|
|
||||||
no: {
|
|
||||||
...generateNoVotes(0, 1, '0'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
updateMarketProposalType
|
|
||||||
);
|
|
||||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
|
||||||
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-177
@@ -1,177 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
KeyValueTable,
|
|
||||||
KeyValueTableRow,
|
|
||||||
Thumbs,
|
|
||||||
RoundedWrapper,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
|
|
||||||
import { SubHeading } from '../../../../components/heading';
|
|
||||||
import { useVoteInformation } from '../../hooks';
|
|
||||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
|
||||||
import { ProposalType } from '../proposal/proposal';
|
|
||||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|
||||||
|
|
||||||
interface ProposalVotesTableProps {
|
|
||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
|
||||||
proposalType: ProposalType | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProposalVotesTable = ({
|
|
||||||
proposal,
|
|
||||||
proposalType,
|
|
||||||
}: ProposalVotesTableProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
appState: { totalSupply },
|
|
||||||
} = useAppState();
|
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
|
||||||
const {
|
|
||||||
willPassByTokenVote,
|
|
||||||
willPassByLPVote,
|
|
||||||
totalTokensPercentage,
|
|
||||||
participationMet,
|
|
||||||
participationLPMet,
|
|
||||||
totalTokensVoted,
|
|
||||||
noPercentage,
|
|
||||||
yesPercentage,
|
|
||||||
noTokens,
|
|
||||||
yesTokens,
|
|
||||||
yesEquityLikeShareWeight,
|
|
||||||
yesVotes,
|
|
||||||
noVotes,
|
|
||||||
totalVotes,
|
|
||||||
requiredMajorityPercentage,
|
|
||||||
requiredParticipation,
|
|
||||||
majorityMet,
|
|
||||||
majorityLPMet,
|
|
||||||
} = useVoteInformation({ proposal });
|
|
||||||
|
|
||||||
const isUpdateMarket = proposalType === ProposalType.PROPOSAL_UPDATE_MARKET;
|
|
||||||
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
|
|
||||||
const updateMarketVotePassMethod = willPassByTokenVote
|
|
||||||
? t('byTokenVote')
|
|
||||||
: t('byLiquidityVote');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CollapsibleToggle
|
|
||||||
toggleState={showDetails}
|
|
||||||
setToggleState={setShowDetails}
|
|
||||||
dataTestId="vote-breakdown-toggle"
|
|
||||||
>
|
|
||||||
<SubHeading title={t('voteBreakdown')} />
|
|
||||||
</CollapsibleToggle>
|
|
||||||
|
|
||||||
{showDetails && (
|
|
||||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
|
||||||
<KeyValueTable
|
|
||||||
data-testid="proposal-votes-table"
|
|
||||||
numerical={true}
|
|
||||||
headingLevel={4}
|
|
||||||
>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('expectedToPass')}
|
|
||||||
{isUpdateMarket ? (
|
|
||||||
updateMarketWillPass ? (
|
|
||||||
<Thumbs up={true} text={updateMarketVotePassMethod} />
|
|
||||||
) : (
|
|
||||||
<Thumbs up={false} />
|
|
||||||
)
|
|
||||||
) : willPassByTokenVote ? (
|
|
||||||
<Thumbs up={true} />
|
|
||||||
) : (
|
|
||||||
<Thumbs up={false} />
|
|
||||||
)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('majorityMet')}
|
|
||||||
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('majorityLPMet')}
|
|
||||||
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
)}
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('participationMet')}
|
|
||||||
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('participationLPMet')}
|
|
||||||
{participationLPMet ? (
|
|
||||||
<Thumbs up={true} />
|
|
||||||
) : (
|
|
||||||
<Thumbs up={false} />
|
|
||||||
)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
)}
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('tokenForProposal')}
|
|
||||||
{formatNumber(yesTokens, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('tokenLPForProposal')}
|
|
||||||
{formatNumber(yesEquityLikeShareWeight, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
)}
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('totalSupply')}
|
|
||||||
{formatNumber(totalSupply, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('tokensAgainstProposal')}
|
|
||||||
{formatNumber(noTokens, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('participationRequired')}
|
|
||||||
{formatNumberPercentage(requiredParticipation)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('majorityRequired')}
|
|
||||||
{formatNumberPercentage(requiredMajorityPercentage)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{!isUpdateMarket && (
|
|
||||||
<>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('numberOfVotingParties')}
|
|
||||||
{formatNumber(totalVotes, 0)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('totalTokensVotes')}
|
|
||||||
{formatNumber(totalTokensVoted, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('totalTokenVotedPercentage')}
|
|
||||||
{formatNumberPercentage(totalTokensPercentage, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('numberOfForVotes')}
|
|
||||||
{formatNumber(yesVotes, 0)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('numberOfAgainstVotes')}
|
|
||||||
{formatNumber(noVotes, 0)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('yesPercentage')}
|
|
||||||
{formatNumberPercentage(yesPercentage, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow noBorder={true}>
|
|
||||||
{t('noPercentage')}
|
|
||||||
{formatNumberPercentage(noPercentage, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</KeyValueTable>
|
|
||||||
</RoundedWrapper>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './proposal-voting-status';
|
|
||||||
-153
@@ -1,153 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
|
||||||
import {
|
|
||||||
lastWeek,
|
|
||||||
mockWalletContext,
|
|
||||||
networkParamsQueryMock,
|
|
||||||
nextWeek,
|
|
||||||
mockNetworkParams,
|
|
||||||
} from '../../test-helpers/mocks';
|
|
||||||
import { ProposalVotingStatus } from './proposal-voting-status';
|
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
|
||||||
import {
|
|
||||||
generateNoVotes,
|
|
||||||
generateProposal,
|
|
||||||
generateYesVotes,
|
|
||||||
} from '../../test-helpers/generate-proposals';
|
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
|
||||||
import { BigNumber } from '../../../../lib/bignumber';
|
|
||||||
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
|
||||||
|
|
||||||
const mockTotalSupply = new BigNumber(100);
|
|
||||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
|
||||||
const fixedTokenValue = 1000000000000000000;
|
|
||||||
|
|
||||||
const mockAppState: AppState = {
|
|
||||||
totalAssociated: new BigNumber('50063005'),
|
|
||||||
decimals: 18,
|
|
||||||
totalSupply: mockTotalSupply,
|
|
||||||
vegaWalletManageOverlay: false,
|
|
||||||
transactionOverlay: false,
|
|
||||||
bannerMessage: '',
|
|
||||||
disconnectNotice: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
|
||||||
useAppState: () => ({
|
|
||||||
appState: mockAppState,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const renderComponent = (
|
|
||||||
proposal: ProposalQuery['proposal'],
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
|
||||||
) =>
|
|
||||||
render(
|
|
||||||
<Router>
|
|
||||||
<MockedProvider mocks={mocks}>
|
|
||||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
|
||||||
<ProposalVotingStatus
|
|
||||||
proposal={proposal}
|
|
||||||
networkParams={mockNetworkParams}
|
|
||||||
/>
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
</MockedProvider>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('ProposalVotingStatus', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
jest.setSystemTime(0);
|
|
||||||
});
|
|
||||||
afterAll(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders majority reached', () => {
|
|
||||||
const yesVotes = 100;
|
|
||||||
const noVotes = 0;
|
|
||||||
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_PASSED,
|
|
||||||
terms: {
|
|
||||||
closingDatetime: lastWeek.toString(),
|
|
||||||
enactmentDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
|
||||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('majority-reached')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders majority not reached', () => {
|
|
||||||
const yesVotes = 20;
|
|
||||||
const noVotes = 80;
|
|
||||||
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_PASSED,
|
|
||||||
terms: {
|
|
||||||
closingDatetime: lastWeek.toString(),
|
|
||||||
enactmentDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
|
||||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('majority-not-reached')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders participation reached', () => {
|
|
||||||
const yesVotes = 1000;
|
|
||||||
const noVotes = 0;
|
|
||||||
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_PASSED,
|
|
||||||
terms: {
|
|
||||||
closingDatetime: lastWeek.toString(),
|
|
||||||
enactmentDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
|
||||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('participation-reached')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders participation not reached', () => {
|
|
||||||
const yesVotes = 0;
|
|
||||||
const noVotes = 0;
|
|
||||||
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
terms: {
|
|
||||||
closingDatetime: lastWeek.toString(),
|
|
||||||
enactmentDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
|
||||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('participation-not-reached')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-174
@@ -1,174 +0,0 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useVoteInformation } from '../../hooks';
|
|
||||||
import { BigNumber } from '../../../../lib/bignumber';
|
|
||||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|
||||||
|
|
||||||
const statusClasses = (reached: boolean) =>
|
|
||||||
classNames('flex items-center gap-2 px-4 py-2 rounded-md', {
|
|
||||||
'bg-vega-green-700': reached,
|
|
||||||
'bg-vega-red-700': !reached,
|
|
||||||
});
|
|
||||||
|
|
||||||
const MajorityStatus = ({
|
|
||||||
reached,
|
|
||||||
requiredMajority,
|
|
||||||
}: {
|
|
||||||
reached: boolean;
|
|
||||||
requiredMajority: string | null | undefined;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={statusClasses(reached)}
|
|
||||||
data-testid="proposal-majority-status"
|
|
||||||
>
|
|
||||||
{reached ? <Icon name="tick" /> : <Icon name="cross" />}
|
|
||||||
{reached ? (
|
|
||||||
<div data-testid="majority-reached">
|
|
||||||
{requiredMajority ? (
|
|
||||||
<>
|
|
||||||
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
|
|
||||||
{t('majorityVotedForProposal')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('requiredMajorityVotedForProposal')
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div data-testid="majority-not-reached">
|
|
||||||
{requiredMajority ? (
|
|
||||||
<>
|
|
||||||
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
|
|
||||||
{t('majorityNotVotedForProposal')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('requiredMajorityNotVotedForProposal')
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ParticipationStatus = ({ reached }: { reached: boolean }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={statusClasses(reached)}>
|
|
||||||
{reached ? (
|
|
||||||
<>
|
|
||||||
<Icon name="tick" />
|
|
||||||
<div data-testid="participation-reached">
|
|
||||||
{t('minParticipationReached')}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon name="cross" />
|
|
||||||
<div data-testid="participation-not-reached">
|
|
||||||
{t('minParticipationNotReached')}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProposalVotingStatus = ({
|
|
||||||
proposal,
|
|
||||||
networkParams,
|
|
||||||
}: {
|
|
||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
|
||||||
networkParams: Partial<NetworkParamsResult>;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { majorityMet, majorityLPMet, participationMet, participationLPMet } =
|
|
||||||
useVoteInformation({
|
|
||||||
proposal,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!proposal) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
|
|
||||||
|
|
||||||
let requiredVotingMajority = null;
|
|
||||||
let requiredVotingMajorityLP = null;
|
|
||||||
|
|
||||||
if (networkParams) {
|
|
||||||
switch (proposal.terms.change.__typename) {
|
|
||||||
case 'NewMarket':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_market_requiredMajority;
|
|
||||||
break;
|
|
||||||
case 'UpdateMarket':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_updateMarket_requiredMajority;
|
|
||||||
requiredVotingMajorityLP =
|
|
||||||
networkParams.governance_proposal_updateMarket_requiredMajorityLP;
|
|
||||||
break;
|
|
||||||
case 'NewAsset':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_asset_requiredMajority;
|
|
||||||
break;
|
|
||||||
case 'UpdateAsset':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_updateAsset_requiredMajority;
|
|
||||||
break;
|
|
||||||
case 'UpdateNetworkParameter':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_updateNetParam_requiredMajority;
|
|
||||||
break;
|
|
||||||
case 'NewFreeform':
|
|
||||||
requiredVotingMajority =
|
|
||||||
networkParams.governance_proposal_freeform_requiredMajority;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUpdateMarket) {
|
|
||||||
return (
|
|
||||||
<div className="mb-6">
|
|
||||||
<p>{t('Token vote')}</p>
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-2 gap-4 items-center"
|
|
||||||
data-testid="token-vote-statuses"
|
|
||||||
>
|
|
||||||
<MajorityStatus
|
|
||||||
reached={majorityMet}
|
|
||||||
requiredMajority={requiredVotingMajority}
|
|
||||||
/>{' '}
|
|
||||||
<ParticipationStatus reached={participationMet} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-4">{t('Liquidity provider vote')}</p>
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-2 gap-4 items-center"
|
|
||||||
data-testid="lp-vote-statuses"
|
|
||||||
>
|
|
||||||
<MajorityStatus
|
|
||||||
reached={majorityLPMet}
|
|
||||||
requiredMajority={requiredVotingMajorityLP}
|
|
||||||
/>{' '}
|
|
||||||
<ParticipationStatus reached={participationLPMet} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-2 gap-4 items-center mb-6">
|
|
||||||
<MajorityStatus
|
|
||||||
reached={majorityMet}
|
|
||||||
requiredMajority={requiredVotingMajority}
|
|
||||||
/>{' '}
|
|
||||||
<ParticipationStatus reached={participationMet} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -34,12 +34,6 @@ jest.mock('../proposal-change-table', () => ({
|
|||||||
jest.mock('../proposal-json', () => ({
|
jest.mock('../proposal-json', () => ({
|
||||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||||
}));
|
}));
|
||||||
jest.mock('../proposal-votes-table', () => ({
|
|
||||||
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
|
|
||||||
}));
|
|
||||||
jest.mock('../vote-details', () => ({
|
|
||||||
VoteDetails: () => <div data-testid="proposal-vote-details"></div>,
|
|
||||||
}));
|
|
||||||
jest.mock('../list-asset', () => ({
|
jest.mock('../list-asset', () => ({
|
||||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||||
}));
|
}));
|
||||||
@@ -104,8 +98,6 @@ it('renders each section', async () => {
|
|||||||
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
|||||||
import { ProposalDescription } from '../proposal-description';
|
import { ProposalDescription } from '../proposal-description';
|
||||||
import { ProposalChangeTable } from '../proposal-change-table';
|
import { ProposalChangeTable } from '../proposal-change-table';
|
||||||
import { ProposalJson } from '../proposal-json';
|
import { ProposalJson } from '../proposal-json';
|
||||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
|
||||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||||
import { VoteDetails } from '../vote-details';
|
import { UserVote } from '../vote-details';
|
||||||
import { ListAsset } from '../list-asset';
|
import { ListAsset } from '../list-asset';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import { ProposalMarketData } from '../proposal-market-data';
|
import { ProposalMarketData } from '../proposal-market-data';
|
||||||
@@ -22,14 +21,6 @@ import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
|||||||
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||||
import { useUserVote } from '../vote-details/use-user-vote';
|
import { useUserVote } from '../vote-details/use-user-vote';
|
||||||
|
|
||||||
export enum ProposalType {
|
|
||||||
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
|
|
||||||
PROPOSAL_UPDATE_MARKET = 'PROPOSAL_UPDATE_MARKET',
|
|
||||||
PROPOSAL_NEW_ASSET = 'PROPOSAL_NEW_ASSET',
|
|
||||||
PROPOSAL_UPDATE_ASSET = 'PROPOSAL_UPDATE_ASSET',
|
|
||||||
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
|
|
||||||
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
|
|
||||||
}
|
|
||||||
export interface ProposalProps {
|
export interface ProposalProps {
|
||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||||
networkParams: Partial<NetworkParamsResult>;
|
networkParams: Partial<NetworkParamsResult>;
|
||||||
@@ -80,39 +71,32 @@ export const Proposal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let minVoterBalance = null;
|
let minVoterBalance = null;
|
||||||
let proposalType = null;
|
|
||||||
|
|
||||||
if (networkParams) {
|
if (networkParams) {
|
||||||
switch (proposal.terms.change.__typename) {
|
switch (proposal.terms.change.__typename) {
|
||||||
case 'NewMarket':
|
case 'NewMarket':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_market_minVoterBalance;
|
networkParams.governance_proposal_market_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_NEW_MARKET;
|
|
||||||
break;
|
break;
|
||||||
case 'UpdateMarket':
|
case 'UpdateMarket':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_updateMarket_minVoterBalance;
|
networkParams.governance_proposal_updateMarket_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
|
|
||||||
break;
|
break;
|
||||||
case 'NewAsset':
|
case 'NewAsset':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_asset_minVoterBalance;
|
networkParams.governance_proposal_asset_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_NEW_ASSET;
|
|
||||||
break;
|
break;
|
||||||
case 'UpdateAsset':
|
case 'UpdateAsset':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_updateAsset_minVoterBalance;
|
networkParams.governance_proposal_updateAsset_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_UPDATE_ASSET;
|
|
||||||
break;
|
break;
|
||||||
case 'UpdateNetworkParameter':
|
case 'UpdateNetworkParameter':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_updateNetParam_minVoterBalance;
|
networkParams.governance_proposal_updateNetParam_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
|
|
||||||
break;
|
break;
|
||||||
case 'NewFreeform':
|
case 'NewFreeform':
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||||
proposalType = ProposalType.PROPOSAL_FREEFORM;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,91 +124,81 @@ export const Proposal = ({
|
|||||||
<ProposalHeader
|
<ProposalHeader
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
isListItem={false}
|
isListItem={false}
|
||||||
networkParams={networkParams}
|
|
||||||
voteState={voteState}
|
voteState={voteState}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div id="details">
|
<div className="my-10">
|
||||||
<div className="my-10">
|
<ProposalChangeTable proposal={proposal} />
|
||||||
<ProposalChangeTable proposal={proposal} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
|
||||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
|
||||||
proposal.id ? (
|
|
||||||
<ListAsset
|
|
||||||
assetId={proposal.id}
|
|
||||||
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
|
||||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<ProposalDescription description={proposal.rationale.description} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{newMarketData && (
|
|
||||||
<div className="mb-4">
|
|
||||||
<ProposalMarketData
|
|
||||||
marketData={newMarketData}
|
|
||||||
parentMarketData={parentMarketData ? parentMarketData : undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
|
||||||
<div className="mb-4">
|
|
||||||
<ProposalMarketChanges
|
|
||||||
originalProposal={
|
|
||||||
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
|
||||||
?.changes || {}
|
|
||||||
}
|
|
||||||
latestEnactedProposal={
|
|
||||||
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal
|
|
||||||
?.terms?.updateMarket?.changes || {}
|
|
||||||
}
|
|
||||||
updatedProposal={
|
|
||||||
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
|
||||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
|
||||||
asset && (
|
|
||||||
<div className="mb-4">
|
|
||||||
<ProposalAssetDetails asset={asset} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mb-6">
|
|
||||||
<ProposalJson proposal={restData?.data?.proposal} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="voting">
|
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||||
<div className="mb-10">
|
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||||
<RoundedWrapper paddingBottom={true}>
|
proposal.id ? (
|
||||||
<VoteDetails
|
<ListAsset
|
||||||
proposal={proposal}
|
assetId={proposal.id}
|
||||||
proposalType={proposalType}
|
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||||
minVoterBalance={minVoterBalance}
|
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||||
spamProtectionMinTokens={
|
/>
|
||||||
networkParams?.spam_protection_voting_min_tokens
|
) : null}
|
||||||
}
|
|
||||||
submit={submit}
|
|
||||||
dialog={Dialog}
|
|
||||||
transaction={transaction}
|
|
||||||
voteState={voteState}
|
|
||||||
voteDatetime={voteDatetime}
|
|
||||||
/>
|
|
||||||
</RoundedWrapper>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalDescription description={proposal.rationale.description} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{newMarketData && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
<ProposalMarketData
|
||||||
|
marketData={newMarketData}
|
||||||
|
parentMarketData={parentMarketData ? parentMarketData : undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalMarketChanges
|
||||||
|
originalProposal={
|
||||||
|
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||||
|
?.changes || {}
|
||||||
|
}
|
||||||
|
latestEnactedProposal={
|
||||||
|
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal?.terms
|
||||||
|
?.updateMarket?.changes || {}
|
||||||
|
}
|
||||||
|
updatedProposal={
|
||||||
|
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||||
|
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||||
|
asset && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalAssetDetails asset={asset} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-10">
|
||||||
|
<RoundedWrapper paddingBottom={true}>
|
||||||
|
<UserVote
|
||||||
|
proposal={proposal}
|
||||||
|
minVoterBalance={minVoterBalance}
|
||||||
|
spamProtectionMinTokens={
|
||||||
|
networkParams?.spam_protection_voting_min_tokens
|
||||||
|
}
|
||||||
|
submit={submit}
|
||||||
|
dialog={Dialog}
|
||||||
|
transaction={transaction}
|
||||||
|
voteState={voteState}
|
||||||
|
voteDatetime={voteDatetime}
|
||||||
|
/>
|
||||||
|
</RoundedWrapper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<ProposalJson proposal={restData?.data?.proposal} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-120
@@ -6,11 +6,7 @@ import { MockedProvider } from '@apollo/client/testing';
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
||||||
import {
|
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||||
generateNoVotes,
|
|
||||||
generateProposal,
|
|
||||||
generateYesVotes,
|
|
||||||
} from '../../test-helpers/generate-proposals';
|
|
||||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||||
import {
|
import {
|
||||||
@@ -93,84 +89,6 @@ describe('Proposals list item details', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_OPEN,
|
|
||||||
terms: {
|
|
||||||
change: {
|
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
yes: {
|
|
||||||
...generateYesVotes(0),
|
|
||||||
totalEquityLikeShareWeight: '1000',
|
|
||||||
},
|
|
||||||
no: {
|
|
||||||
...generateNoVotes(0),
|
|
||||||
totalEquityLikeShareWeight: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
|
||||||
'Currently expected to pass by LP vote'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_OPEN,
|
|
||||||
terms: {
|
|
||||||
change: {
|
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
yes: {
|
|
||||||
...generateYesVotes(1000, 1000),
|
|
||||||
totalEquityLikeShareWeight: '0',
|
|
||||||
},
|
|
||||||
no: {
|
|
||||||
...generateNoVotes(0),
|
|
||||||
totalEquityLikeShareWeight: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
|
||||||
'Currently expected to pass by token vote'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_OPEN,
|
|
||||||
terms: {
|
|
||||||
change: {
|
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
yes: {
|
|
||||||
...generateYesVotes(0),
|
|
||||||
totalEquityLikeShareWeight: '0',
|
|
||||||
},
|
|
||||||
no: {
|
|
||||||
...generateNoVotes(0),
|
|
||||||
totalEquityLikeShareWeight: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
|
||||||
'Currently expected to fail'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders proposal state: Open - 5 minutes left to vote', () => {
|
it('Renders proposal state: Open - 5 minutes left to vote', () => {
|
||||||
renderComponent(
|
renderComponent(
|
||||||
generateProposal({
|
generateProposal({
|
||||||
@@ -213,43 +131,6 @@ describe('Proposals list item details', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders proposal state: Open - majority not reached', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_OPEN,
|
|
||||||
terms: {
|
|
||||||
enactmentDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
votes: {
|
|
||||||
no: generateNoVotes(1, 1000000000000000000),
|
|
||||||
yes: generateYesVotes(1, 1000000000000000000),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
|
||||||
'Currently expected to fail'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders proposal state: Open - will pass', () => {
|
|
||||||
renderComponent(
|
|
||||||
generateProposal({
|
|
||||||
state: ProposalState.STATE_OPEN,
|
|
||||||
votes: {
|
|
||||||
__typename: 'ProposalVotes',
|
|
||||||
yes: generateYesVotes(3000, 1000000000000000000),
|
|
||||||
no: generateNoVotes(0),
|
|
||||||
},
|
|
||||||
terms: {
|
|
||||||
closingDatetime: nextWeek.toString(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
|
||||||
'Currently expected to pass'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders proposal state: Rejected', () => {
|
it('Renders proposal state: Rejected', () => {
|
||||||
renderComponent(
|
renderComponent(
|
||||||
generateProposal({
|
generateProposal({
|
||||||
|
|||||||
+1
-30
@@ -11,7 +11,6 @@ import {
|
|||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import { useVoteInformation } from '../../hooks';
|
|
||||||
|
|
||||||
export const ProposalsListItemDetails = ({
|
export const ProposalsListItemDetails = ({
|
||||||
proposal,
|
proposal,
|
||||||
@@ -20,18 +19,10 @@ export const ProposalsListItemDetails = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const state = proposal?.state;
|
const state = proposal?.state;
|
||||||
const { willPassByTokenVote, willPassByLPVote } = useVoteInformation({
|
|
||||||
proposal,
|
|
||||||
});
|
|
||||||
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
|
|
||||||
const updateMarketVotePassMethod = willPassByTokenVote
|
|
||||||
? t('byTokenVote')
|
|
||||||
: t('byLPVote');
|
|
||||||
const nowToEnactmentInHours = differenceInHours(
|
const nowToEnactmentInHours = differenceInHours(
|
||||||
new Date(proposal?.terms.closingDatetime),
|
new Date(proposal?.terms.closingDatetime),
|
||||||
new Date()
|
new Date()
|
||||||
);
|
);
|
||||||
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
|
|
||||||
|
|
||||||
let voteDetails: ReactNode;
|
let voteDetails: ReactNode;
|
||||||
let voteStatus: ReactNode;
|
let voteStatus: ReactNode;
|
||||||
@@ -78,31 +69,11 @@ export const ProposalsListItemDetails = ({
|
|||||||
}
|
}
|
||||||
case ProposalState.STATE_OPEN: {
|
case ProposalState.STATE_OPEN: {
|
||||||
voteDetails = (
|
voteDetails = (
|
||||||
<span className={nowToEnactmentInHours < 6 ? 'text-vega-pink' : ''}>
|
<span className={nowToEnactmentInHours < 6 ? 'text-vega-orange' : ''}>
|
||||||
{formatDistanceToNowStrict(new Date(proposal?.terms.closingDatetime))}{' '}
|
{formatDistanceToNowStrict(new Date(proposal?.terms.closingDatetime))}{' '}
|
||||||
{t('left to vote')}
|
{t('left to vote')}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
voteStatus =
|
|
||||||
(isUpdateMarket &&
|
|
||||||
(updateMarketWillPass ? (
|
|
||||||
<>
|
|
||||||
{t('currentlySetTo')} {t('pass')} {updateMarketVotePassMethod}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{t('currentlySetTo')} {t('fail')}
|
|
||||||
</>
|
|
||||||
))) ||
|
|
||||||
(willPassByTokenVote ? (
|
|
||||||
<>
|
|
||||||
{t('currentlySetTo')} {t('pass')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{t('currentlySetTo')} {t('fail')}
|
|
||||||
</>
|
|
||||||
));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_REJECTED: {
|
case ProposalState.STATE_REJECTED: {
|
||||||
|
|||||||
+3
-12
@@ -4,28 +4,19 @@ import { ProposalsListItemDetails } from './proposals-list-item-details';
|
|||||||
import { useUserVote } from '../vote-details/use-user-vote';
|
import { useUserVote } from '../vote-details/use-user-vote';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
|
||||||
|
|
||||||
interface ProposalsListItemProps {
|
interface ProposalsListItemProps {
|
||||||
proposal?: ProposalFieldsFragment | ProposalQuery['proposal'] | null;
|
proposal?: ProposalFieldsFragment | ProposalQuery['proposal'] | null;
|
||||||
networkParams: Partial<NetworkParamsResult> | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProposalsListItem = ({
|
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
|
||||||
proposal,
|
|
||||||
networkParams,
|
|
||||||
}: ProposalsListItemProps) => {
|
|
||||||
const { voteState } = useUserVote(proposal?.id);
|
const { voteState } = useUserVote(proposal?.id);
|
||||||
if (!proposal || !proposal.id || !networkParams) return null;
|
if (!proposal || !proposal.id) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li id={proposal.id} data-testid="proposals-list-item">
|
<li id={proposal.id} data-testid="proposals-list-item">
|
||||||
<RoundedWrapper paddingBottom={true} heightFull={true}>
|
<RoundedWrapper paddingBottom={true} heightFull={true}>
|
||||||
<ProposalHeader
|
<ProposalHeader proposal={proposal} voteState={voteState} />
|
||||||
proposal={proposal}
|
|
||||||
networkParams={networkParams}
|
|
||||||
voteState={voteState}
|
|
||||||
/>
|
|
||||||
<ProposalsListItemDetails proposal={proposal} />
|
<ProposalsListItemDetails proposal={proposal} />
|
||||||
</RoundedWrapper>
|
</RoundedWrapper>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
+146
-187
@@ -7,23 +7,13 @@ import { ProposalsListItem } from '../proposals-list-item';
|
|||||||
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import {
|
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
|
||||||
AsyncRenderer,
|
|
||||||
Button,
|
|
||||||
Toggle,
|
|
||||||
VegaIcon,
|
|
||||||
VegaIconNames,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
|
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||||
import {
|
|
||||||
NetworkParams,
|
|
||||||
useNetworkParams,
|
|
||||||
} from '@vegaprotocol/network-parameters';
|
|
||||||
|
|
||||||
interface ProposalsListProps {
|
interface ProposalsListProps {
|
||||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||||
@@ -60,7 +50,10 @@ export const orderByUpgradeBlockHeight = (
|
|||||||
) =>
|
) =>
|
||||||
orderBy(
|
orderBy(
|
||||||
arr,
|
arr,
|
||||||
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
|
[
|
||||||
|
(p) => (p?.upgradeBlockHeight ? parseInt(p.upgradeBlockHeight, 10) : 0),
|
||||||
|
(p) => p.vegaReleaseTag,
|
||||||
|
],
|
||||||
['desc', 'desc']
|
['desc', 'desc']
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -75,20 +68,6 @@ export const ProposalsList = ({
|
|||||||
lastBlockHeight,
|
lastBlockHeight,
|
||||||
}: ProposalsListProps) => {
|
}: ProposalsListProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
|
||||||
params: networkParams,
|
|
||||||
loading: networkParamsLoading,
|
|
||||||
error: networkParamsError,
|
|
||||||
} = useNetworkParams([
|
|
||||||
NetworkParams.governance_proposal_market_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
|
||||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [filterString, setFilterString] = useState('');
|
const [filterString, setFilterString] = useState('');
|
||||||
const [closedProposalsView, setClosedProposalsView] =
|
const [closedProposalsView, setClosedProposalsView] =
|
||||||
useState<ClosedProposalsViewOptions>(
|
useState<ClosedProposalsViewOptions>(
|
||||||
@@ -153,181 +132,161 @@ export const ProposalsList = ({
|
|||||||
p?.party?.id?.toString().includes(filterString);
|
p?.party?.id?.toString().includes(filterString);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<div data-testid="proposals-list">
|
||||||
loading={networkParamsLoading}
|
<div className="grid xs:grid-cols-2 items-center">
|
||||||
error={networkParamsError}
|
<Heading
|
||||||
data={networkParams}
|
centerContent={false}
|
||||||
>
|
marginBottom={false}
|
||||||
<div data-testid="proposals-list">
|
title={t('pageTitleProposals')}
|
||||||
<div className="grid xs:grid-cols-2 items-center">
|
/>
|
||||||
<Heading
|
|
||||||
centerContent={false}
|
|
||||||
marginBottom={false}
|
|
||||||
title={t('pageTitleProposals')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{DocsLinks && (
|
<div className="xs:justify-self-end" data-testid="new-proposal-link">
|
||||||
<div
|
<Link to={`${Routes.PROPOSALS}/propose/raw`}>
|
||||||
className="xs:justify-self-end"
|
<Button variant="primary" size="sm">
|
||||||
data-testid="new-proposal-link"
|
<div className="flex items-center gap-1">{t('NewProposal')}</div>
|
||||||
>
|
</Button>
|
||||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
|
</Link>
|
||||||
<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>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="mb-8">
|
<p className="mb-8">
|
||||||
{t(
|
{t(
|
||||||
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
|
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
|
||||||
)}{' '}
|
)}{' '}
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
data-testid="proposal-documentation-link"
|
data-testid="proposal-documentation-link"
|
||||||
href={ExternalLinks.GOVERNANCE_PAGE}
|
href={ExternalLinks.GOVERNANCE_PAGE}
|
||||||
className="text-white"
|
className="text-white"
|
||||||
>
|
>
|
||||||
{t(`Find out more about Vega governance`)}
|
{t(`Find out more about Vega governance`)}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{proposals.length > 0 && (
|
{proposals.length > 0 && (
|
||||||
<ProposalsListFilter
|
<ProposalsListFilter
|
||||||
filterString={filterString}
|
filterString={filterString}
|
||||||
setFilterString={(value) => {
|
setFilterString={(value) => {
|
||||||
setFilterString(value);
|
setFilterString(value);
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
// If the filter is engaged, ensure the user is viewing governance proposals,
|
// If the filter is engaged, ensure the user is viewing governance proposals,
|
||||||
// as network upgrades do not have IDs to filter by and will be excluded.
|
// as network upgrades do not have IDs to filter by and will be excluded.
|
||||||
setClosedProposalsView(
|
setClosedProposalsView(
|
||||||
ClosedProposalsViewOptions.NetworkGovernance
|
ClosedProposalsViewOptions.NetworkGovernance
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
||||||
<SubHeading title={t('openProposals')} />
|
<SubHeading title={t('openProposals')} />
|
||||||
|
|
||||||
{sortedProposals.open.length > 0 ||
|
{sortedProposals.open.length > 0 ||
|
||||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||||
<ul data-testid="open-proposals">
|
<ul data-testid="open-proposals">
|
||||||
{filterString.length < 1 &&
|
{filterString.length < 1 &&
|
||||||
sortedProtocolUpgradeProposals.open.map((proposal) => (
|
sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||||
<ProtocolUpgradeProposalsListItem
|
<ProtocolUpgradeProposalsListItem
|
||||||
key={proposal.upgradeBlockHeight}
|
key={proposal.upgradeBlockHeight}
|
||||||
proposal={proposal}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
|
||||||
<ProposalsListItem
|
|
||||||
key={proposal?.id}
|
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
networkParams={networkParams}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p className="mb-0" data-testid="no-open-proposals">
|
|
||||||
{t('noOpenProposals')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="relative">
|
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
||||||
<SubHeading title={t('closedProposals')} />
|
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||||
{sortedProposals.closed.length > 0 ||
|
))}
|
||||||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
|
</ul>
|
||||||
<>
|
) : (
|
||||||
{
|
<p className="mb-0" data-testid="no-open-proposals">
|
||||||
// We need both the closed proposals and closed protocol upgrade
|
{t('noOpenProposals')}
|
||||||
// proposals to be present for there to be a toggle. It also gets
|
</p>
|
||||||
// hidden if the user has filtered the list, as the upgrade proposals
|
)}
|
||||||
// do not have the necessary fields for filtering.
|
</section>
|
||||||
sortedProposals.closed.length > 0 &&
|
|
||||||
sortedProtocolUpgradeProposals.closed.length > 0 &&
|
<section className="relative">
|
||||||
filterString.length < 1 && (
|
<SubHeading title={t('closedProposals')} />
|
||||||
<div
|
{sortedProposals.closed.length > 0 ||
|
||||||
className="grid w-full justify-end xl:-mt-12 pb-6"
|
sortedProtocolUpgradeProposals.closed.length > 0 ? (
|
||||||
data-testid="toggle-closed-proposals"
|
<>
|
||||||
>
|
{
|
||||||
<div className="w-[440px]">
|
// We need both the closed proposals and closed protocol upgrade
|
||||||
<Toggle
|
// proposals to be present for there to be a toggle. It also gets
|
||||||
name="closed-proposals-toggle"
|
// hidden if the user has filtered the list, as the upgrade proposals
|
||||||
toggles={[
|
// do not have the necessary fields for filtering.
|
||||||
{
|
sortedProposals.closed.length > 0 &&
|
||||||
label: t(
|
sortedProtocolUpgradeProposals.closed.length > 0 &&
|
||||||
ClosedProposalsViewOptions.NetworkGovernance
|
filterString.length < 1 && (
|
||||||
),
|
<div
|
||||||
value:
|
className="flex justify-end xl:-mt-12 pb-6"
|
||||||
ClosedProposalsViewOptions.NetworkGovernance,
|
data-testid="toggle-closed-proposals"
|
||||||
},
|
>
|
||||||
{
|
<div className="w-full max-w-[420px]">
|
||||||
label: t(
|
<Toggle
|
||||||
ClosedProposalsViewOptions.NetworkUpgrades
|
name="closed-proposals-toggle"
|
||||||
),
|
toggles={[
|
||||||
value: ClosedProposalsViewOptions.NetworkUpgrades,
|
{
|
||||||
},
|
label: t(
|
||||||
]}
|
ClosedProposalsViewOptions.NetworkGovernance
|
||||||
checkedValue={closedProposalsView}
|
),
|
||||||
onChange={(e) =>
|
value: ClosedProposalsViewOptions.NetworkGovernance,
|
||||||
setClosedProposalsView(
|
},
|
||||||
e.target.value as ClosedProposalsViewOptions
|
{
|
||||||
)
|
label: t(
|
||||||
}
|
ClosedProposalsViewOptions.NetworkUpgrades
|
||||||
/>
|
),
|
||||||
</div>
|
value: ClosedProposalsViewOptions.NetworkUpgrades,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
checkedValue={closedProposalsView}
|
||||||
|
onChange={(e) =>
|
||||||
|
setClosedProposalsView(
|
||||||
|
e.target.value as ClosedProposalsViewOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<ul data-testid="closed-proposals">
|
<ul data-testid="closed-proposals">
|
||||||
{closedProposalsView ===
|
{closedProposalsView ===
|
||||||
ClosedProposalsViewOptions.NetworkUpgrades && (
|
ClosedProposalsViewOptions.NetworkUpgrades && (
|
||||||
<div data-testid="closed-upgrade-proposals">
|
<div data-testid="closed-upgrade-proposals">
|
||||||
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
|
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
|
||||||
<ProtocolUpgradeProposalsListItem
|
<ProtocolUpgradeProposalsListItem
|
||||||
key={proposal.upgradeBlockHeight}
|
key={proposal.upgradeBlockHeight}
|
||||||
|
proposal={proposal}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{closedProposalsView ===
|
||||||
|
ClosedProposalsViewOptions.NetworkGovernance && (
|
||||||
|
<div data-testid="closed-governance-proposals">
|
||||||
|
{sortedProposals.closed
|
||||||
|
.filter(filterPredicate)
|
||||||
|
.map((proposal) => (
|
||||||
|
<ProposalsListItem
|
||||||
|
key={proposal?.id}
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="mb-0" data-testid="no-closed-proposals">
|
||||||
|
{t('noClosedProposals')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
{closedProposalsView ===
|
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
|
||||||
ClosedProposalsViewOptions.NetworkGovernance && (
|
{t('seeRejectedProposals')}
|
||||||
<div data-testid="closed-governance-proposals">
|
</Link>
|
||||||
{sortedProposals.closed
|
</div>
|
||||||
.filter(filterPredicate)
|
|
||||||
.map((proposal) => (
|
|
||||||
<ProposalsListItem
|
|
||||||
key={proposal?.id}
|
|
||||||
proposal={proposal}
|
|
||||||
networkParams={networkParams}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className="mb-0" data-testid="no-closed-proposals">
|
|
||||||
{t('noClosedProposals')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
|
|
||||||
{t('seeRejectedProposals')}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</AsyncRenderer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-29
@@ -1,15 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { Heading } from '../../../../components/heading';
|
import { Heading } from '../../../../components/heading';
|
||||||
import { ProposalsListItem } from '../proposals-list-item';
|
import { ProposalsListItem } from '../proposals-list-item';
|
||||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import {
|
|
||||||
NetworkParams,
|
|
||||||
useNetworkParams,
|
|
||||||
} from '@vegaprotocol/network-parameters';
|
|
||||||
|
|
||||||
interface ProposalsListProps {
|
interface ProposalsListProps {
|
||||||
proposals: Array<ProposalQuery['proposal'] | ProposalFieldsFragment>;
|
proposals: Array<ProposalQuery['proposal'] | ProposalFieldsFragment>;
|
||||||
@@ -17,19 +12,6 @@ interface ProposalsListProps {
|
|||||||
|
|
||||||
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
|
||||||
params: networkParams,
|
|
||||||
loading: networkParamsLoading,
|
|
||||||
error: networkParamsError,
|
|
||||||
} = useNetworkParams([
|
|
||||||
NetworkParams.governance_proposal_market_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
|
||||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
|
||||||
]);
|
|
||||||
const [filterString, setFilterString] = useState('');
|
const [filterString, setFilterString] = useState('');
|
||||||
|
|
||||||
const filterPredicate = (
|
const filterPredicate = (
|
||||||
@@ -39,11 +21,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
|||||||
p?.party?.id?.toString().includes(filterString);
|
p?.party?.id?.toString().includes(filterString);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<>
|
||||||
loading={networkParamsLoading}
|
|
||||||
error={networkParamsError}
|
|
||||||
data={networkParams}
|
|
||||||
>
|
|
||||||
<Heading title={t('pageTitleRejectedProposals')} />
|
<Heading title={t('pageTitleRejectedProposals')} />
|
||||||
<ProposalsListFilter
|
<ProposalsListFilter
|
||||||
filterString={filterString}
|
filterString={filterString}
|
||||||
@@ -53,11 +31,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
|||||||
{proposals.length > 0 ? (
|
{proposals.length > 0 ? (
|
||||||
<ul data-testid="rejected-proposals">
|
<ul data-testid="rejected-proposals">
|
||||||
{proposals.filter(filterPredicate).map((proposal) => (
|
{proposals.filter(filterPredicate).map((proposal) => (
|
||||||
<ProposalsListItem
|
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||||
key={proposal?.id}
|
|
||||||
proposal={proposal}
|
|
||||||
networkParams={networkParams}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
@@ -66,6 +40,6 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</AsyncRenderer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ export const ProtocolUpgradeProposalsListItem = ({
|
|||||||
<Link
|
<Link
|
||||||
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
|
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
|
||||||
proposal.vegaReleaseTag
|
proposal.vegaReleaseTag
|
||||||
)}`}
|
)}/${proposal.upgradeBlockHeight}`}
|
||||||
>
|
>
|
||||||
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './vote-breakdown';
|
||||||
+348
@@ -0,0 +1,348 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
|
import {
|
||||||
|
lastWeek,
|
||||||
|
mockWalletContext,
|
||||||
|
networkParamsQueryMock,
|
||||||
|
nextWeek,
|
||||||
|
} from '../../test-helpers/mocks';
|
||||||
|
import { VoteBreakdown } from './vote-breakdown';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import {
|
||||||
|
generateNoVotes,
|
||||||
|
generateProposal,
|
||||||
|
generateYesVotes,
|
||||||
|
} from '../../test-helpers/generate-proposals';
|
||||||
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { BigNumber } from '../../../../lib/bignumber';
|
||||||
|
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
||||||
|
|
||||||
|
const mockTotalSupply = new BigNumber(100);
|
||||||
|
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||||
|
const fixedTokenValue = 1000000000000000000;
|
||||||
|
|
||||||
|
const mockAppState: AppState = {
|
||||||
|
totalAssociated: new BigNumber('50063005'),
|
||||||
|
decimals: 18,
|
||||||
|
totalSupply: mockTotalSupply,
|
||||||
|
vegaWalletManageOverlay: false,
|
||||||
|
transactionOverlay: false,
|
||||||
|
bannerMessage: '',
|
||||||
|
disconnectNotice: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
useAppState: () => ({
|
||||||
|
appState: mockAppState,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const renderComponent = (
|
||||||
|
proposal: ProposalQuery['proposal'],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||||
|
) =>
|
||||||
|
render(
|
||||||
|
<Router>
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||||
|
<VoteBreakdown proposal={proposal} />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('VoteBreakdown', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(0);
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders majority reached', () => {
|
||||||
|
const yesVotes = 100;
|
||||||
|
const noVotes = 0;
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('token-majority-met')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders majority not reached', () => {
|
||||||
|
const yesVotes = 20;
|
||||||
|
const noVotes = 80;
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('token-majority-not-met')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders participation reached', () => {
|
||||||
|
const yesVotes = 1000;
|
||||||
|
const noVotes = 0;
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('token-participation-met')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders participation not reached', () => {
|
||||||
|
const yesVotes = 0;
|
||||||
|
const noVotes = 0;
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('token-participation-not-met')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
yes: {
|
||||||
|
...generateYesVotes(0),
|
||||||
|
totalEquityLikeShareWeight: '1000',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
...generateNoVotes(0),
|
||||||
|
totalEquityLikeShareWeight: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
|
'Currently expected to pass by liquidity vote'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
yes: {
|
||||||
|
...generateYesVotes(1000, fixedTokenValue),
|
||||||
|
totalEquityLikeShareWeight: '0',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
...generateNoVotes(0, fixedTokenValue),
|
||||||
|
totalEquityLikeShareWeight: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
|
'Currently expected to pass by token vote'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
yes: {
|
||||||
|
...generateYesVotes(0),
|
||||||
|
totalEquityLikeShareWeight: '0',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
...generateNoVotes(0),
|
||||||
|
totalEquityLikeShareWeight: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
|
'Currently expected to fail'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progress bar displays status - token majority', () => {
|
||||||
|
const yesVotes = 80;
|
||||||
|
const noVotes = 20;
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = screen.getByTestId('token-majority-progress');
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
|
||||||
|
expect(style.width).toBe(`${yesVotes}%`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progress bar displays status - token participation', () => {
|
||||||
|
const yesVotes = 40;
|
||||||
|
const noVotes = 20;
|
||||||
|
const totalVotes = yesVotes + noVotes;
|
||||||
|
const totalSupplyValue = mockTotalSupply.toNumber();
|
||||||
|
const expectedProgress = (totalVotes / totalSupplyValue) * 100; // Here it should be 60%
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||||
|
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = screen.getByTestId('token-participation-progress');
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
|
||||||
|
expect(style.width).toBe(`${expectedProgress}%`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progress bar displays status - LP majority', () => {
|
||||||
|
const yesVotesLP = 800;
|
||||||
|
const noVotesLP = 200;
|
||||||
|
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: {
|
||||||
|
...generateYesVotes(0),
|
||||||
|
totalEquityLikeShareWeight: `${yesVotesLP}`,
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
...generateNoVotes(0),
|
||||||
|
totalEquityLikeShareWeight: `${noVotesLP}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = screen.getByTestId('lp-majority-progress');
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
expect(style.width).toBe(`${expectedProgress}%`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progress bar displays status - LP participation', () => {
|
||||||
|
const yesVotesLP = 400;
|
||||||
|
const noVotesLP = 600;
|
||||||
|
const totalVotesLP = yesVotesLP + noVotesLP;
|
||||||
|
const totalLPSupply = 1000;
|
||||||
|
const expectedProgress = (totalVotesLP / totalLPSupply) * 100; // 100%
|
||||||
|
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: {
|
||||||
|
...generateYesVotes(0),
|
||||||
|
totalEquityLikeShareWeight: `${yesVotesLP}`,
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
...generateNoVotes(0),
|
||||||
|
totalEquityLikeShareWeight: `${noVotesLP}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = screen.getByTestId('lp-participation-progress');
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
expect(style.width).toBe(`${expectedProgress}%`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useVoteInformation } from '../../hooks';
|
||||||
|
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||||
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
|
||||||
|
interface VoteBreakdownProps {
|
||||||
|
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VoteProgressProps {
|
||||||
|
percentageFor: BigNumber;
|
||||||
|
colourfulBg?: boolean;
|
||||||
|
testId?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VoteProgress = ({
|
||||||
|
percentageFor,
|
||||||
|
colourfulBg,
|
||||||
|
testId,
|
||||||
|
children,
|
||||||
|
}: VoteProgressProps) => {
|
||||||
|
const containerClasses = classNames(
|
||||||
|
'relative h-10 rounded-md border border-vega-dark-300 overflow-hidden',
|
||||||
|
colourfulBg ? 'bg-vega-pink' : 'bg-vega-dark-400'
|
||||||
|
);
|
||||||
|
|
||||||
|
const progressClasses = classNames(
|
||||||
|
'absolute h-full top-0 left-0',
|
||||||
|
colourfulBg ? 'bg-vega-green' : 'bg-white'
|
||||||
|
);
|
||||||
|
|
||||||
|
const textClasses = classNames(
|
||||||
|
'absolute top-0 left-0 w-full h-full flex items-center justify-start px-3 text-black'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerClasses}>
|
||||||
|
<div
|
||||||
|
className={progressClasses}
|
||||||
|
style={{ width: `${percentageFor}%` }}
|
||||||
|
data-testid={testId}
|
||||||
|
></div>
|
||||||
|
<div className={textClasses}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusProps {
|
||||||
|
reached: boolean;
|
||||||
|
threshold: BigNumber;
|
||||||
|
text: string;
|
||||||
|
testId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid={testId}>
|
||||||
|
{reached ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon name="tick" size={4} />
|
||||||
|
<span>
|
||||||
|
{threshold.toString()}% {text} {t('met')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon name="cross" size={4} />
|
||||||
|
<span>
|
||||||
|
{threshold.toString()}% {text} {t('not met')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||||
|
const {
|
||||||
|
totalTokensPercentage,
|
||||||
|
participationMet,
|
||||||
|
totalTokensVoted,
|
||||||
|
totalLPTokensPercentage,
|
||||||
|
noPercentage,
|
||||||
|
noLPPercentage,
|
||||||
|
yesPercentage,
|
||||||
|
yesLPPercentage,
|
||||||
|
yesTokens,
|
||||||
|
noTokens,
|
||||||
|
yesEquityLikeShareWeight,
|
||||||
|
noEquityLikeShareWeight,
|
||||||
|
totalEquityLikeShareWeight,
|
||||||
|
requiredMajorityPercentage,
|
||||||
|
requiredMajorityLPPercentage,
|
||||||
|
requiredParticipation,
|
||||||
|
requiredParticipationLP,
|
||||||
|
participationLPMet,
|
||||||
|
majorityMet,
|
||||||
|
majorityLPMet,
|
||||||
|
willPassByTokenVote,
|
||||||
|
willPassByLPVote,
|
||||||
|
} = useVoteInformation({ proposal });
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultDP = 2;
|
||||||
|
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||||
|
const isUpdateMarket = proposal?.terms?.change?.__typename === 'UpdateMarket';
|
||||||
|
const participationThresholdProgress = BigNumber.min(
|
||||||
|
totalTokensPercentage.dividedBy(requiredParticipation).multipliedBy(100),
|
||||||
|
new BigNumber(100)
|
||||||
|
);
|
||||||
|
const lpParticipationThresholdProgress =
|
||||||
|
requiredParticipationLP &&
|
||||||
|
BigNumber.min(
|
||||||
|
totalLPTokensPercentage
|
||||||
|
.dividedBy(requiredParticipationLP)
|
||||||
|
.multipliedBy(100),
|
||||||
|
new BigNumber(100)
|
||||||
|
);
|
||||||
|
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||||
|
const updateMarketVotePassMethod = willPassByTokenVote
|
||||||
|
? 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 headingClasses = classNames('mb-2 text-vega-dark-400');
|
||||||
|
const progressDetailsClasses = classNames(
|
||||||
|
'flex justify-between flex-wrap mt-2 text-sm'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6">
|
||||||
|
{isProposalOpen && (
|
||||||
|
<div
|
||||||
|
data-testid="vote-status"
|
||||||
|
className="flex items-center gap-1 mb-2 text-bold"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{willPass ? (
|
||||||
|
<Icon name="tick" size={5} className="text-vega-green" />
|
||||||
|
) : (
|
||||||
|
<Icon name="cross" size={5} className="text-vega-pink" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>{t('currentlySetTo')} </span>
|
||||||
|
{willPass ? (
|
||||||
|
<span>
|
||||||
|
<span className="text-vega-green">{t('pass')}</span>
|
||||||
|
{isUpdateMarket && <span> {updateMarketVotePassMethod}</span>}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-vega-pink">{t('fail')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isUpdateMarket && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
|
||||||
|
<div className={sectionWrapperClasses}>
|
||||||
|
<section
|
||||||
|
className={sectionClasses}
|
||||||
|
data-testid="lp-majority-breakdown"
|
||||||
|
>
|
||||||
|
<VoteProgress
|
||||||
|
percentageFor={yesLPPercentage}
|
||||||
|
colourfulBg={true}
|
||||||
|
testId="lp-majority-progress"
|
||||||
|
>
|
||||||
|
<Status
|
||||||
|
reached={majorityLPMet}
|
||||||
|
threshold={requiredMajorityLPPercentage}
|
||||||
|
text={t('majorityThreshold')}
|
||||||
|
testId={
|
||||||
|
majorityLPMet ? 'lp-majority-met' : 'lp-majority-not-met'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</VoteProgress>
|
||||||
|
|
||||||
|
<div className={progressDetailsClasses}>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||||
|
<Tooltip
|
||||||
|
description={formatNumber(
|
||||||
|
yesEquityLikeShareWeight,
|
||||||
|
defaultDP
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button>
|
||||||
|
{yesEquityLikeShareWeight
|
||||||
|
.dividedBy(toBigNum(10 ** 6, 0))
|
||||||
|
.toFixed(1)}
|
||||||
|
M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
(
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button>{yesLPPercentage.toFixed(0)}%</button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||||
|
<Tooltip
|
||||||
|
description={formatNumber(
|
||||||
|
noEquityLikeShareWeight,
|
||||||
|
defaultDP
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button>
|
||||||
|
{noEquityLikeShareWeight
|
||||||
|
.dividedBy(toBigNum(10 ** 6, 0))
|
||||||
|
.toFixed(1)}
|
||||||
|
M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
(
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button>{noLPPercentage.toFixed(0)}%</button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
className={sectionClasses}
|
||||||
|
data-testid="lp-participation-breakdown"
|
||||||
|
>
|
||||||
|
<VoteProgress
|
||||||
|
percentageFor={
|
||||||
|
lpParticipationThresholdProgress || new BigNumber(0)
|
||||||
|
}
|
||||||
|
testId="lp-participation-progress"
|
||||||
|
>
|
||||||
|
<Status
|
||||||
|
reached={participationLPMet}
|
||||||
|
threshold={requiredParticipationLP || new BigNumber(1)}
|
||||||
|
text={t('participationThreshold')}
|
||||||
|
testId={
|
||||||
|
participationLPMet
|
||||||
|
? 'lp-participation-met'
|
||||||
|
: 'lp-participation-not-met'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</VoteProgress>
|
||||||
|
|
||||||
|
<div className="flex mt-2 text-sm">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('totalLiquidityProviderTokensVoted')}:</span>
|
||||||
|
<Tooltip
|
||||||
|
description={formatNumber(
|
||||||
|
totalEquityLikeShareWeight,
|
||||||
|
defaultDP
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button>
|
||||||
|
{totalEquityLikeShareWeight
|
||||||
|
.dividedBy(toBigNum(10 ** 6, 0))
|
||||||
|
.toFixed(1)}
|
||||||
|
M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
|
||||||
|
<div className={sectionWrapperClasses}>
|
||||||
|
<section
|
||||||
|
className={sectionClasses}
|
||||||
|
data-testid="token-majority-breakdown"
|
||||||
|
>
|
||||||
|
<VoteProgress
|
||||||
|
percentageFor={yesPercentage}
|
||||||
|
colourfulBg={true}
|
||||||
|
testId="token-majority-progress"
|
||||||
|
>
|
||||||
|
<Status
|
||||||
|
reached={majorityMet}
|
||||||
|
threshold={requiredMajorityPercentage}
|
||||||
|
text={t('majorityThreshold')}
|
||||||
|
testId={
|
||||||
|
majorityMet ? 'token-majority-met' : 'token-majority-not-met'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</VoteProgress>
|
||||||
|
|
||||||
|
<div className={progressDetailsClasses}>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('tokenVotesFor')}:</span>
|
||||||
|
<Tooltip description={formatNumber(yesTokens, defaultDP)}>
|
||||||
|
<button data-testid="num-votes-for">
|
||||||
|
{yesTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
(
|
||||||
|
<Tooltip
|
||||||
|
description={<span>{yesPercentage.toFixed(defaultDP)}%</span>}
|
||||||
|
>
|
||||||
|
<button data-testid="votes-for-percentage">
|
||||||
|
{yesPercentage.toFixed(0)}%
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('tokenVotesAgainst')}:</span>
|
||||||
|
<Tooltip description={formatNumber(noTokens, defaultDP)}>
|
||||||
|
<button data-testid="num-votes-against">
|
||||||
|
{noTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
(
|
||||||
|
<Tooltip
|
||||||
|
description={<span>{noPercentage.toFixed(defaultDP)}%</span>}
|
||||||
|
>
|
||||||
|
<button data-testid="votes-against-percentage">
|
||||||
|
{noPercentage.toFixed(0)}%
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
className={sectionClasses}
|
||||||
|
data-testid="token-participation-breakdown"
|
||||||
|
>
|
||||||
|
<VoteProgress
|
||||||
|
percentageFor={participationThresholdProgress}
|
||||||
|
testId="token-participation-progress"
|
||||||
|
>
|
||||||
|
<Status
|
||||||
|
reached={participationMet}
|
||||||
|
threshold={requiredParticipation}
|
||||||
|
text={t('participationThreshold')}
|
||||||
|
testId={
|
||||||
|
participationMet
|
||||||
|
? 'token-participation-met'
|
||||||
|
: 'token-participation-not-met'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</VoteProgress>
|
||||||
|
|
||||||
|
<div className="flex mt-2 text-sm">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>{t('totalTokensVoted')}:</span>
|
||||||
|
<Tooltip description={formatNumber(totalTokensVoted, defaultDP)}>
|
||||||
|
<button data-testid="total-voted">
|
||||||
|
{totalTokensVoted.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span data-testid="total-voted-percentage">
|
||||||
|
({totalTokensPercentage.toFixed(defaultDP)}%)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1 +1 @@
|
|||||||
export { VoteDetails } from './vote-details';
|
export { UserVote } from './user-vote';
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||||
|
import { VoteButtonsContainer } from './vote-buttons';
|
||||||
|
import { SubHeading } from '../../../../components/heading';
|
||||||
|
import type { VoteValue } from '@vegaprotocol/types';
|
||||||
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||||
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import type { VoteState } from './use-user-vote';
|
||||||
|
|
||||||
|
interface UserVoteProps {
|
||||||
|
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||||
|
minVoterBalance: string | null | undefined;
|
||||||
|
spamProtectionMinTokens: string | null | undefined;
|
||||||
|
transaction: VegaTxState | null;
|
||||||
|
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||||
|
dialog: (props: DialogProps) => JSX.Element;
|
||||||
|
voteState: VoteState | null;
|
||||||
|
voteDatetime: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserVote = ({
|
||||||
|
proposal,
|
||||||
|
minVoterBalance,
|
||||||
|
spamProtectionMinTokens,
|
||||||
|
submit,
|
||||||
|
transaction,
|
||||||
|
dialog,
|
||||||
|
voteState,
|
||||||
|
voteDatetime,
|
||||||
|
}: UserVoteProps) => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section data-testid="user-vote">
|
||||||
|
{proposal?.state === ProposalState.STATE_OPEN ? (
|
||||||
|
<SubHeading title={t('castYourVote')} />
|
||||||
|
) : (
|
||||||
|
<SubHeading title={t('yourVote')} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pubKey ? (
|
||||||
|
proposal && (
|
||||||
|
<VoteButtonsContainer
|
||||||
|
voteState={voteState}
|
||||||
|
voteDatetime={voteDatetime}
|
||||||
|
proposalState={proposal.state}
|
||||||
|
proposalId={proposal.id ?? ''}
|
||||||
|
minVoterBalance={minVoterBalance}
|
||||||
|
spamProtectionMinTokens={spamProtectionMinTokens}
|
||||||
|
className="flex"
|
||||||
|
submit={submit}
|
||||||
|
transaction={transaction}
|
||||||
|
dialog={dialog}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="pb-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<Icon name={'info-sign'} />
|
||||||
|
<div>{t('connectAVegaWalletToVote')}</div>
|
||||||
|
</div>
|
||||||
|
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
|
||||||
|
{t('findOutMoreAboutHowToVote')}
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
<ConnectToVega />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -190,14 +190,15 @@ export const VoteButtons = ({
|
|||||||
(voteState === VoteState.Yes || voteState === VoteState.No) && (
|
(voteState === VoteState.Yes || voteState === VoteState.No) && (
|
||||||
<p data-testid="you-voted">
|
<p data-testid="you-voted">
|
||||||
<span>{t('youVoted')}:</span>{' '}
|
<span>{t('youVoted')}:</span>{' '}
|
||||||
<span className="text-white font-bold">
|
<span className="mx-1 text-white font-bold uppercase">
|
||||||
{t(`voteState_${voteState}`)}
|
{t(`voteState_${voteState}`)}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
{voteDatetime ? (
|
{voteDatetime ? (
|
||||||
<span>{format(voteDatetime, DATE_FORMAT_LONG)}. </span>
|
<span>on {format(voteDatetime, DATE_FORMAT_LONG)}. </span>
|
||||||
) : null}
|
) : null}
|
||||||
{proposalVotable ? (
|
{proposalVotable ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
|
className="text-white"
|
||||||
data-testid="change-vote-button"
|
data-testid="change-vote-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setChangeVote(true);
|
setChangeVote(true);
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
|
||||||
import { RoundedWrapper, Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
|
||||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
|
||||||
import { formatNumber } from '../../../../lib/format-number';
|
|
||||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
|
||||||
import { useVoteInformation } from '../../hooks';
|
|
||||||
import { CurrentProposalStatus } from '../current-proposal-status';
|
|
||||||
import { VoteButtonsContainer } from './vote-buttons';
|
|
||||||
import { SubHeading } from '../../../../components/heading';
|
|
||||||
import { ProposalType } from '../proposal/proposal';
|
|
||||||
import type { VoteValue } from '@vegaprotocol/types';
|
|
||||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
|
||||||
import type { VoteState } from './use-user-vote';
|
|
||||||
|
|
||||||
interface VoteDetailsProps {
|
|
||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
|
||||||
minVoterBalance: string | null | undefined;
|
|
||||||
spamProtectionMinTokens: string | null | undefined;
|
|
||||||
proposalType: ProposalType | null;
|
|
||||||
transaction: VegaTxState | null;
|
|
||||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
|
||||||
dialog: (props: DialogProps) => JSX.Element;
|
|
||||||
voteState: VoteState | null;
|
|
||||||
voteDatetime: Date | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VoteDetails = ({
|
|
||||||
proposal,
|
|
||||||
minVoterBalance,
|
|
||||||
spamProtectionMinTokens,
|
|
||||||
proposalType,
|
|
||||||
submit,
|
|
||||||
transaction,
|
|
||||||
dialog,
|
|
||||||
voteState,
|
|
||||||
voteDatetime,
|
|
||||||
}: VoteDetailsProps) => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
|
||||||
const {
|
|
||||||
totalTokensPercentage,
|
|
||||||
participationMet,
|
|
||||||
totalTokensVoted,
|
|
||||||
totalLPTokensPercentage,
|
|
||||||
noPercentage,
|
|
||||||
noLPPercentage,
|
|
||||||
yesPercentage,
|
|
||||||
yesLPPercentage,
|
|
||||||
yesTokens,
|
|
||||||
noTokens,
|
|
||||||
requiredMajorityPercentage,
|
|
||||||
requiredMajorityLPPercentage,
|
|
||||||
requiredParticipation,
|
|
||||||
requiredParticipationLP,
|
|
||||||
participationLPMet,
|
|
||||||
} = useVoteInformation({ proposal });
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const defaultDecimals = 2;
|
|
||||||
const daysLeft = t('daysLeft', {
|
|
||||||
daysLeft: formatDistanceToNow(new Date(proposal?.terms.closingDatetime)),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
|
||||||
<section>
|
|
||||||
<SubHeading title={t('liquidityVotes')} />
|
|
||||||
<p data-testid="liquidity-votes-status">
|
|
||||||
<span>
|
|
||||||
<CurrentProposalStatus proposal={proposal} />
|
|
||||||
</span>
|
|
||||||
{'. '}
|
|
||||||
{proposal?.state === ProposalState.STATE_OPEN ? daysLeft : null}
|
|
||||||
</p>
|
|
||||||
<table className="w-full mb-8">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-vega-green w-[18%] text-left">
|
|
||||||
{t('for')}
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<VoteProgress
|
|
||||||
threshold={requiredMajorityLPPercentage}
|
|
||||||
progress={yesLPPercentage}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
<th className="text-danger w-[18%] text-right">
|
|
||||||
{t('against')}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
className="text-left"
|
|
||||||
data-testid="vote-progress-indicator-percentage-for"
|
|
||||||
>
|
|
||||||
{yesLPPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
<td className="text-center text-white">
|
|
||||||
{t('majorityRequired')}{' '}
|
|
||||||
{requiredMajorityLPPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="text-right"
|
|
||||||
data-testid="vote-progress-indicator-percentage-against"
|
|
||||||
>
|
|
||||||
{noLPPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p className="mb-6">
|
|
||||||
{t('participation')}
|
|
||||||
{': '}
|
|
||||||
{participationLPMet ? (
|
|
||||||
<span className="text-vega-green mx-4">{t('met')}</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-danger mx-4">{t('notMet')}</span>
|
|
||||||
)}{' '}
|
|
||||||
{formatNumber(totalLPTokensPercentage, defaultDecimals)}%
|
|
||||||
<span className="ml-4">
|
|
||||||
{requiredParticipationLP && (
|
|
||||||
<>
|
|
||||||
({formatNumber(requiredParticipationLP, defaultDecimals)}%{' '}
|
|
||||||
{t('governanceRequired')})
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
<section data-testid="votes-table">
|
|
||||||
<SubHeading title={t('tokenVotes')} />
|
|
||||||
<p data-testid="token-votes-status">
|
|
||||||
<span>
|
|
||||||
<CurrentProposalStatus proposal={proposal} />
|
|
||||||
</span>
|
|
||||||
{'. '}
|
|
||||||
{proposal?.state === ProposalState.STATE_OPEN ? daysLeft : null}
|
|
||||||
</p>
|
|
||||||
<table className="w-full mb-4">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-vega-green w-[18%] text-left">{t('for')}</th>
|
|
||||||
<th>
|
|
||||||
<VoteProgress
|
|
||||||
threshold={requiredMajorityPercentage}
|
|
||||||
progress={yesPercentage}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
<th className="text-danger w-[18%] text-right">{t('against')}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
className="text-left"
|
|
||||||
data-testid="vote-progress-indicator-percentage-for"
|
|
||||||
>
|
|
||||||
{yesPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
<td className="text-center text-white">
|
|
||||||
{t('majorityRequired')}{' '}
|
|
||||||
{requiredMajorityPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="text-right"
|
|
||||||
data-testid="vote-progress-indicator-percentage-against"
|
|
||||||
>
|
|
||||||
{noPercentage.toFixed(defaultDecimals)}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td data-testid="vote-progress-indicator-tokens-for">
|
|
||||||
{' '}
|
|
||||||
{formatNumber(yesTokens, defaultDecimals)}{' '}
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td
|
|
||||||
data-testid="vote-progress-indicator-tokens-against"
|
|
||||||
className="text-right"
|
|
||||||
>
|
|
||||||
{formatNumber(noTokens, defaultDecimals)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p className="mb-6">
|
|
||||||
{t('participation')}
|
|
||||||
{': '}
|
|
||||||
{participationMet ? (
|
|
||||||
<span className="text-vega-green mx-4">{t('met')}</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-danger mx-4">{t('notMet')}</span>
|
|
||||||
)}{' '}
|
|
||||||
{formatNumber(totalTokensVoted, defaultDecimals)}{' '}
|
|
||||||
{formatNumber(totalTokensPercentage, defaultDecimals)}%
|
|
||||||
<span className="ml-4">
|
|
||||||
({formatNumber(requiredParticipation, defaultDecimals)}%{' '}
|
|
||||||
{t('governanceRequired')})
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
|
||||||
<p>{t('votingThresholdInfo')}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<section className="mt-10">
|
|
||||||
{proposal?.state === ProposalState.STATE_OPEN ? (
|
|
||||||
<SubHeading title={t('castYourVote')} />
|
|
||||||
) : (
|
|
||||||
<SubHeading title={t('yourVote')} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{pubKey ? (
|
|
||||||
proposal && (
|
|
||||||
<VoteButtonsContainer
|
|
||||||
voteState={voteState}
|
|
||||||
voteDatetime={voteDatetime}
|
|
||||||
proposalState={proposal.state}
|
|
||||||
proposalId={proposal.id ?? ''}
|
|
||||||
minVoterBalance={minVoterBalance}
|
|
||||||
spamProtectionMinTokens={spamProtectionMinTokens}
|
|
||||||
className="flex"
|
|
||||||
submit={submit}
|
|
||||||
transaction={transaction}
|
|
||||||
dialog={dialog}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<RoundedWrapper paddingBottom={true}>
|
|
||||||
<div className="mb-4">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Icon name={'info-sign'} />
|
|
||||||
<div>{t('connectAVegaWalletToVote')}</div>
|
|
||||||
</div>
|
|
||||||
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
|
|
||||||
{t('findOutMoreAboutHowToVote')}
|
|
||||||
</ExternalLink>
|
|
||||||
</div>
|
|
||||||
<ConnectToVega />
|
|
||||||
</RoundedWrapper>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
import {
|
||||||
import { Heading } from '../../../../components/heading';
|
useEnvironment,
|
||||||
|
DocsLinks,
|
||||||
|
ExternalLinks,
|
||||||
|
} from '@vegaprotocol/environment';
|
||||||
|
import { Heading, SubHeading } from '../../../../components/heading';
|
||||||
import {
|
import {
|
||||||
AsyncRenderer,
|
AsyncRenderer,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputError,
|
InputError,
|
||||||
|
RoundedWrapper,
|
||||||
TextArea,
|
TextArea,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { validateJson } from '@vegaprotocol/utils';
|
import { validateJson } from '@vegaprotocol/utils';
|
||||||
@@ -79,15 +84,6 @@ export const ProposeRaw = () => {
|
|||||||
spamProtectionMin={params.spam_protection_proposal_min_tokens}
|
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 && (
|
{VEGA_EXPLORER_URL && (
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{t('MoreProposalsInfo')}{' '}
|
{t('MoreProposalsInfo')}{' '}
|
||||||
@@ -98,12 +94,44 @@ export const ProposeRaw = () => {
|
|||||||
</p>
|
</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">
|
<div data-testid="raw-proposal-form">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<FormGroup
|
<FormGroup label="Valid JSON required" labelFor="proposal-data">
|
||||||
label="Make a proposal by submitting JSON"
|
|
||||||
labelFor="proposal-data"
|
|
||||||
>
|
|
||||||
<TextArea
|
<TextArea
|
||||||
id="proposal-data"
|
id="proposal-data"
|
||||||
className="min-h-[200px]"
|
className="min-h-[200px]"
|
||||||
|
|||||||
+4
-1
@@ -43,7 +43,10 @@ describe('EpochIndividualRewardsTable', () => {
|
|||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<EpochIndividualRewardsTable data={mockData} />
|
<EpochIndividualRewardsTable
|
||||||
|
data={mockData}
|
||||||
|
marketCreationQuantumMultiple={'1000'}
|
||||||
|
/>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
);
|
);
|
||||||
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
|
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
|
||||||
|
|||||||
+3
@@ -8,6 +8,7 @@ import type { EpochIndividualReward } from './generate-epoch-individual-rewards-
|
|||||||
|
|
||||||
interface EpochIndividualRewardsGridProps {
|
interface EpochIndividualRewardsGridProps {
|
||||||
data: EpochIndividualReward;
|
data: EpochIndividualReward;
|
||||||
|
marketCreationQuantumMultiple: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RewardItemProps {
|
interface RewardItemProps {
|
||||||
@@ -69,9 +70,11 @@ const RewardItem = ({
|
|||||||
|
|
||||||
export const EpochIndividualRewardsTable = ({
|
export const EpochIndividualRewardsTable = ({
|
||||||
data,
|
data,
|
||||||
|
marketCreationQuantumMultiple,
|
||||||
}: EpochIndividualRewardsGridProps) => {
|
}: EpochIndividualRewardsGridProps) => {
|
||||||
return (
|
return (
|
||||||
<RewardsTable
|
<RewardsTable
|
||||||
|
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||||
dataTestId="epoch-individual-rewards-table"
|
dataTestId="epoch-individual-rewards-table"
|
||||||
epoch={Number(data.epoch)}
|
epoch={Number(data.epoch)}
|
||||||
>
|
>
|
||||||
|
|||||||
+5
@@ -9,6 +9,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
|||||||
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||||
|
|
||||||
const EPOCHS_PAGE_SIZE = 10;
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ export const EpochIndividualRewards = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const { delegationsPagination } = ENV;
|
const { delegationsPagination } = ENV;
|
||||||
|
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||||
|
'rewards_marketCreationQuantumMultiple'
|
||||||
|
);
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useRewardsQuery({
|
const { data, loading, error, refetch } = useRewardsQuery({
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
@@ -103,6 +107,7 @@ export const EpochIndividualRewards = ({
|
|||||||
{epochIndividualRewardSummaries.map(
|
{epochIndividualRewardSummaries.map(
|
||||||
(epochIndividualRewardSummary) => (
|
(epochIndividualRewardSummary) => (
|
||||||
<EpochIndividualRewardsTable
|
<EpochIndividualRewardsTable
|
||||||
|
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||||
data={epochIndividualRewardSummary}
|
data={epochIndividualRewardSummary}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-1
@@ -66,7 +66,10 @@ describe('EpochTotalRewardsTable', () => {
|
|||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<EpochTotalRewardsTable data={mockData} />
|
<EpochTotalRewardsTable
|
||||||
|
data={mockData}
|
||||||
|
marketCreationQuantumMultiple={'1000'}
|
||||||
|
/>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
);
|
);
|
||||||
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
|
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
|
||||||
|
|||||||
+7
-1
@@ -8,6 +8,7 @@ import type { EpochTotalSummary } from './generate-epoch-total-rewards-list';
|
|||||||
|
|
||||||
interface EpochTotalRewardsGridProps {
|
interface EpochTotalRewardsGridProps {
|
||||||
data: EpochTotalSummary;
|
data: EpochTotalSummary;
|
||||||
|
marketCreationQuantumMultiple: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RewardItemProps {
|
interface RewardItemProps {
|
||||||
@@ -52,9 +53,14 @@ const RewardItem = ({
|
|||||||
|
|
||||||
export const EpochTotalRewardsTable = ({
|
export const EpochTotalRewardsTable = ({
|
||||||
data,
|
data,
|
||||||
|
marketCreationQuantumMultiple,
|
||||||
}: EpochTotalRewardsGridProps) => {
|
}: EpochTotalRewardsGridProps) => {
|
||||||
return (
|
return (
|
||||||
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
|
<RewardsTable
|
||||||
|
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||||
|
dataTestId="epoch-total-rewards-table"
|
||||||
|
epoch={data.epoch}
|
||||||
|
>
|
||||||
{Array.from(data.assetRewards.values()).map(
|
{Array.from(data.assetRewards.values()).map(
|
||||||
({ name, rewards, totalAmount, decimals }, i) => (
|
({ name, rewards, totalAmount, decimals }, i) => (
|
||||||
<div className="contents" key={i}>
|
<div className="contents" key={i}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
|
|||||||
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
||||||
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
||||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||||
|
|
||||||
const EPOCHS_PAGE_SIZE = 10;
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
|||||||
const epochId = Number(currentEpoch.id) - 1;
|
const epochId = Number(currentEpoch.id) - 1;
|
||||||
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||||
|
'rewards_marketCreationQuantumMultiple'
|
||||||
|
);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
@@ -70,7 +74,11 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
|||||||
>
|
>
|
||||||
{Array.from(epochTotalRewardSummaries.values()).map(
|
{Array.from(epochTotalRewardSummaries.values()).map(
|
||||||
(epochTotalSummary, index) => (
|
(epochTotalSummary, index) => (
|
||||||
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
|
<EpochTotalRewardsTable
|
||||||
|
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||||
|
data={epochTotalSummary}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
@@ -133,24 +133,26 @@ export const RewardsPage = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-[360px]">
|
<div className="flex justify-end">
|
||||||
<Toggle
|
<div className="w-full max-w-[360px]">
|
||||||
name="epoch-reward-view-toggle"
|
<Toggle
|
||||||
toggles={[
|
name="epoch-reward-view-toggle"
|
||||||
{
|
toggles={[
|
||||||
label: t('totalDistributed'),
|
{
|
||||||
value: 'total',
|
label: t('totalDistributed'),
|
||||||
},
|
value: 'total',
|
||||||
{
|
},
|
||||||
label: t('earnedByMe'),
|
{
|
||||||
value: 'individual',
|
label: t('earnedByMe'),
|
||||||
},
|
value: 'individual',
|
||||||
]}
|
},
|
||||||
checkedValue={toggleRewardsView}
|
]}
|
||||||
onChange={(e) =>
|
checkedValue={toggleRewardsView}
|
||||||
setToggleRewardsView(e.target.value as RewardsView)
|
onChange={(e) =>
|
||||||
}
|
setToggleRewardsView(e.target.value as RewardsView)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -77,7 +77,11 @@ const ColumnHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ColumnHeaders = () => {
|
const ColumnHeaders = ({
|
||||||
|
marketCreationQuantumMultiple,
|
||||||
|
}: {
|
||||||
|
marketCreationQuantumMultiple: string | null;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="contents">
|
<div className="contents">
|
||||||
@@ -89,7 +93,9 @@ const ColumnHeaders = () => {
|
|||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
key={columnTitle}
|
key={columnTitle}
|
||||||
title={t(columnTitle)}
|
title={t(columnTitle)}
|
||||||
tooltipContent={t(description)}
|
tooltipContent={t(description, {
|
||||||
|
marketCreationQuantumMultiple,
|
||||||
|
})}
|
||||||
className={headerGridItemStyles()}
|
className={headerGridItemStyles()}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -105,6 +111,7 @@ export interface RewardTableProps {
|
|||||||
dataTestId: string;
|
dataTestId: string;
|
||||||
epoch: number;
|
epoch: number;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
marketCreationQuantumMultiple: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewards table children will be the row items. Make sure they contain
|
// Rewards table children will be the row items. Make sure they contain
|
||||||
@@ -113,12 +120,15 @@ export const RewardsTable = ({
|
|||||||
dataTestId,
|
dataTestId,
|
||||||
epoch,
|
epoch,
|
||||||
children,
|
children,
|
||||||
|
marketCreationQuantumMultiple,
|
||||||
}: RewardTableProps) => (
|
}: RewardTableProps) => (
|
||||||
<div data-testid={dataTestId} className="mb-12">
|
<div data-testid={dataTestId} className="mb-12">
|
||||||
<SubHeading title={`EPOCH ${epoch}`} />
|
<SubHeading title={`EPOCH ${epoch}`} />
|
||||||
|
|
||||||
<div className={gridStyles}>
|
<div className={gridStyles}>
|
||||||
<ColumnHeaders />
|
<ColumnHeaders
|
||||||
|
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ const routerConfig = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
|
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag/:proposalBlockHeight`,
|
||||||
element: <LazyProtocolUpgradeProposal />,
|
element: <LazyProtocolUpgradeProposal />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ const Home = ({ name }: RouteChildProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</HomeSection>
|
</HomeSection>
|
||||||
<div className="flex gap-12">
|
<div className="flex justify-between flex-wrap gap-x-12 gap-y-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-[360px]">
|
||||||
<HomeSection>
|
<HomeSection>
|
||||||
<SubHeading title={t('Staking')} />
|
<SubHeading title={t('Staking')} />
|
||||||
<p>
|
<p>
|
||||||
@@ -118,7 +118,7 @@ const Home = ({ name }: RouteChildProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</HomeSection>
|
</HomeSection>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-[360px]">
|
||||||
<HomeSection>
|
<HomeSection>
|
||||||
<SubHeading title={t('Governance')} />
|
<SubHeading title={t('Governance')} />
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-2xl text-white uppercase mb-4;
|
@apply text-2xl text-white uppercase mb-4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,475 +0,0 @@
|
|||||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
|
||||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
|
||||||
import {
|
|
||||||
MarketState,
|
|
||||||
MarketStateMapping,
|
|
||||||
PropertyKeyType,
|
|
||||||
} from '@vegaprotocol/types';
|
|
||||||
import { addDays, subDays } from 'date-fns';
|
|
||||||
import {
|
|
||||||
chainIdQuery,
|
|
||||||
statisticsQuery,
|
|
||||||
createDataConnection,
|
|
||||||
oracleSpecDataConnectionQuery,
|
|
||||||
createMarketFragment,
|
|
||||||
marketsQuery,
|
|
||||||
marketsDataQuery,
|
|
||||||
createMarketsDataFragment,
|
|
||||||
assetQuery,
|
|
||||||
networkParamsQuery,
|
|
||||||
nodeGuardQuery,
|
|
||||||
} from '@vegaprotocol/mock';
|
|
||||||
import {
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
getDateTimeFormat,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
|
|
||||||
describe('Closed markets', { tags: '@smoke' }, () => {
|
|
||||||
const settlementDataProperty = 'settlement-data-property';
|
|
||||||
const settlementDataPropertyKey = {
|
|
||||||
__typename: 'PropertyKey' as const,
|
|
||||||
name: settlementDataProperty,
|
|
||||||
type: PropertyKeyType.TYPE_INTEGER,
|
|
||||||
numberDecimalPlaces: 2,
|
|
||||||
};
|
|
||||||
const settlementDataSourceData: DataSourceDefinition = {
|
|
||||||
sourceType: {
|
|
||||||
sourceType: {
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
__typename: 'Filter',
|
|
||||||
key: settlementDataPropertyKey,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const rowSelector =
|
|
||||||
'[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row';
|
|
||||||
|
|
||||||
const assetsResult = assetQuery();
|
|
||||||
// @ts-ignore asset definitely exists
|
|
||||||
const settlementAsset = assetsResult.assetsConnection.edges[0].node;
|
|
||||||
|
|
||||||
const settledMarket = createMarketFragment({
|
|
||||||
id: '0',
|
|
||||||
state: MarketState.STATE_SETTLED,
|
|
||||||
marketTimestamps: {
|
|
||||||
open: subDays(new Date(), 10).toISOString(),
|
|
||||||
close: subDays(new Date(), 4).toISOString(),
|
|
||||||
},
|
|
||||||
tradableInstrument: {
|
|
||||||
instrument: {
|
|
||||||
product: {
|
|
||||||
dataSourceSpecBinding: {
|
|
||||||
settlementDataProperty,
|
|
||||||
},
|
|
||||||
dataSourceSpecForTradingTermination: {
|
|
||||||
id: 'market-1-trading-termination-oracle-id',
|
|
||||||
},
|
|
||||||
dataSourceSpecForSettlementData: {
|
|
||||||
id: 'market-1-settlement-data-oracle-id',
|
|
||||||
data: settlementDataSourceData,
|
|
||||||
},
|
|
||||||
settlementAsset,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const terminatedMarket = createMarketFragment({
|
|
||||||
id: '1',
|
|
||||||
state: MarketState.STATE_TRADING_TERMINATED,
|
|
||||||
marketTimestamps: {
|
|
||||||
open: subDays(new Date(), 10).toISOString(),
|
|
||||||
close: null, // market
|
|
||||||
},
|
|
||||||
tradableInstrument: {
|
|
||||||
instrument: {
|
|
||||||
metadata: {
|
|
||||||
tags: [
|
|
||||||
`settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
product: {
|
|
||||||
dataSourceSpecBinding: {
|
|
||||||
settlementDataProperty,
|
|
||||||
},
|
|
||||||
dataSourceSpecForSettlementData: {
|
|
||||||
id: 'market-1-settlement-data-oracle-id',
|
|
||||||
data: settlementDataSourceData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const delayedSettledMarket = createMarketFragment({
|
|
||||||
id: '2',
|
|
||||||
state: MarketState.STATE_TRADING_TERMINATED,
|
|
||||||
marketTimestamps: {
|
|
||||||
open: subDays(new Date(), 10).toISOString(),
|
|
||||||
close: null, // market
|
|
||||||
},
|
|
||||||
tradableInstrument: {
|
|
||||||
instrument: {
|
|
||||||
metadata: {
|
|
||||||
tags: [
|
|
||||||
`settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
product: {
|
|
||||||
dataSourceSpecBinding: {
|
|
||||||
settlementDataProperty,
|
|
||||||
},
|
|
||||||
dataSourceSpecForSettlementData: {
|
|
||||||
id: 'market-1-settlement-data-oracle-id',
|
|
||||||
data: settlementDataSourceData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const unknownMarket = createMarketFragment({
|
|
||||||
id: '3',
|
|
||||||
state: MarketState.STATE_SETTLED,
|
|
||||||
});
|
|
||||||
|
|
||||||
const closedMarketsResult = [
|
|
||||||
{
|
|
||||||
node: settledMarket,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: terminatedMarket,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: delayedSettledMarket,
|
|
||||||
},
|
|
||||||
{ node: unknownMarket },
|
|
||||||
{
|
|
||||||
node: createMarketFragment({ id: '4', state: MarketState.STATE_PENDING }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: createMarketFragment({ id: '5', state: MarketState.STATE_ACTIVE }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const settledMarketData = createMarketsDataFragment({
|
|
||||||
market: {
|
|
||||||
id: settledMarket.id,
|
|
||||||
},
|
|
||||||
bestBidPrice: '1000',
|
|
||||||
bestOfferPrice: '2000',
|
|
||||||
markPrice: '1500',
|
|
||||||
});
|
|
||||||
|
|
||||||
const closedMarketsDataResult = [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
data: settledMarketData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
data: createMarketsDataFragment({
|
|
||||||
market: {
|
|
||||||
id: terminatedMarket.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
data: createMarketsDataFragment({
|
|
||||||
market: {
|
|
||||||
id: delayedSettledMarket.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
data: createMarketsDataFragment({
|
|
||||||
market: {
|
|
||||||
id: unknownMarket.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const specDataConnection = createDataConnection();
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.setOnBoardingViewed();
|
|
||||||
cy.mockGQL((req) => {
|
|
||||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
|
||||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
|
||||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
|
||||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
|
||||||
aliasGQLQuery(
|
|
||||||
req,
|
|
||||||
'Markets',
|
|
||||||
marketsQuery({
|
|
||||||
marketsConnection: {
|
|
||||||
edges: closedMarketsResult,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
aliasGQLQuery(
|
|
||||||
req,
|
|
||||||
'MarketsData',
|
|
||||||
marketsDataQuery({
|
|
||||||
marketsConnection: {
|
|
||||||
edges: closedMarketsDataResult,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
aliasGQLQuery(
|
|
||||||
req,
|
|
||||||
'OracleSpecDataConnection',
|
|
||||||
oracleSpecDataConnectionQuery()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.mockSubscription();
|
|
||||||
|
|
||||||
cy.visit('/#/markets/all');
|
|
||||||
cy.get('[data-testid="Closed markets"]').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a settled market', () => {
|
|
||||||
const expectedMarkets = closedMarketsResult.filter((edge) => {
|
|
||||||
return [
|
|
||||||
MarketState.STATE_SETTLED,
|
|
||||||
MarketState.STATE_TRADING_TERMINATED,
|
|
||||||
].includes(edge.node.state);
|
|
||||||
});
|
|
||||||
const product = settledMarket.tradableInstrument.instrument.product;
|
|
||||||
|
|
||||||
// rows should be filtered to only include settled/terminated markets
|
|
||||||
cy.get(rowSelector).should('have.length', expectedMarkets.length);
|
|
||||||
|
|
||||||
// check each column in the first row renders correctly
|
|
||||||
// 6001-MARK-001
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="code"]')
|
|
||||||
.find('[data-testid="market-code"]')
|
|
||||||
.should('have.text', settledMarket.tradableInstrument.instrument.code);
|
|
||||||
|
|
||||||
// 6001-MARK-071
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[title="Future"]')
|
|
||||||
.should('have.text', 'Futr');
|
|
||||||
|
|
||||||
// 6001-MARK-002
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="name"]')
|
|
||||||
.should('have.text', settledMarket.tradableInstrument.instrument.name);
|
|
||||||
|
|
||||||
// 6001-MARK-003
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="state"]')
|
|
||||||
.should('have.text', MarketStateMapping[settledMarket.state]);
|
|
||||||
|
|
||||||
// 6001-MARK-004
|
|
||||||
// 6001-MARK-005
|
|
||||||
// 6001-MARK-009
|
|
||||||
// 6001-MARK-008
|
|
||||||
// 6001-MARK-010
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="settlementDate"]')
|
|
||||||
.find('[data-testid="link"]')
|
|
||||||
.should(($el) => {
|
|
||||||
const href = $el.attr('href');
|
|
||||||
expect(href).to.match(
|
|
||||||
new RegExp(
|
|
||||||
`/oracles/${product.dataSourceSpecForTradingTermination.id}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.should('have.text', '4 days ago')
|
|
||||||
.should(
|
|
||||||
'have.attr',
|
|
||||||
'title',
|
|
||||||
getDateTimeFormat().format(
|
|
||||||
new Date(settledMarket.marketTimestamps.close)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6001-MARK-011
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="bestBidPrice"]')
|
|
||||||
.should(
|
|
||||||
'have.text',
|
|
||||||
addDecimalsFormatNumber(
|
|
||||||
settledMarketData.bestBidPrice,
|
|
||||||
settledMarket.decimalPlaces
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6001-MARK-012
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="bestOfferPrice"]')
|
|
||||||
.should(
|
|
||||||
'have.text',
|
|
||||||
addDecimalsFormatNumber(
|
|
||||||
settledMarketData.bestOfferPrice,
|
|
||||||
settledMarket.decimalPlaces
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6001-MARK-013
|
|
||||||
cy.get(rowSelector).first().find('[col-id="markPrice"]').should(
|
|
||||||
'have.text',
|
|
||||||
|
|
||||||
addDecimalsFormatNumber(
|
|
||||||
settledMarketData.markPrice,
|
|
||||||
settledMarket.decimalPlaces
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6001-MARK-014
|
|
||||||
// 6001-MARK-015
|
|
||||||
// 6001-MARK-016
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="settlementDataOracleId"]')
|
|
||||||
.find('[data-testid="link"]')
|
|
||||||
.should(($el) => {
|
|
||||||
const href = $el.attr('href');
|
|
||||||
expect(href).to.match(
|
|
||||||
new RegExp(`/oracles/${product.dataSourceSpecForSettlementData.id}`)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.should(
|
|
||||||
'have.text',
|
|
||||||
addDecimalsFormatNumber(
|
|
||||||
// @ts-ignore cannot deep un-partial
|
|
||||||
specDataConnection.externalData.data.data[0].value,
|
|
||||||
settlementDataPropertyKey.numberDecimalPlaces
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6001-MARK-018
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="settlementAsset"]')
|
|
||||||
.should('have.text', product.settlementAsset.symbol);
|
|
||||||
|
|
||||||
// 6001-MARK-020
|
|
||||||
cy.get('.ag-pinned-right-cols-container')
|
|
||||||
.find('[col-id="market-actions"]')
|
|
||||||
.first()
|
|
||||||
.find('button svg')
|
|
||||||
.should('exist');
|
|
||||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.find('[col-id="successorMarket"]')
|
|
||||||
.first()
|
|
||||||
.should('have.text', '-');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// test market list for market in terminated state
|
|
||||||
it('renders a terminated market', () => {
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.eq(1)
|
|
||||||
.find('[col-id="state"]')
|
|
||||||
.should('have.text', MarketStateMapping[terminatedMarket.state]);
|
|
||||||
|
|
||||||
// 6001-MARK-006
|
|
||||||
// 6001-MARK-007
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.eq(1)
|
|
||||||
.find('[col-id="settlementDate"]')
|
|
||||||
.find('[data-testid="link"]')
|
|
||||||
.should('have.text', 'Expected in 4 days');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a terminated market which was expected to have settled', () => {
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.eq(2)
|
|
||||||
.find('[col-id="settlementDate"]')
|
|
||||||
.should('have.class', 'text-danger')
|
|
||||||
.find('[data-testid="link"]')
|
|
||||||
.should('have.text', 'Expected 2 days ago');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders terminated market which doesnt have settlement date metadata', () => {
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.eq(3)
|
|
||||||
.find('[col-id="settlementDate"]')
|
|
||||||
.find('[data-testid="link"]')
|
|
||||||
.should('have.text', 'Unknown');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can open asset detail dialog', () => {
|
|
||||||
cy.mockGQL((req) => {
|
|
||||||
aliasGQLQuery(req, 'Asset', assetsResult);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(rowSelector)
|
|
||||||
.first()
|
|
||||||
.find('[col-id="settlementAsset"]')
|
|
||||||
.find('button')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// 6001-MARK-019
|
|
||||||
cy.get('[data-testid="dialog-title"]').should(
|
|
||||||
'have.text',
|
|
||||||
`Asset details - ${settlementAsset.symbol}`
|
|
||||||
);
|
|
||||||
|
|
||||||
cy.get('[data-testid="dialog-close"]').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can open row actions', () => {
|
|
||||||
cy.get('.ag-pinned-right-cols-container')
|
|
||||||
.find('[col-id="market-actions"]')
|
|
||||||
.first()
|
|
||||||
.find('button')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
|
||||||
const dropdownContentItem = '[role="menuitem"]';
|
|
||||||
cy.get(dropdownContent)
|
|
||||||
.find(dropdownContentItem)
|
|
||||||
.eq(0)
|
|
||||||
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
|
|
||||||
.should('have.text', 'Copy Market ID');
|
|
||||||
|
|
||||||
cy.get(dropdownContent)
|
|
||||||
.find(dropdownContentItem)
|
|
||||||
.eq(1)
|
|
||||||
.find('a')
|
|
||||||
.then(($el) => {
|
|
||||||
const href = $el.attr('href');
|
|
||||||
expect(/\/markets\/0/.test(href || '')).to.equal(true);
|
|
||||||
})
|
|
||||||
.should('have.text', 'View on Explorer');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
|
||||||
before(() => {
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setOnBoardingViewed();
|
|
||||||
cy.visit('/#/markets/all');
|
|
||||||
cy.get('[data-testid="Closed markets"]').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can see no markets message', () => {
|
|
||||||
// 6001-MARK-034
|
|
||||||
cy.getByTestId('tab-closed-markets').should('contain.text', 'No markets');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -133,11 +133,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
|||||||
validateMarketDataRow(4, 'Decimals', '5');
|
validateMarketDataRow(4, 'Decimals', '5');
|
||||||
validateMarketDataRow(5, 'Quantum', '1');
|
validateMarketDataRow(5, 'Quantum', '1');
|
||||||
validateMarketDataRow(6, 'Status', 'Enabled');
|
validateMarketDataRow(6, 'Status', 'Enabled');
|
||||||
validateMarketDataRow(
|
validateMarketDataRow(7, 'Contract address', '0x0158…78a4');
|
||||||
7,
|
|
||||||
'Contract address',
|
|
||||||
'0x0158031158Bb4dF2AD02eAA31e8963E84EA978a4'
|
|
||||||
);
|
|
||||||
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
||||||
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
||||||
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
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 = 'middle-mark-price-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,6 +24,9 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
|
cy.getByTestId('deal-ticket-fee-margin-required').within(() => {
|
||||||
|
cy.get('button').click();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('limit order', () => {
|
describe('limit order', () => {
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
// 0004-EWAL-005
|
// 0004-EWAL-005
|
||||||
// 0004-EWAL-006
|
// 0004-EWAL-006
|
||||||
|
|
||||||
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
|
||||||
cy.getByTestId('Deposits').click();
|
cy.getByTestId('Deposits').click();
|
||||||
cy.getByTestId('deposit-button').click();
|
cy.getByTestId('deposit-button').click();
|
||||||
connectEthereumWallet('MetaMask');
|
connectEthereumWallet('MetaMask');
|
||||||
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
|
cy.getByTestId('ethereum-address').should('have.text', '0xEe7D…d94F');
|
||||||
cy.getByTestId('disconnect-ethereum-wallet')
|
cy.getByTestId('disconnect-ethereum-wallet')
|
||||||
.should('have.text', 'Disconnect')
|
.should('have.text', 'Disconnect')
|
||||||
.click();
|
.click();
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
|||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
|||||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
|||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
|||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
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
|
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
NX_APP_VERSION=v0.21.0-core-0.72.14
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=false
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
|||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
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
|
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||||
NX_APP_VERSION=v0.20.19-core-0.71.6
|
NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=false
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
|||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
|||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
|||||||
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||||
|
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=false
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,53 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { titlefy } from '@vegaprotocol/utils';
|
import { titlefy } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
LocalStoragePersistTabs as Tabs,
|
||||||
|
Tab,
|
||||||
|
TradingAnchorButton,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { Markets } from './markets';
|
import { Markets } from './markets';
|
||||||
import { Proposed } from './proposed';
|
import { Proposed } from './proposed';
|
||||||
import { usePageTitleStore } from '../../stores';
|
import { usePageTitleStore } from '../../stores';
|
||||||
import { Closed } from './closed';
|
import { Closed } from './closed';
|
||||||
|
import {
|
||||||
|
DApp,
|
||||||
|
TOKEN_NEW_MARKET_PROPOSAL,
|
||||||
|
useLinks,
|
||||||
|
} from '@vegaprotocol/environment';
|
||||||
|
|
||||||
export const MarketsPage = () => {
|
export const MarketsPage = () => {
|
||||||
const { updateTitle } = usePageTitleStore((store) => ({
|
const { updateTitle } = usePageTitleStore((store) => ({
|
||||||
updateTitle: store.updateTitle,
|
updateTitle: store.updateTitle,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const tokenLink = useLinks(DApp.Token);
|
||||||
|
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateTitle(titlefy(['Markets']));
|
updateTitle(titlefy(['Markets']));
|
||||||
}, [updateTitle]);
|
}, [updateTitle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||||
<div className="h-full my-1 border border-default rounded-sm">
|
<div className="h-full my-1 border rounded-sm border-default">
|
||||||
<Tabs storageKey="console-markets">
|
<Tabs storageKey="console-markets">
|
||||||
<Tab id="open-markets" name={t('Open markets')}>
|
<Tab id="open-markets" name={t('Open markets')}>
|
||||||
<Markets />
|
<Markets />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
<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>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Proposed />
|
<Proposed />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||||
|
|||||||
@@ -1,24 +1,6 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import {
|
|
||||||
DApp,
|
|
||||||
TOKEN_NEW_MARKET_PROPOSAL,
|
|
||||||
useLinks,
|
|
||||||
} from '@vegaprotocol/environment';
|
|
||||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||||
|
|
||||||
export const Proposed = () => {
|
export const Proposed = () => {
|
||||||
const tokenLink = useLinks(DApp.Token);
|
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
|
||||||
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 compact from 'lodash/compact';
|
||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
||||||
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
|
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@@ -12,12 +12,13 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
|||||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||||
import {
|
import {
|
||||||
AsyncRenderer,
|
AsyncRenderer,
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Splash,
|
Splash,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
TradingButton,
|
||||||
|
TradingDropdown,
|
||||||
|
TradingDropdownContent,
|
||||||
|
TradingDropdownItem,
|
||||||
|
TradingDropdownTrigger,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||||
import { PriceChart } from 'pennant';
|
import { PriceChart } from 'pennant';
|
||||||
@@ -150,6 +151,7 @@ const AccountHistoryManager = ({
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
}, [accounts, marketFilterCb]);
|
}, [accounts, marketFilterCb]);
|
||||||
|
|
||||||
const resolveMarket = useCallback(
|
const resolveMarket = useCallback(
|
||||||
(m: Market) => {
|
(m: Market) => {
|
||||||
setMarket(m);
|
setMarket(m);
|
||||||
@@ -174,111 +176,114 @@ const AccountHistoryManager = ({
|
|||||||
}),
|
}),
|
||||||
[pubKey, asset, accountType, range, market?.id]
|
[pubKey, asset, accountType, range, market?.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data } = useAccountHistoryQuery({
|
const { data } = useAccountHistoryQuery({
|
||||||
variables,
|
variables,
|
||||||
skip: !asset || !pubKey,
|
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 (
|
return (
|
||||||
<div className="h-full w-full flex flex-col gap-8">
|
<div className="flex flex-col h-full gap-2">
|
||||||
<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 flex-wrap justify-between px-1 pt-2 gap-2">
|
||||||
<div className="flex items-center gap-4 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
<>
|
<TradingDropdown
|
||||||
{accountTypeMenu}
|
trigger={
|
||||||
{assetsMenu}
|
<TradingDropdownTrigger>
|
||||||
{marketsMenu}
|
<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>
|
</div>
|
||||||
<div className="pt-1 justify-items-end">
|
<div className="justify-items-end">
|
||||||
<Toggle
|
<Toggle
|
||||||
id="account-history-date-range"
|
id="account-history-date-range"
|
||||||
name="account-history-date-range"
|
name="account-history-date-range"
|
||||||
@@ -287,16 +292,19 @@ const AccountHistoryManager = ({
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setRange(e.target.value as keyof typeof DateRange)
|
setRange(e.target.value as keyof typeof DateRange)
|
||||||
}
|
}
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-5/6 px-4">
|
<div className="flex-1">
|
||||||
{asset && (
|
{asset && (
|
||||||
<AccountHistoryChart
|
<div className="h-full">
|
||||||
data={data}
|
<AccountHistoryChart
|
||||||
accountType={accountType}
|
data={data}
|
||||||
asset={asset}
|
accountType={accountType}
|
||||||
/>
|
asset={asset}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ViewType, useSidebar } from '../sidebar';
|
import { ViewType, useSidebar } from '../sidebar';
|
||||||
|
|
||||||
export const AccountsMenu = () => {
|
export const AccountsMenu = () => {
|
||||||
@@ -8,7 +8,6 @@ export const AccountsMenu = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TradingButton
|
<TradingButton
|
||||||
intent={Intent.Primary}
|
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
data-testid="open-transfer"
|
data-testid="open-transfer"
|
||||||
onClick={() => setView({ type: ViewType.Transfer })}
|
onClick={() => setView({ type: ViewType.Transfer })}
|
||||||
@@ -16,7 +15,6 @@ export const AccountsMenu = () => {
|
|||||||
{t('Transfer')}
|
{t('Transfer')}
|
||||||
</TradingButton>
|
</TradingButton>
|
||||||
<TradingButton
|
<TradingButton
|
||||||
intent={Intent.Primary}
|
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
onClick={() => setView({ type: ViewType.Deposit })}
|
onClick={() => setView({ type: ViewType.Deposit })}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ViewType, useSidebar } from '../sidebar';
|
import { ViewType, useSidebar } from '../sidebar';
|
||||||
|
|
||||||
export const DepositsMenu = () => {
|
export const DepositsMenu = () => {
|
||||||
@@ -7,7 +7,6 @@ export const DepositsMenu = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TradingButton
|
<TradingButton
|
||||||
intent={Intent.Primary}
|
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
onClick={() => setView({ type: ViewType.Deposit })}
|
onClick={() => setView({ type: ViewType.Deposit })}
|
||||||
data-testid="deposit-button"
|
data-testid="deposit-button"
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
TradingDropdown,
|
||||||
DropdownMenuCheckboxItem,
|
TradingDropdownCheckboxItem,
|
||||||
DropdownMenuContent,
|
TradingDropdownContent,
|
||||||
DropdownMenuItemIndicator,
|
TradingDropdownItemIndicator,
|
||||||
DropdownMenuTrigger,
|
TradingDropdownTrigger,
|
||||||
VegaIcon,
|
|
||||||
VegaIconNames,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { MarketSelectorButton } from './market-selector-button';
|
||||||
|
|
||||||
type Assets = Array<{ id: string; symbol: string }>;
|
type Assets = Array<{ id: string; symbol: string }>;
|
||||||
|
|
||||||
@@ -25,17 +24,19 @@ export const AssetDropdown = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<TradingDropdown
|
||||||
trigger={
|
trigger={
|
||||||
<DropdownMenuTrigger data-testid="asset-trigger">
|
<TradingDropdownTrigger data-testid="asset-trigger">
|
||||||
<TriggerText assets={assets} checkedAssets={checkedAssets} />
|
<MarketSelectorButton>
|
||||||
</DropdownMenuTrigger>
|
{triggerText({ assets, checkedAssets })}
|
||||||
|
</MarketSelectorButton>
|
||||||
|
</TradingDropdownTrigger>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DropdownMenuContent>
|
<TradingDropdownContent>
|
||||||
{assets?.map((a) => {
|
{assets?.map((a) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuCheckboxItem
|
<TradingDropdownCheckboxItem
|
||||||
key={a.id}
|
key={a.id}
|
||||||
checked={checkedAssets.includes(a.id)}
|
checked={checkedAssets.includes(a.id)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
@@ -46,16 +47,16 @@ export const AssetDropdown = ({
|
|||||||
data-testid={`asset-id-${a.id}`}
|
data-testid={`asset-id-${a.id}`}
|
||||||
>
|
>
|
||||||
{a.symbol}
|
{a.symbol}
|
||||||
<DropdownMenuItemIndicator />
|
<TradingDropdownItemIndicator />
|
||||||
</DropdownMenuCheckboxItem>
|
</TradingDropdownCheckboxItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownMenuContent>
|
</TradingDropdownContent>
|
||||||
</DropdownMenu>
|
</TradingDropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TriggerText = ({
|
const triggerText = ({
|
||||||
assets,
|
assets,
|
||||||
checkedAssets,
|
checkedAssets,
|
||||||
}: {
|
}: {
|
||||||
@@ -72,9 +73,5 @@ const TriggerText = ({
|
|||||||
text = t(`${checkedAssets.length} Assets`);
|
text = t(`${checkedAssets.length} Assets`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return text;
|
||||||
<span className="flex justify-between items-center">
|
|
||||||
{text} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './market-selector';
|
export * from './market-selector';
|
||||||
export * from './market-selector-item';
|
export * from './market-selector-item';
|
||||||
|
export * from './market-selector-button';
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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';
|
||||||
@@ -69,6 +69,7 @@ describe('MarketSelectorItem', () => {
|
|||||||
targetStake: '1000000',
|
targetStake: '1000000',
|
||||||
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||||
priceMonitoringBounds: null,
|
priceMonitoringBounds: null,
|
||||||
|
lastTradedPrice: '100',
|
||||||
};
|
};
|
||||||
|
|
||||||
const candles = [
|
const candles = [
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const MarketSelectorItem = ({
|
|||||||
<div style={style} role="row">
|
<div style={style} role="row">
|
||||||
<Link
|
<Link
|
||||||
to={`/markets/${market.id}`}
|
to={`/markets/${market.id}`}
|
||||||
className={classNames('h-full flex items-center gap-2 px-4', {
|
className={classNames('h-full flex items-center gap-2 mx-2 px-2', {
|
||||||
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
|
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
|
||||||
market.id !== currentMarketId,
|
market.id !== currentMarketId,
|
||||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||||
@@ -94,7 +94,7 @@ const MarketData = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-2/5" role="gridcell">
|
<div className="w-2/5" role="gridcell">
|
||||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
<h3 className="overflow-hidden text-sm text-ellipsis lg:text-base whitespace-nowrap">
|
||||||
{market.tradableInstrument.instrument.code}{' '}
|
{market.tradableInstrument.instrument.code}{' '}
|
||||||
{allProducts && productType && (
|
{allProducts && productType && (
|
||||||
<MarketProductPill productType={productType} />
|
<MarketProductPill productType={productType} />
|
||||||
@@ -107,7 +107,7 @@ const MarketData = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
|
||||||
title={instrument.product.settlementAsset.symbol}
|
title={instrument.product.settlementAsset.symbol}
|
||||||
data-testid="market-selector-price"
|
data-testid="market-selector-price"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
@@ -115,14 +115,14 @@ const MarketData = ({
|
|||||||
{price} {instrument.product.settlementAsset.symbol}
|
{price} {instrument.product.settlementAsset.symbol}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
|
||||||
title={t('24h vol')}
|
title={t('24h vol')}
|
||||||
data-testid="market-selector-volume"
|
data-testid="market-selector-volume"
|
||||||
role="gridcell"
|
role="gridcell"
|
||||||
>
|
>
|
||||||
{volume}
|
{volume}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/5 flex justify-end" role="gridcell">
|
<div className="flex justify-end w-1/5" role="gridcell">
|
||||||
{oneDayCandles && (
|
{oneDayCandles && (
|
||||||
<Sparkline
|
<Sparkline
|
||||||
width={64}
|
width={64}
|
||||||
|
|||||||
@@ -262,9 +262,7 @@ describe('MarketSelector', () => {
|
|||||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||||
const options = screen.getAllByTestId(/sort-item/);
|
const options = screen.getAllByTestId(/sort-item/);
|
||||||
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
||||||
Object.entries(Sort)
|
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
|
||||||
.filter(([key]) => key !== Sort.None)
|
|
||||||
.map(([key]) => SortTypeMapping[key as SortType])
|
|
||||||
);
|
);
|
||||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const MarketSelector = ({
|
|||||||
const [filter, setFilter] = useState<Filter>({
|
const [filter, setFilter] = useState<Filter>({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.All,
|
product: Product.All,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
const allProducts = filter.product === Product.All;
|
const allProducts = filter.product === Product.All;
|
||||||
@@ -52,8 +52,8 @@ export const MarketSelector = ({
|
|||||||
}, [reload]);
|
}, [reload]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="market-selector">
|
<div data-testid="market-selector" className="md:w-[580px]">
|
||||||
<div className="pt-2 px-2 mb-2">
|
<div className="px-2 pt-2 mb-2">
|
||||||
<ProductSelector
|
<ProductSelector
|
||||||
product={filter.product}
|
product={filter.product}
|
||||||
onSelect={(product) => {
|
onSelect={(product) => {
|
||||||
@@ -106,9 +106,6 @@ export const MarketSelector = ({
|
|||||||
currentSort={filter.sort}
|
currentSort={filter.sort}
|
||||||
onSelect={(sort) => {
|
onSelect={(sort) => {
|
||||||
setFilter((curr) => {
|
setFilter((curr) => {
|
||||||
if (curr.sort === sort) {
|
|
||||||
return { ...curr, sort: Sort.None };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...curr,
|
...curr,
|
||||||
sort,
|
sort,
|
||||||
@@ -294,9 +291,9 @@ const List = ({
|
|||||||
|
|
||||||
const Skeleton = () => {
|
const Skeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2 px-2">
|
<div className="px-2 mb-2">
|
||||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
|
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
|
||||||
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
|
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||||
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,11 +33,14 @@ export const ProductSelector = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex mb-2">
|
<div className="flex mb-2">
|
||||||
{Object.keys(Product).map((t) => {
|
{Object.keys(Product).map((t) => {
|
||||||
const classes = classNames('px-3 py-1.5 rounded', {
|
const classes = classNames(
|
||||||
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
'text-sm px-3 py-1.5 rounded hover:text-vega-clight-50 dark:hover:text-vega-cdark-50',
|
||||||
t === product,
|
{
|
||||||
'text-secondary': t !== product,
|
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
||||||
});
|
t === product,
|
||||||
|
'text-secondary': t !== product,
|
||||||
|
}
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
@@ -53,7 +56,7 @@ export const ProductSelector = ({
|
|||||||
})}
|
})}
|
||||||
<Link
|
<Link
|
||||||
to={Routes.MARKETS}
|
to={Routes.MARKETS}
|
||||||
className="flex items-center gap-2 ml-auto"
|
className="flex items-center ml-auto text-sm gap-2"
|
||||||
title={t('See all markets')}
|
title={t('See all markets')}
|
||||||
>
|
>
|
||||||
<span className="underline underline-offset-4">{t('Browse')}</span>
|
<span className="underline underline-offset-4">{t('Browse')}</span>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
TradingDropdown,
|
||||||
DropdownMenuContent,
|
TradingDropdownContent,
|
||||||
DropdownMenuItemIndicator,
|
TradingDropdownItemIndicator,
|
||||||
DropdownMenuRadioGroup,
|
TradingDropdownRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
TradingDropdownRadioItem,
|
||||||
DropdownMenuTrigger,
|
TradingDropdownTrigger,
|
||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { MarketSelectorButton } from './market-selector-button';
|
||||||
|
|
||||||
export const Sort = {
|
export const Sort = {
|
||||||
None: 'None',
|
|
||||||
Gained: 'Gained',
|
Gained: 'Gained',
|
||||||
Lost: 'Lost',
|
Lost: 'Lost',
|
||||||
New: 'New',
|
New: 'New',
|
||||||
@@ -23,17 +22,15 @@ export type SortType = keyof typeof Sort;
|
|||||||
export const SortTypeMapping: {
|
export const SortTypeMapping: {
|
||||||
[key in SortType]: string;
|
[key in SortType]: string;
|
||||||
} = {
|
} = {
|
||||||
[Sort.None]: 'None',
|
[Sort.TopTraded]: 'Top traded',
|
||||||
[Sort.Gained]: 'Top gaining',
|
[Sort.Gained]: 'Top gaining',
|
||||||
[Sort.Lost]: 'Top losing',
|
[Sort.Lost]: 'Top losing',
|
||||||
[Sort.New]: 'New markets',
|
[Sort.New]: 'New markets',
|
||||||
[Sort.TopTraded]: 'Top traded',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortIconMapping: {
|
const SortIconMapping: {
|
||||||
[key in SortType]: VegaIconNames;
|
[key in SortType]: VegaIconNames;
|
||||||
} = {
|
} = {
|
||||||
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
|
|
||||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||||
[Sort.New]: VegaIconNames.STAR,
|
[Sort.New]: VegaIconNames.STAR,
|
||||||
@@ -48,43 +45,38 @@ export const SortDropdown = ({
|
|||||||
onSelect: (sort: SortType) => void;
|
onSelect: (sort: SortType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<TradingDropdown
|
||||||
trigger={
|
trigger={
|
||||||
<DropdownMenuTrigger data-testid="sort-trigger">
|
<TradingDropdownTrigger data-testid="sort-trigger">
|
||||||
<span className="flex justify-between items-center">
|
<MarketSelectorButton>
|
||||||
{currentSort === SortTypeMapping.None
|
{SortTypeMapping[currentSort]}
|
||||||
? t('Sort')
|
</MarketSelectorButton>
|
||||||
: SortTypeMapping[currentSort]}{' '}
|
</TradingDropdownTrigger>
|
||||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
|
||||||
</span>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DropdownMenuContent>
|
<TradingDropdownContent>
|
||||||
<DropdownMenuRadioGroup
|
<TradingDropdownRadioGroup
|
||||||
value={currentSort}
|
value={currentSort}
|
||||||
onValueChange={(value) => onSelect(value as SortType)}
|
onValueChange={(value) => onSelect(value as SortType)}
|
||||||
>
|
>
|
||||||
{Object.keys(Sort)
|
{Object.keys(Sort).map((key) => {
|
||||||
.filter((s) => s !== Sort.None)
|
return (
|
||||||
.map((key) => {
|
<TradingDropdownRadioItem
|
||||||
return (
|
inset
|
||||||
<DropdownMenuRadioItem
|
key={key}
|
||||||
inset
|
value={key}
|
||||||
key={key}
|
data-testid={`sort-item-${key}`}
|
||||||
value={key}
|
>
|
||||||
data-testid={`sort-item-${key}`}
|
<span className="flex gap-2">
|
||||||
>
|
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||||
<span className="flex gap-2">
|
{SortTypeMapping[key as SortType]}
|
||||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
</span>
|
||||||
{SortTypeMapping[key as SortType]}
|
<TradingDropdownItemIndicator />
|
||||||
</span>
|
</TradingDropdownRadioItem>
|
||||||
<DropdownMenuItemIndicator />
|
);
|
||||||
</DropdownMenuRadioItem>
|
})}
|
||||||
);
|
</TradingDropdownRadioGroup>
|
||||||
})}
|
</TradingDropdownContent>
|
||||||
</DropdownMenuRadioGroup>
|
</TradingDropdown>
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user