Compare commits

..
509 changed files with 6317 additions and 11336 deletions
@@ -1,74 +0,0 @@
name: After Release
on:
release:
types: [published]
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: resolve ipfs hashes for release
run: |
echo "Tag name: ${{ github.event.release.tag_name }}"
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
- name: Edit Release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
body: |
---
# Deployments
* https://explorer.vega.xyz
* https://governance.vega.xyz
# IPFS releases
Tye IPFS hash of this release of the Trading app is:
CIDv0: ${{ env.IPFS_V0 }}
CIDv1: ${{ env.IPFS_V1 }}
You can always access the latest IPFS release by visiting [vega.trading](https://vega.trading).
You can also access Trading directly from an IPFS gateway.
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
Your settings are not remembered across different URLs.
IPFS gateways:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/
+3 -3
View File
@@ -5,8 +5,6 @@ on:
branches: branches:
- release/* - release/*
- develop - develop
tags:
- v*
pull_request: pull_request:
types: types:
- opened - opened
@@ -99,13 +97,15 @@ jobs:
- name: See affected apps - name: See affected apps
run: | run: |
echo ">>>> debug" echo ">>>> debug"
echo "NX Version: $nx_version"
echo "NX_BASE: ${{ env.NX_BASE }}" echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}" echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo ">>>> eof debug" echo ">>>> eof debug"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)" affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
echo -n "Affected projects: $affected"
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )" branch_slug="$(echo ${{ github.head_ref || github.ref_name }} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
projects_e2e="" projects_e2e=""
preview_governance="not deployed" preview_governance="not deployed"
preview_trading="not deployed" preview_trading="not deployed"
+1
View File
@@ -33,4 +33,5 @@ jobs:
config: baseUrl=${{ github.event.inputs.url }} config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live env: grepTags=@live
env: env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
project: ${{ fromJSON(inputs.projects) }} project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }} name: ${{ matrix.project }}
runs-on: self-hosted-runner runs-on: self-hosted-runner
timeout-minutes: 60 timeout-minutes: 40
steps: steps:
# Checks if skip cache was requested # Checks if skip cache was requested
- name: Set skip-nx-cache flag - name: Set skip-nx-cache flag
@@ -66,7 +66,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 }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --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 }}
@@ -98,4 +98,4 @@ jobs:
if: ${{ failure() }} if: ${{ failure() }}
with: with:
name: test-report-${{ matrix.project }} name: test-report-${{ matrix.project }}
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports path: frontend-monorepo/apps/trading-e2e/cypress/reports
+35 -84
View File
@@ -30,21 +30,13 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr) - name: Log in to the Container registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with:
# registry: registry.hub.docker.com
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -62,24 +54,30 @@ jobs:
- name: Define variables - name: Define variables
run: | run: |
envName='' envName=''
dockerfile="dist.Dockerfile"
if [[ "${{ github.event_name }}" = "push" ]]; then if [[ "${{ github.event_name }}" = "push" ]]; then
domain="vega.rocks" domain="vega.rocks"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)" envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
domain="vega.community" domain="vega.community"
if [[ "${{ matrix.app }}" = "trading" ]]; then
dockerfile="ipfs.Dockerfile"
fi
fi fi
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1" envName="stagnet1"
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} = "true" ]]; then
envName="mainnet"
fi fi
bucketName="${{ matrix.app }}.${envName}.${domain}" bucketName="${{ matrix.app }}.${envName}.${domain}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
fi fi
nodeVersion=$(cat .nvmrc | head -n 1)
echo ENV_NAME=${envName} >> $GITHUB_ENV echo ENV_NAME=${envName} >> $GITHUB_ENV
echo NODE_VERSION=${nodeVersion} >> $GITHUB_ENV
echo DOCKERFILE=docker/${dockerfile} >> $GITHUB_ENV
- name: Build local dist - name: Build local dist
if: ${{ env.DOCKERFILE != 'docker/ipfs.Dockerfile' }}
run: | run: |
flags="" flags=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
@@ -100,60 +98,62 @@ jobs:
- name: Build and export to local Docker - name: Build and export to local Docker
id: docker_build id: docker_build
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: ${{ env.DOCKERFILE }}
load: true load: true
build-args: | build-args: |
APP=${{ matrix.app }} APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest - name: Image digest
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image - name: Sanity check docker image
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: | run: |
echo "Check ipfs-hash" echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash if [[ "${{ env.DOCKERFILE }}" = "docker/ipfs.Dockerfile" ]]; then
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
echo "List html directory" fi
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr) echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree .'
- name: Copy dist to local filesystem
if: ${{ env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' }}
run: |
docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
docker cp dist:/usr/share/nginx/html dist
echo "check local dist files"
tree dist/html
mv dist/html dist-result
- name: Publish dist as docker image
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
if: ${{ github.event_name == 'pull_request' || (matrix.app == 'trading' && github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') ) }} if: ${{ github.event_name == 'pull_request' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: ${{ env.DOCKERFILE }}
push: true push: true
build-args: | build-args: |
APP=${{ matrix.app }} APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }} ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
- name: Publish dist as docker image (docker hub)
uses: docker/build-push-action@v3
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
vegaprotocol/${{ matrix.app }}:mainnet
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master uses: jakejarvis/s3-sync-action@master
if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') }} if: ${{ github.event_name == 'push' }}
with: with:
args: --acl private --follow-symlinks --delete args: --acl private --follow-symlinks --delete
env: env:
@@ -169,52 +169,3 @@ jobs:
with: with:
labels: ${{ matrix.app }}-preview labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }} number: ${{ github.event.number }}
- name: Trigger fleek deployment
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: |
# display info about app
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Update vega.trading DNS to redirect to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
-77
View File
@@ -1,77 +0,0 @@
name: 'Rollback console'
on:
workflow_dispatch:
inputs:
version:
description: 'Version that should be set on rollback'
required: true
type: string
jobs:
rollback:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Retag mainnet
run: |
docker pull vegaprotocol/trading:${{ inputs.version }}
docker tag vegaprotocol/trading:${{ inputs.version }} vegaprotocol/trading:mainnet
docker push vegaprotocol/trading:mainnet
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
- name: Trigger fleek deployment
run: |
# display info about app
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Update vega.trading DNS to redirect to the new console
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
+1 -2
View File
@@ -2,7 +2,6 @@
# compiled output # compiled output
/dist /dist
/dist-result
/tmp /tmp
/out-tsc /out-tsc
/tools/executors/**/*.js /tools/executors/**/*.js
@@ -49,4 +48,4 @@ cypress.env.json
.next .next
#cypress #cypress
/apps/**/cypress/reports/ /apps/trading-e2e/cypress/reports/
-20
View File
@@ -1,20 +0,0 @@
.PHONY: latest-release
latest-release:
gh release list | head -n 1 | awk '{print $1}'
.PHONY: show-latest-release
show-latest-release:
gh release view `gh release list | head -n 1 | awk '{print $1}'`
.PHONY: recalculate-ipfs
recalculate-ipfs:
echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
docker create --name=dist ${TAG}
docker cp dist:/usr/share/nginx/html dist
docker rm dist
+12 -55
View File
@@ -103,68 +103,25 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 🐋 Hosting a console # Docker & Vegacapsule
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions). ## Docker
In order to run a container on port 3000: The [Dockerfile](./dockerfiles) for running the frontends is pretty basic, merely building the application with the APP arg that is passed in and serving the application from [nginx](./nginx/nginx.conf). The only complexity that exists is that there is a script which allows the passing of run time environment variables to the containers. See configuration below for how to do this.
You can build any of the containers locally with the following command:
```bash
docker build --dockerfile dockerfiles/Dockerfile.cra . --build-arg APP=[YOUR APP] --tag=[TAG]
```
In order to run a container:
```bash ```bash
docker run -p 3000:80 [TAG] docker run -p 3000:80 [TAG]
``` ```
## Build instructions Images ending with `.dist` are to pack locally created transpiled HTML files into nginx container for non-compatible with yarn architectures like M1 Mac
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
### nx build outside the docker
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
```bash
./docker/prepare-dist.sh
```
You can build any of the containers locally with the following command:
```bash
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
```
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for `trading` (console) releases.
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### Verifying ipfs-hash of existing current application version
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
4. Download docker image with the desired release `docker pull $TAG`.
5. Recalculate hash: `make recalculate-ipfs`
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
## Config ## Config
+1 -2
View File
@@ -1,11 +1,10 @@
const { defineConfig } = require('cypress'); const { defineConfig } = require('cypress');
module.exports = defineConfig({ module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter', projectId: 'et4snf',
e2e: { e2e: {
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config); require('@cypress/grep/src/plugin')(config);
return config; return config;
}, },
-1
View File
@@ -15,6 +15,5 @@
import '@vegaprotocol/cypress'; import '@vegaprotocol/cypress';
import './common.functions.js'; import './common.functions.js';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep'; import registerCypressGrep from '@cypress/grep';
registerCypressGrep(); registerCypressGrep();
-1
View File
@@ -13,7 +13,6 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
# App flags # App flags
NX_EXPLORER_ASSETS=1 NX_EXPLORER_ASSETS=1
-1
View File
@@ -2,7 +2,6 @@
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
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest/ NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
+1 -2
View File
@@ -1,2 +1 @@
# .env is stagnet1, so there are no overrides required # .env is stagnet1, so there are no overrides required
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET3":"https://stagnet3.explorer.vega.xyz"}'
+2 -24
View File
@@ -1,18 +1,9 @@
import { import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
AppFailure,
NetworkLoader,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
useInitializeEnv,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider'; import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client'; import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import { router } from './routes/router-config'; import { router } from './routes/router-config';
import { t } from '@vegaprotocol/i18n';
const splashLoading = ( const splashLoading = (
<Splash> <Splash>
@@ -21,23 +12,10 @@ const splashLoading = (
); );
function App() { function App() {
const { VEGA_URL } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useNodeSwitcherStore(
(store) => [store.dialogOpen, store.setDialogOpen]
);
return ( return (
<TendermintWebsocketProvider> <TendermintWebsocketProvider>
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}> <NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard <RouterProvider router={router} fallbackElement={splashLoading} />
skeleton={<div>{t('Loading')}</div>}
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
>
<RouterProvider router={router} fallbackElement={splashLoading} />
</NodeGuard>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
</NetworkLoader> </NetworkLoader>
</TendermintWebsocketProvider> </TendermintWebsocketProvider>
); );
@@ -1,19 +1,13 @@
import { import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment';
useEnvironment,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit'; import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { ENV } from '../../config/env'; import { ENV } from '../../config/env';
export const Footer = () => { export const Footer = () => {
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment(); const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
const setNodeSwitcherOpen = useNodeSwitcherStore( const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
(store) => store.setDialogOpen
);
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo( const showFullFeedbackLabel = useMemo(
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize), () => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
@@ -21,40 +15,46 @@ export const Footer = () => {
); );
return ( return (
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200"> <>
<div className="flex justify-between gap-2 align-middle"> <footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
{GIT_COMMIT_HASH && ( <div className="flex justify-between gap-2 align-middle">
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4"> {GIT_COMMIT_HASH && (
<p data-testid="git-commit-hash"> <div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
{t('Version')}:{' '} <p data-testid="git-commit-hash">
<Link {t('Version')}:{' '}
href={ <Link
GIT_ORIGIN_URL href={
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}` GIT_ORIGIN_URL
: undefined ? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
} : undefined
target={GIT_ORIGIN_URL ? '_blank' : undefined} }
> target={GIT_ORIGIN_URL ? '_blank' : undefined}
{GIT_COMMIT_HASH} >
</Link> {GIT_COMMIT_HASH}
</p> </Link>
</p>
</div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
{t('Change')}
</Link>
</div> </div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4"> <div className="flex pl-2 content-center">
{VEGA_URL && <NodeUrl url={VEGA_URL} />} <ExternalLink href={ENV.addresses.feedback}>
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}> {showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
{t('Change')} </ExternalLink>
</Link> </div>
</div> </div>
</footer>
<div className="flex pl-2 content-center"> <NodeSwitcherDialog
<ExternalLink href={ENV.addresses.feedback}> open={nodeSwitcherOpen}
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')} setOpen={setNodeSwitcherOpen}
</ExternalLink> />
</div> </>
</div>
</footer>
); );
}; };
@@ -3,7 +3,6 @@ import { Header } from './header';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
NetworkSwitcher: () => ( NetworkSwitcher: () => (
<div data-testid="network-switcher">NetworkSwitcher</div> <div data-testid="network-switcher">NetworkSwitcher</div>
), ),
@@ -1,8 +1,8 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
import { import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
InstrumentInfoPanel, InstrumentInfoPanel,
KeyDetailsInfoPanel, KeyDetailsInfoPanel,
LiquidityPriceRangeInfoPanel, LiquidityPriceRangeInfoPanel,
@@ -12,8 +12,8 @@ import {
RiskModelInfoPanel, RiskModelInfoPanel,
RiskParametersInfoPanel, RiskParametersInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/markets'; import { MarketInfoTable } from '@vegaprotocol/market-info';
import type { DataSourceDefinition } from '@vegaprotocol/types'; import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
@@ -1,4 +1,4 @@
import type { MarketFieldsFragment } from '@vegaprotocol/markets'; import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
@@ -1,5 +1,3 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go // https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
export const ErrorCodes = new Map([ export const ErrorCodes = new Map([
[51, 'Transaction failed validation'], [51, 'Transaction failed validation'],
@@ -30,11 +28,7 @@ export const ChainResponseCode = ({
}: ChainResponseCodeProps) => { }: ChainResponseCodeProps) => {
const isSuccess = successCodes.has(code); const isSuccess = successCodes.has(code);
const icon = isSuccess ? ( const icon = isSuccess ? '✅' : '❌';
<Icon name="tick-circle" className="fill-vega-green-550" />
) : (
<Icon name="cross" className="fill-vega-pink-550" />
);
const label = ErrorCodes.get(code) || 'Unknown response code'; const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245 // Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
@@ -42,7 +36,7 @@ export const ChainResponseCode = ({
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error; error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return ( return (
<div title={`Response code: ${code} - ${label}`} className=" inline-block"> <div title={`Response code: ${code} - ${label}`} className="inline-block">
<span <span
className="mr-2" className="mr-2"
aria-label={isSuccess ? 'Success' : 'Warning'} aria-label={isSuccess ? 'Success' : 'Warning'}
@@ -4,7 +4,6 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import ProposalLink from '../../links/proposal-link/proposal-link'; import ProposalLink from '../../links/proposal-link/proposal-link';
import { VoteIcon } from '../../vote-icon/vote-icon';
interface TxProposalVoteProps { interface TxProposalVoteProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -31,22 +30,27 @@ export const TxProposalVote = ({
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
const vote = txData.command.voteSubmission.value === 'VALUE_YES'; const vote = txData.command.voteSubmission.value ? '👍' : '👎';
return ( return (
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Proposal details')}</TableCell> <TableCell>{t('Proposal details')}</TableCell>
<TableCell> <TableCell>
<ProposalLink id={txData.command.voteSubmission.proposalId} /> <ProposalLink id={txData.command.voteSubmission.proposalId} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Vote')}</TableCell> <TableCell>{t('Vote')}</TableCell>
<TableCell> <TableCell>{vote}</TableCell>
<VoteIcon vote={vote} />
</TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
); );
@@ -1,6 +1,5 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
interface TxOrderTypeProps { interface TxOrderTypeProps {
orderType: string; orderType: string;
@@ -138,15 +137,12 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
let type = displayString[orderType] || orderType; let type = displayString[orderType] || orderType;
let colours = let colours =
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250'; 'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-150';
// This will get unwieldy and should probably produce a different colour of tag // This will get unwieldy and should probably produce a different colour of tag
if (type === 'Chain Event' && !!command?.chainEvent) { if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent); type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink'; colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') {
colours =
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
} else if (type === 'Proposal' || type === 'Governance Proposal') { } else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) { if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission); type = getLabelForProposal(command.proposalSubmission);
@@ -154,16 +150,6 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
if (type === 'Vote on Proposal') {
return (
<VoteIcon
vote={command?.voteSubmission?.value === 'VALUE_YES'}
yesText="Proposal vote"
noText="Proposal vote"
/>
);
}
if (type === 'Vote on Proposal' || type === 'Vote Submission') { if (type === 'Vote on Proposal' || type === 'Vote Submission') {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
@@ -98,6 +98,6 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success'); expect(screen.getByTestId('tx-success')).toHaveTextContent('Success: ✅');
}); });
}); });
@@ -31,7 +31,7 @@ export const TxsInfiniteListItem = ({
return ( return (
<div <div
data-testid="transaction-row" data-testid="transaction-row"
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10" className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
> >
<div <div
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm col-span-10 md:col-span-3 leading-none"
@@ -83,7 +83,7 @@ export const TxsInfiniteListItem = ({
data-testid="tx-success" data-testid="tx-success"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="md:hidden uppercase text-vega-dark-300">
Success&nbsp; Success:&nbsp;
</span> </span>
{isNumber(code) ? ( {isNumber(code) ? (
<ChainResponseCode code={code} hideLabel={true} /> <ChainResponseCode code={code} hideLabel={true} />
@@ -1,37 +0,0 @@
import { render } from '@testing-library/react';
import { VoteIcon } from './vote-icon';
describe('Vote TX icon', () => {
it('should use the text For by default for yes votes', () => {
const yes = render(<VoteIcon vote={true} />);
expect(yes.getByTestId('label')).toHaveTextContent('For');
});
it('should use the yesText for yes votes if specified', () => {
const yes = render(<VoteIcon vote={true} yesText="Test" />);
expect(yes.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the tick icon for yes votes', () => {
const no = render(<VoteIcon vote={true} />);
expect(no.getByRole('img')).toHaveAttribute(
'aria-label',
'tick-circle icon'
);
});
it('should use the text Against by default for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByTestId('label')).toHaveTextContent('Against');
});
it('should use the noText for no votes if specified', () => {
const no = render(<VoteIcon vote={false} noText="Test" />);
expect(no.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the delete icon for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByRole('img')).toHaveAttribute('aria-label', 'delete icon');
});
});
@@ -1,40 +0,0 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
export interface VoteIconProps {
// True is a yes vote, false is undefined or no vorte
vote: boolean;
// Defaults to 'For', but can be any text
yesText?: string;
// Defaults to 'Against', but can be any text
noText?: string;
}
/**
* Displays a lozenge with an icon representing the way a user voted for a proposal.
* The yes and no text can be overridden
*
* @returns
*/
export function VoteIcon({
vote,
yesText = 'For',
noText = 'Against',
}: VoteIconProps) {
const label = vote ? yesText : noText;
const bg = vote ? 'bg-vega-green-550' : 'bg-vega-pink-550';
const icon: IconName = vote ? 'tick-circle' : 'delete';
const fill = vote ? 'vega-green-300' : 'vega-pink-300';
const text = vote ? 'vega-green-200' : 'vega-pink-200';
return (
<div
className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`}
>
<Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} />
<span className={`text-base text-${text}`} data-testid="label">
{label}
</span>
</div>
);
}
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoWithDataProvider } from '@vegaprotocol/markets'; import { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
import { PageTitle } from '../../components/page-helpers/page-title'; import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => { export const MarketPage = () => {
@@ -1,6 +1,6 @@
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 { marketsProvider } from '@vegaprotocol/markets'; import { marketsProvider } from '@vegaprotocol/market-list';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
+5 -7
View File
@@ -32,9 +32,9 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px); line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
} }
.vega-ag-grid .ag-row, .vega-ag-grid .ag-row {
.vega-ag-grid .ag-cell { border-width: 1px 0;
border-width: 0; border-bottom: 1px solid transparent;
} }
/* Light variables */ /* Light variables */
@@ -46,6 +46,7 @@
--ag-header-column-separator-color: theme(colors.neutral[300]); --ag-header-column-separator-color: theme(colors.neutral[300]);
--ag-row-border-color: theme(colors.white); --ag-row-border-color: theme(colors.white);
--ag-row-hover-color: theme(colors.neutral[100]); --ag-row-hover-color: theme(colors.neutral[100]);
--ag-font-size: 12px;
} }
/* Dark variables */ /* Dark variables */
@@ -57,8 +58,5 @@
--ag-header-column-separator-color: theme(colors.neutral[600]); --ag-header-column-separator-color: theme(colors.neutral[600]);
--ag-row-border-color: theme(colors.black); --ag-row-border-color: theme(colors.black);
--ag-row-hover-color: theme(colors.neutral[800]); --ag-row-hover-color: theme(colors.neutral[800]);
} --ag-font-size: 12px;
.voteicon svg {
vertical-align: baseline;
} }
+1 -2
View File
@@ -1,11 +1,10 @@
const { defineConfig } = require('cypress'); const { defineConfig } = require('cypress');
module.exports = defineConfig({ module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter', projectId: 'et4snf',
e2e: { e2e: {
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config); require('@cypress/grep/src/plugin')(config);
return config; return config;
}, },
@@ -7,7 +7,6 @@ import {
waitForSpinner, waitForSpinner,
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
clickOnValidatorFromList, clickOnValidatorFromList,
@@ -68,7 +67,6 @@ context(
'teardown wallet & drill into a specific validator', 'teardown wallet & drill into a specific validator',
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff();
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.connectVegaWallet(); cy.connectVegaWallet();
@@ -254,10 +252,10 @@ context(
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId(stakeValidatorListStakePercentage).should( cy.getByTestId(stakeValidatorListStakePercentage).should(
'have.text', 'have.text',
'50.02%' '100%'
); );
navigateTo(navigation.validators); navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%'); validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
} }
); );
@@ -109,7 +109,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-proposal-status').should( cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text', 'have.text',
'Approved by validators ' 'Approved '
); );
}); });
}); });
@@ -136,7 +136,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-state').should( cy.getByTestId('protocol-upgrade-state').should(
'have.text', 'have.text',
'Approved by validators' 'Approved'
); );
cy.getByTestId('protocol-upgrade-release-tag').should( cy.getByTestId('protocol-upgrade-release-tag').should(
'have.text', 'have.text',
@@ -63,6 +63,7 @@ context(
}); });
it('should have option to go to last and newest page', function () { it('should have option to go to last and newest page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('goto-last-page').click(); cy.getByTestId('goto-last-page').click();
cy.getByTestId('epoch-total-rewards-table') cy.getByTestId('epoch-total-rewards-table')
.last() .last()
@@ -29,7 +29,7 @@ const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]'; const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]'; const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]'; const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/; const stakeNumberRegex = /^\d*\.?\d*$/;
context('Validators Page - verify elements on page', function () { context('Validators Page - verify elements on page', function () {
before('navigate to validators page', function () { before('navigate to validators page', function () {
@@ -84,13 +84,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(stakedByOperatorToolTip) cy.get(stakedByOperatorToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by operator: 3,000.00'); .should('contain', 'Staked by operator: 0.00');
cy.get(stakedByDelegatesToolTip) cy.get(stakedByDelegatesToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by delegates: 0.00'); .should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip) cy.get(totalStakedToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total stake: 3,000.00'); .should('contain', 'Total stake: 0.00');
}); });
it('Should be able to see validator normalised voting power', function () { it('Should be able to see validator normalised voting power', function () {
@@ -106,10 +106,10 @@ context('Validators Page - verify elements on page', function () {
cy.get(unnormalisedVotingPowerToolTip) cy.get(unnormalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%'); .should('contain', 'Unnormalised voting power: 0.00%');
cy.get(normalisedVotingPowerToolTip) cy.get(normalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Normalised voting power: 50.00%'); .should('contain', 'Normalised voting power: 0.10%');
}); });
// 2002-SINC-018 // 2002-SINC-018
@@ -126,13 +126,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(performancePenaltyToolTip) cy.get(performancePenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Performance penalty: 0.00%'); .should('contain', 'Performance penalty: 100.00%');
cy.get(overstakedPenaltyToolTip) cy.get(overstakedPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886 .should('contain', 'Overstaked penalty:'); // value not asserted due to #2886
cy.get(totalPenaltyToolTip) cy.get(totalPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total penalties: 60.00%'); .should('contain', 'Total penalties: 0.00%');
}); });
it('Should be able to see validator pending stake', function () { it('Should be able to see validator pending stake', function () {
-3
View File
@@ -8,7 +8,6 @@ import './wallet-eth.functions.ts';
import './wallet-teardown.functions.ts'; import './wallet-teardown.functions.ts';
import './wallet-vega.functions.ts'; import './wallet-vega.functions.ts';
import './proposal.functions.ts'; import './proposal.functions.ts';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep'; import registerCypressGrep from '@cypress/grep';
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock'; import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
@@ -34,6 +33,4 @@ before(() => {
aliasGQLQuery(req, 'ChainId', chainIdQuery()); aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery()); aliasGQLQuery(req, 'Statistics', statisticsQuery());
}); });
// Self stake validators so they are displayed
cy.validatorsSelfDelegate();
}); });
@@ -3,6 +3,7 @@ import {
StakingBridge, StakingBridge,
Token, Token,
TokenVesting, TokenVesting,
TokenFaucetable,
CollateralBridge, CollateralBridge,
} from '@vegaprotocol/smart-contracts'; } from '@vegaprotocol/smart-contracts';
import { ethers, Wallet } from 'ethers'; import { ethers, Wallet } from 'ethers';
@@ -40,7 +41,7 @@ export async function depositAsset(
decimalPlaces: number decimalPlaces: number
) { ) {
// Approve asset // Approve asset
const faucet = new Token(assetEthAddress, signer); const faucet = new TokenFaucetable(assetEthAddress, signer);
cy.wrap( cy.wrap(
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)), faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
{ {
@@ -61,7 +62,7 @@ export async function depositAsset(
} }
export async function faucetAsset(assetEthAddress: string) { export async function faucetAsset(assetEthAddress: string) {
const faucet = new Token(assetEthAddress, signer); const faucet = new TokenFaucetable(assetEthAddress, signer);
await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset'); await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset');
} }
-6
View File
@@ -1,6 +0,0 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;
+3 -9
View File
@@ -58,15 +58,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
token.decimals(), token.decimals(),
]); ]);
const totalSupply = toBigNum(supply.toString(), decimals); const totalSupply = toBigNum(supply, decimals);
const totalWallet = toBigNum( const totalWallet = toBigNum(totalAssociatedWallet, decimals);
totalAssociatedWallet.toString(), const totalVesting = toBigNum(totalAssociatedVesting, decimals);
decimals
);
const totalVesting = toBigNum(
totalAssociatedVesting.toString(),
decimals
);
appDispatch({ appDispatch({
type: AppStateActionType.SET_TOKEN, type: AppStateActionType.SET_TOKEN,
+29 -94
View File
@@ -2,6 +2,7 @@ import './i18n';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader'; import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info'; import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -16,7 +17,6 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
import { ContractsProvider } from './contexts/contracts/contracts-provider'; import { ContractsProvider } from './contexts/contracts/contracts-provider';
import { AppRouter } from './routes'; import { AppRouter } from './routes';
import type { EthereumConfig } from '@vegaprotocol/web3'; import type { EthereumConfig } from '@vegaprotocol/web3';
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
import { import {
createConnectors, createConnectors,
useEthTransactionManager, useEthTransactionManager,
@@ -37,14 +37,10 @@ import {
useEnvironment, useEnvironment,
NetworkLoader, NetworkLoader,
useInitializeEnv, useInitializeEnv,
NodeGuard,
AppFailure,
NodeSwitcherDialog,
useNodeSwitcherStore,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { ENV } from './config'; import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws'; import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader'; import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager'; import { ToastsManager } from './toasts-manager';
import { import {
@@ -52,8 +48,6 @@ import {
TELEMETRY_ON, TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog'; } from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers'; import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next';
import { isPartyNotFoundError } from './lib/party';
const cache: InMemoryCacheConfig = { const cache: InMemoryCacheConfig = {
typePolicies: { typePolicies: {
@@ -159,8 +153,7 @@ const Web3Container = ({
<InitializeHandlers /> <InitializeHandlers />
<VegaWalletDialogs /> <VegaWalletDialogs />
<TransactionModal /> <TransactionModal />
<CreateWithdrawalDialog /> <WithdrawalDialog />
<WithdrawalApprovalDialogContainer />
<TelemetryDialog /> <TelemetryDialog />
</> </>
</BalanceManager> </BalanceManager>
@@ -186,82 +179,32 @@ const ScrollToTop = () => {
return null; return null;
}; };
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => { const AppContainer = () => {
const { config, loading, error } = useEthereumConfig(); const { config, loading, error } = useEthereumConfig();
const { const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH, ETHEREUM_PROVIDER_URL } =
VEGA_ENV, useEnvironment();
VEGA_URL,
GIT_COMMIT_HASH,
GIT_BRANCH,
ETHEREUM_PROVIDER_URL,
} = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON); const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
const { t } = useTranslation();
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
store.dialogOpen,
store.setDialogOpen,
]);
useEffect(() => { useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') { if (ENV.dsn && telemetryOn) {
Sentry.init({ Sentry.init({
dsn: ENV.dsn, dsn: ENV.dsn,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 0.1, tracesSampleRate: 0.1,
enabled: true, enabled: true,
environment: VEGA_ENV, environment: VEGA_ENV,
release: GIT_COMMIT_HASH, release: GIT_COMMIT_HASH,
beforeSend(event, hint) { beforeSend(event) {
const error = hint?.originalException; if (event.request?.url?.includes('/claim?')) {
const errorIsString = typeof error === 'string'; return {
const errorIsObject = error instanceof Error; ...event,
const requestUrl = event.request?.url; request: {
const transaction = event.transaction; ...event.request,
url: event.request?.url.split('?')[0],
if ( },
(errorIsString && isPartyNotFoundError({ message: error })) || };
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
} }
return event;
const updatedRequest =
requestUrl && requestUrl.includes('/claim?')
? { ...event.request, url: removeQueryParams(requestUrl) }
: event.request;
const updatedTransaction =
transaction && transaction.includes('/claim?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/claim?')
) {
return {
...breadcrumb,
data: {
...breadcrumb.data,
to: removeQueryParams(breadcrumb.data.to),
},
};
}
return breadcrumb;
});
return {
...event,
request: updatedRequest,
transaction: updatedTransaction,
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
};
}, },
}); });
Sentry.setTag('branch', GIT_BRANCH); Sentry.setTag('branch', GIT_BRANCH);
@@ -276,29 +219,21 @@ const AppContainer = () => {
<ScrollToTop /> <ScrollToTop />
<AppStateProvider> <AppStateProvider>
<div className="grid min-h-full text-white"> <div className="grid min-h-full text-white">
<NodeGuard <AsyncRenderer<EthereumConfig | null>
skeleton={<div>{t('Loading')}</div>} loading={loading}
failure={ data={config}
<AppFailure title={t('NodeUnsuitable', { url: VEGA_URL })} /> error={error}
render={(cnf) =>
cnf && (
<Web3Container
chainId={Number(cnf.chain_id)}
providerUrl={ETHEREUM_PROVIDER_URL}
/>
)
} }
> />
<AsyncRenderer<EthereumConfig | null>
loading={loading}
data={config}
error={error}
render={(cnf) =>
cnf && (
<Web3Container
chainId={Number(cnf.chain_id)}
providerUrl={ETHEREUM_PROVIDER_URL}
/>
)
}
/>
</NodeGuard>
</div> </div>
</AppStateProvider> </AppStateProvider>
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
</Router> </Router>
); );
}; };
@@ -1,14 +1,23 @@
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
export const EthConnectPrompt = () => { export const EthConnectPrompt = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { open } = useWeb3ConnectStore(); const { appDispatch } = useAppState();
return ( return (
<Button <Button
variant="default" variant="default"
onClick={open} onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
fill={true} fill={true}
data-testid="connect-to-eth-btn" data-testid="connect-to-eth-btn"
> >
@@ -2,7 +2,10 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useWeb3ConnectStore } from '@vegaprotocol/web3'; import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
export const EthWalletContainer = ({ export const EthWalletContainer = ({
children, children,
@@ -11,12 +14,21 @@ export const EthWalletContainer = ({
}) => { }) => {
const { account } = useWeb3React(); const { account } = useWeb3React();
const { t } = useTranslation(); const { t } = useTranslation();
const { open } = useWeb3ConnectStore(); const { appDispatch } = useAppState();
if (!account) { if (!account) {
return ( return (
<div className="w-full text-center"> <div className="w-full text-center">
<Button onClick={open}>{t('connectEthWallet')}</Button> <Button
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
>
{t('connectEthWallet')}
</Button>
</div> </div>
); );
} }
@@ -27,11 +27,7 @@ import {
import { Loader } from '@vegaprotocol/ui-toolkit'; import { Loader } from '@vegaprotocol/ui-toolkit';
import colors from 'tailwindcss/colors'; import colors from 'tailwindcss/colors';
import { useBalances } from '../../lib/balances/balances-store'; import { useBalances } from '../../lib/balances/balances-store';
import { import { useEthereumConfig, useWeb3Disconnect } from '@vegaprotocol/web3';
useEthereumConfig,
useWeb3ConnectStore,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
import { getChainName } from '@vegaprotocol/web3'; import { getChainName } from '@vegaprotocol/web3';
const removeLeadingAddressSymbol = (key: string) => { const removeLeadingAddressSymbol = (key: string) => {
@@ -193,7 +189,6 @@ export const EthWallet = () => {
const pendingTxs = usePendingTransactions(); const pendingTxs = usePendingTransactions();
const disconnect = useWeb3Disconnect(connector); const disconnect = useWeb3Disconnect(connector);
const { config } = useEthereumConfig(); const { config } = useEthereumConfig();
const { open } = useWeb3ConnectStore();
return ( return (
<WalletCard> <WalletCard>
@@ -238,7 +233,12 @@ export const EthWallet = () => {
) : ( ) : (
<Button <Button
fill={true} fill={true}
onClick={open} onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
data-testid="connect-to-eth-wallet-button" data-testid="connect-to-eth-wallet-button"
> >
{t('connectEthWalletToAssociate')} {t('connectEthWalletToAssociate')}
@@ -1,6 +1,6 @@
import { Link } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks } from '@vegaprotocol/utils';
export const DownloadWalletPrompt = () => { export const DownloadWalletPrompt = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -26,7 +26,6 @@ import type {
WalletDelegationFieldsFragment, WalletDelegationFieldsFragment,
} from './__generated__/Delegations'; } from './__generated__/Delegations';
import { DelegationsDocument } from './__generated__/Delegations'; import { DelegationsDocument } from './__generated__/Delegations';
import { isPartyNotFoundError } from '../../lib/party';
export const usePollForDelegations = () => { export const usePollForDelegations = () => {
const { token: vegaToken } = useContracts(); const { token: vegaToken } = useContracts();
@@ -76,7 +75,6 @@ export const usePollForDelegations = () => {
}) })
.then((res) => { .then((res) => {
if (!mounted) return; if (!mounted) return;
const canonisedDelegations = removePaginationWrapper( const canonisedDelegations = removePaginationWrapper(
res.data.party?.delegationsConnection?.edges res.data.party?.delegationsConnection?.edges
); );
@@ -205,15 +203,6 @@ export const usePollForDelegations = () => {
setDelegatedNodes(delegatedAmounts); setDelegatedNodes(delegatedAmounts);
}) })
.catch((err: Error) => { .catch((err: Error) => {
// if party isn't found, dont log to Sentry, just clear state as, user
// will not have any delagations or accounts
if (isPartyNotFoundError(err)) {
setDelegations([]);
setAccounts([]);
setDelegatedNodes([]);
setCurrentStakeAvailable(new BigNumber(0));
return;
}
Sentry.captureException(err); Sentry.captureException(err);
// If query fails stop interval. Its almost certain that the query // If query fails stop interval. Its almost certain that the query
// will just continue to fail // will just continue to fail
@@ -25,26 +25,20 @@ export function Web3Connector({
connectors, connectors,
chainId, chainId,
}: Web3ConnectorProps) { }: Web3ConnectorProps) {
const { open, close, isOpen } = useWeb3ConnectStore(); const { appState, appDispatch } = useAppState();
const setDialogOpen = useCallback( const setDialogOpen = useCallback(
(isOpen: boolean) => { (isOpen: boolean) => {
if (isOpen) { appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
open();
} else {
close();
}
}, },
[open, close] [appDispatch]
); );
const appChainId = Number(chainId); const appChainId = Number(chainId);
return ( return (
<> <>
<Web3Content appChainId={appChainId}>{children}</Web3Content> <Web3Content appChainId={appChainId}>{children}</Web3Content>
<Web3ConnectDialog <Web3ConnectDialog
connectors={connectors} connectors={connectors}
dialogOpen={isOpen} dialogOpen={appState.ethConnectOverlay}
setDialogOpen={setDialogOpen} setDialogOpen={setDialogOpen}
desiredChainId={appChainId} desiredChainId={appChainId}
/> />
@@ -34,6 +34,9 @@ export interface AppState {
/** Whether or not the manage VEGA wallet overlay is open */ /** Whether or not the manage VEGA wallet overlay is open */
vegaWalletManageOverlay: boolean; vegaWalletManageOverlay: boolean;
/** Whether or not the connect to Ethereum wallet overlay is open */
ethConnectOverlay: boolean;
/** Whether or not the transaction modal is open */ /** Whether or not the transaction modal is open */
transactionOverlay: boolean; transactionOverlay: boolean;
/** /**
@@ -54,6 +57,7 @@ export enum AppStateActionType {
REFRESH_BALANCES, REFRESH_BALANCES,
SET_VEGA_WALLET_OVERLAY, SET_VEGA_WALLET_OVERLAY,
SET_VEGA_WALLET_MANAGE_OVERLAY, SET_VEGA_WALLET_MANAGE_OVERLAY,
SET_ETH_WALLET_OVERLAY,
SET_DRAWER, SET_DRAWER,
REFRESH_ASSOCIATED_BALANCES, REFRESH_ASSOCIATED_BALANCES,
SET_ASSOCIATION_BREAKDOWN, SET_ASSOCIATION_BREAKDOWN,
@@ -77,6 +81,10 @@ export type AppStateAction =
type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY; type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY;
isOpen: boolean; isOpen: boolean;
} }
| {
type: AppStateActionType.SET_ETH_WALLET_OVERLAY;
isOpen: boolean;
}
| { | {
type: AppStateActionType.SET_DRAWER; type: AppStateActionType.SET_DRAWER;
isOpen: boolean; isOpen: boolean;
@@ -16,6 +16,7 @@ const initialAppState: AppState = {
totalSupply: new BigNumber(0), totalSupply: new BigNumber(0),
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -44,6 +45,12 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay, vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
}; };
} }
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
return {
...state,
ethConnectOverlay: action.isOpen,
};
}
case AppStateActionType.SET_DRAWER: { case AppStateActionType.SET_DRAWER: {
return { return {
...state, ...state,
@@ -202,7 +202,6 @@
"tokenVotes": "Token votes", "tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes", "liquidityVotes": "Liquidity votes",
"castYourVote": "Cast your vote", "castYourVote": "Cast your vote",
"yourVote": "Your vote",
"for": "For", "for": "For",
"against": "Against", "against": "Against",
"majorityRequired": "Majority Required", "majorityRequired": "Majority Required",
@@ -594,9 +593,7 @@
"numberOfAgainstVotes": "Number of votes against", "numberOfAgainstVotes": "Number of votes against",
"yesPercentage": "Yes percentage", "yesPercentage": "Yes percentage",
"noPercentage": "No percentage", "noPercentage": "No percentage",
"proposalJson": "Full proposal JSON", "proposalTerms": "Proposal terms",
"proposalDetails": "Proposal details",
"proposalDescription": "Description",
"currentlySetTo": "Currently expected to ", "currentlySetTo": "Currently expected to ",
"currently": "currently", "currently": "currently",
"finalOutcomeMayDiffer": "Final outcome may differ", "finalOutcomeMayDiffer": "Final outcome may differ",
@@ -790,9 +787,9 @@
"homeVegaTokenButtonText": "Manage tokens", "homeVegaTokenButtonText": "Manage tokens",
"downloadProposalJson": "Download proposal as JSON", "downloadProposalJson": "Download proposal as JSON",
"networkUpgrade": "Network Upgrade", "networkUpgrade": "Network Upgrade",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved by validators", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Waiting for validator votes", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Pending",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Declined by validators", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Rejected",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified",
"vegaRelease{release}": "Vega Release {{release}}", "vegaRelease{release}": "Vega Release {{release}}",
"upgradeBlockHeight": "Upgrade block height", "upgradeBlockHeight": "Upgrade block height",
@@ -814,6 +811,5 @@
"OptOutOfTelemetry": "You can opt out any time via settings", "OptOutOfTelemetry": "You can opt out any time via settings",
"NoThanks": "No thanks", "NoThanks": "No thanks",
"ShareData": "Share data", "ShareData": "Share data",
"ContinueSharingData": "Continue sharing data", "ContinueSharingData": "Continue sharing data"
"NodeUnsuitable": "Node: {{url}} is unsuitable"
} }
-8
View File
@@ -1,8 +0,0 @@
export const PARTY_NOT_FOUND = 'failed to get party for ID';
export const isPartyNotFoundError = (error: { message: string }) => {
if (error.message.includes(PARTY_NOT_FOUND)) {
return true;
}
return false;
};
@@ -50,22 +50,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
?.map((t) => { ?.map((t) => {
const tranche_progress = const tranche_progress =
t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0; t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0;
let lockedDecimal; const lockedDecimal = tranche_progress < 0 ? 1 : 1 - tranche_progress;
if (t.duration !== 0) {
if (tranche_progress < 0) {
lockedDecimal = 1;
} else {
lockedDecimal = 1 - tranche_progress;
}
} else {
if (now < t.cliff_start) {
lockedDecimal = 1;
} else {
lockedDecimal = 0;
}
}
const clampedLockedDecimal = Math.max(0, Math.min(1, lockedDecimal));
return { return {
tranche_id: t.tranche_id, tranche_id: t.tranche_id,
tranche_start: secondsToDate(t.cliff_start), tranche_start: secondsToDate(t.cliff_start),
@@ -75,7 +60,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
toBigNum(t.current_balance, decimals) toBigNum(t.current_balance, decimals)
), ),
locked_amount: toBigNum(t.initial_balance, decimals).times( locked_amount: toBigNum(t.initial_balance, decimals).times(
clampedLockedDecimal lockedDecimal
), ),
users: t.users, users: t.users,
}; };
+1 -2
View File
@@ -12,8 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
import { ProposalsListItem } from '../proposals/components/proposals-list-item'; import { ProposalsListItem } from '../proposals/components/proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item'; import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import Routes from '../routes'; import Routes from '../routes';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useNodesQuery } from '../staking/home/__generated__/Nodes'; import { useNodesQuery } from '../staking/home/__generated__/Nodes';
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals'; import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
import { import {
@@ -53,6 +53,7 @@ const mockAppState: AppState = {
totalSupply: new BigNumber(65000000), totalSupply: new BigNumber(65000000),
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -1 +0,0 @@
export { ProposalDescription } from './proposal-description';
@@ -1,51 +0,0 @@
import ReactMarkdown from 'react-markdown';
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
export const ProposalDescription = ({
description,
}: {
description: string;
}) => {
const { t } = useTranslation();
const [showDescription, setShowDescription] = useState(false);
const showDescriptionIconClasses = classnames('mb-4', {
'rotate-180': showDescription,
});
return (
<section data-testid="proposal-description">
<button
onClick={() => setShowDescription(!showDescription)}
data-testid="proposal-description-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDescription')} />
<div className={showDescriptionIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDescription && (
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
<div className="p-2">
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
</RoundedWrapper>
)}
</section>
);
};
@@ -206,6 +206,28 @@ describe('Proposal header', () => {
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
}),
false
);
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
/Class aptent/
);
});
// Remove once proposals have rationale and re-enable above tests // Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => { it('Renders Freeform proposal - id for title', () => {
renderComponent( renderComponent(
@@ -5,6 +5,7 @@ import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
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 ReactMarkdown from 'react-markdown';
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';
@@ -22,7 +23,12 @@ export const ProposalHeader = ({
let details: ReactNode; let details: ReactNode;
let proposalType = ''; let proposalType = '';
const title = proposal?.rationale.title.trim(); let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim();
if (title?.length === 0 && description && description.length > 0) {
title = description;
description = '';
}
const titleContent = shorten(title ?? '', 100); const titleContent = shorten(title ?? '', 100);
@@ -139,6 +145,23 @@ export const ProposalHeader = ({
{details} {details}
</div> </div>
)} )}
{description && !isListItem && (
<div data-testid="proposal-description">
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
<SubHeading title={t('ProposalDescription')} />
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
)}
</> </>
); );
}; };
@@ -1 +0,0 @@
export { ProposalJson } from './proposal-json';
@@ -0,0 +1 @@
export { ProposalTermsJson } from './proposal-terms-json';
@@ -1,15 +1,15 @@
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading'; import { SubHeading } from '../../../../components/heading';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { PartialDeep } from 'type-fest';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type * as Schema from '@vegaprotocol/types';
import { useState } from 'react';
import classnames from 'classnames';
export const ProposalJson = ({ export const ProposalTermsJson = ({
proposal, terms,
}: { }: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; terms: PartialDeep<Schema.ProposalTerms>;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false); const [showDetails, setShowDetails] = useState(false);
@@ -18,20 +18,20 @@ export const ProposalJson = ({
}); });
return ( return (
<section data-testid="proposal-json"> <section>
<button <button
onClick={() => setShowDetails(!showDetails)} onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-json-toggle" data-testid="proposal-terms-toggle"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<SubHeading title={t('proposalJson')} /> <SubHeading title={t('proposalTerms')} />
<div className={showDetailsIconClasses}> <div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} /> <Icon name="chevron-down" size={8} />
</div> </div>
</div> </div>
</button> </button>
{showDetails && <SyntaxHighlighter data={proposal} />} {showDetails && <SyntaxHighlighter data={terms} />}
</section> </section>
); );
}; };
@@ -1 +0,0 @@
export { ProposalTerms } from './proposal-terms';
@@ -1,142 +0,0 @@
import {
RoundedWrapper,
KeyValueTable,
KeyValueTableRow,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../../lib/bignumber';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import classnames from 'classnames';
interface ProposalTermsProps {
data: Record<string, unknown>;
}
const getParsedValue = (value: unknown) => {
if (typeof value === 'string') {
try {
// Check if value is a number string - if so use bignumber to maintain precision
if (/^\d+(\.\d+)?$/.test(value)) {
return new BigNumber(value).toString();
} else {
// This would convert, for example, a JSON object ('{"key":"value"}')
// into an actual JS object ({key: "value"})
return JSON.parse(value);
}
} catch (error) {
return value;
}
} else {
return value;
}
};
const RenderKeyValue = ({
title,
value,
}: {
title: string;
value: string | number;
}) => (
<KeyValueTable>
<KeyValueTableRow>
{title}
{value}
</KeyValueTableRow>
</KeyValueTable>
);
const RenderArray = ({ title, array }: { title: string; array: unknown[] }) => {
if (array.every((item) => typeof item === 'string')) {
return <RenderKeyValue title={title} value={array.join(', ')} />;
} else {
return (
<div>
<div className="mb-2">{title}</div>
{array.map((item, index) => (
<div key={index}>
<ProposalTermsRenderer data={item as Record<string, unknown>} />
</div>
))}
</div>
);
}
};
// Working with 'unknown' type as a proposal's terms can be in many shapes
const RenderTerm = ({ title, value }: { title: string; value: unknown }) => {
const parsedValue = getParsedValue(value);
if (parsedValue === null || typeof parsedValue === 'boolean') {
return <RenderKeyValue title={title} value={String(parsedValue)} />;
} else if (
typeof parsedValue === 'string' ||
typeof parsedValue === 'number'
) {
return <RenderKeyValue title={title} value={parsedValue} />;
} else if (typeof parsedValue === 'object') {
if (Array.isArray(parsedValue)) {
return <RenderArray title={title} array={parsedValue} />;
} else {
return (
<div>
<div className="my-2">{title}</div>
<ProposalTermsRenderer
data={parsedValue as Record<string, unknown>}
/>
</div>
);
}
} else if (parsedValue === undefined) {
return <span>{'undefined'}</span>;
} else {
return <span>{String(parsedValue)}</span>;
}
};
const ProposalTermsRenderer = ({ data }: ProposalTermsProps) => {
return (
<RoundedWrapper paddingBottom={true}>
{Object.keys(data)
.filter(
(key) =>
!['__typename', 'closingDatetime', 'enactmentDatetime'].includes(
key
)
)
.map((key, index) => (
<div key={index}>
<RenderTerm title={key} value={data[key]} />
</div>
))}
</RoundedWrapper>
);
};
export const ProposalTerms = ({ data }: ProposalTermsProps) => {
const { t } = useTranslation();
const [showTerms, setShowTerms] = useState(false);
const showTermsIconClasses = classnames('mb-4', {
'rotate-180': showTerms,
});
return (
<section data-testid="proposal-terms">
<button
onClick={() => setShowTerms(!showTerms)}
data-testid="proposal-terms-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDetails')} />
<div className={showTermsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showTerms && <ProposalTermsRenderer data={data} />}
</section>
);
};
@@ -25,11 +25,8 @@ jest.mock('../proposal-detail-header/proposal-header', () => ({
jest.mock('../proposal-change-table', () => ({ jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>, ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
})); }));
jest.mock('../proposal-json', () => ({ jest.mock('../proposal-terms-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>, ProposalTermsJson: () => <div data-testid="proposal-terms-json"></div>,
}));
jest.mock('../proposal-terms/proposal-terms', () => ({
ProposalTerms: () => <div data-testid="proposal-terms"></div>,
})); }));
jest.mock('../proposal-votes-table', () => ({ jest.mock('../proposal-votes-table', () => ({
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>, ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
@@ -52,8 +49,7 @@ it('renders each section', async () => {
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />); render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
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-terms-json')).toBeInTheDocument();
expect(screen.getByTestId('proposal-terms')).toBeInTheDocument();
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument(); expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument(); expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument(); expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
@@ -6,10 +6,8 @@ import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header'; import { ProposalHeader } from '../proposal-detail-header/proposal-header';
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 { ProposalDescription } from '../proposal-description';
import { ProposalChangeTable } from '../proposal-change-table'; import { ProposalChangeTable } from '../proposal-change-table';
import { ProposalJson } from '../proposal-json'; import { ProposalTermsJson } from '../proposal-terms-json';
import { ProposalTerms } from '../proposal-terms';
import { ProposalVotesTable } from '../proposal-votes-table'; import { ProposalVotesTable } from '../proposal-votes-table';
import { VoteDetails } from '../vote-details'; import { VoteDetails } from '../vote-details';
import { ListAsset } from '../list-asset'; import { ListAsset } from '../list-asset';
@@ -22,7 +20,7 @@ export enum ProposalType {
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER', PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM', PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
} }
export interface ProposalProps { interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
} }
@@ -80,11 +78,9 @@ export const Proposal = ({ proposal }: ProposalProps) => {
<AsyncRenderer data={params} loading={loading} error={error}> <AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal"> <section data-testid="proposal">
<ProposalHeader proposal={proposal} isListItem={false} /> <ProposalHeader proposal={proposal} isListItem={false} />
<div className="my-10"> <div className="my-10">
<ProposalChangeTable proposal={proposal} /> <ProposalChangeTable proposal={proposal} />
</div> </div>
{proposal.terms.change.__typename === 'NewAsset' && {proposal.terms.change.__typename === 'NewAsset' &&
proposal.terms.change.source.__typename === 'ERC20' && proposal.terms.change.source.__typename === 'ERC20' &&
proposal.id ? ( proposal.id ? (
@@ -94,20 +90,7 @@ export const Proposal = ({ proposal }: ProposalProps) => {
lifetimeLimit={proposal.terms.change.source.lifetimeLimit} lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
/> />
) : null} ) : null}
<div className="mb-12">
<div className="mb-4">
<ProposalDescription description={proposal.rationale.description} />
</div>
<div className="mb-4">
<ProposalTerms data={proposal.terms} />
</div>
<div className="mb-6">
<ProposalJson proposal={proposal} />
</div>
<div className="mb-10">
<RoundedWrapper paddingBottom={true}> <RoundedWrapper paddingBottom={true}>
<VoteDetails <VoteDetails
proposal={proposal} proposal={proposal}
@@ -119,10 +102,10 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/> />
</RoundedWrapper> </RoundedWrapper>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} /> <ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div> </div>
<ProposalTermsJson terms={proposal.terms} />
</section> </section>
</AsyncRenderer> </AsyncRenderer>
); );
@@ -24,6 +24,7 @@ const openProposalClosesNextMonth = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextMonth.toString(), closingDatetime: nextMonth.toString(),
enactmentDatetime: nextMonth.toString(),
}, },
}); });
@@ -35,6 +36,7 @@ const openProposalClosesNextWeek = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextWeek.toString(), closingDatetime: nextWeek.toString(),
enactmentDatetime: nextWeek.toString(),
}, },
}); });
@@ -43,6 +45,7 @@ const enactedProposalClosedLastWeek = generateProposal({
state: ProposalState.STATE_ENACTED, state: ProposalState.STATE_ENACTED,
terms: { terms: {
closingDatetime: lastWeek.toString(), closingDatetime: lastWeek.toString(),
enactmentDatetime: lastWeek.toString(),
}, },
}); });
@@ -51,6 +54,7 @@ const failedProposalClosedLastMonth = generateProposal({
state: ProposalState.STATE_FAILED, state: ProposalState.STATE_FAILED,
terms: { terms: {
closingDatetime: lastMonth.toString(), closingDatetime: lastMonth.toString(),
enactmentDatetime: lastMonth.toString(),
}, },
}); });
@@ -8,11 +8,12 @@ import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes'; import Routes from '../../../routes';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { createDocsLinks, ExternalLinks } from '@vegaprotocol/utils';
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 { useEnvironment } from '@vegaprotocol/environment';
interface ProposalsListProps { interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>; proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -35,6 +36,7 @@ export const ProposalsList = ({
protocolUpgradeProposals, protocolUpgradeProposals,
lastBlockHeight, lastBlockHeight,
}: ProposalsListProps) => { }: ProposalsListProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const [filterString, setFilterString] = useState(''); const [filterString, setFilterString] = useState('');
const sortedProposals = proposals.reduce( const sortedProposals = proposals.reduce(
@@ -81,9 +83,9 @@ export const ProposalsList = ({
marginBottom={false} marginBottom={false}
title={t('pageTitleProposals')} title={t('pageTitleProposals')}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<div className="xs:justify-self-end" data-testid="new-proposal-link"> <div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}> <ExternalLink href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}>
<Button variant="primary" size="sm"> <Button variant="primary" size="sm">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{t('NewProposal')} {t('NewProposal')}
@@ -2,10 +2,9 @@ import { render, screen } from '@testing-library/react';
import { ProposalFormTerms } from './proposal-form-terms'; import { ProposalFormTerms } from './proposal-form-terms';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const renderComponent = () => { const renderComponent = () => {
@@ -5,7 +5,7 @@ import {
ExternalLink, ExternalLink,
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { DocsLinks } from '@vegaprotocol/environment'; import { useDocsLink, useEnvironment, DOCS } from '@vegaprotocol/environment';
import type { UseFormRegisterReturn } from 'react-hook-form'; import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormTermsProps { interface ProposalFormTermsProps {
@@ -21,20 +21,21 @@ export const ProposalFormTerms = ({
labelOverride, labelOverride,
docsLink, docsLink,
}: ProposalFormTermsProps) => { }: ProposalFormTermsProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const useLink = useDocsLink();
const PROPOSALS_GUIDE = `${useLink(DOCS.PROPOSALS_GUIDE)}${docsLink}`;
return ( return (
<FormGroup <FormGroup
label={labelOverride || t('ProposalTerms')} label={labelOverride || t('ProposalTerms')}
labelFor="proposal-terms" labelFor="proposal-terms"
> >
{docsLink && DocsLinks && ( {docsLink && VEGA_DOCS_URL && (
<div className="mt-[-4px] mb-2 text-sm font-light"> <div className="mt-[-4px] mb-2 text-sm font-light">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink href={PROPOSALS_GUIDE} target="_blank">
href={`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`} {PROPOSALS_GUIDE}
target="_blank"
>
{`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`}
</ExternalLink> </ExternalLink>
</div> </div>
)} )}
@@ -32,9 +32,7 @@ describe('ProtocolUpgradeProposalDetailInfo', () => {
it('should render the state', () => { it('should render the state', () => {
const { getByTestId } = renderComponent(); const { getByTestId } = renderComponent();
expect(getByTestId('protocol-upgrade-state')).toHaveTextContent( expect(getByTestId('protocol-upgrade-state')).toHaveTextContent('Pending');
'Waiting for validator votes'
);
}); });
it('should render the vega release tag', () => { it('should render the vega release tag', () => {
@@ -26,34 +26,28 @@ describe('ProtocolUpgradeProposalsListItem', () => {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
icon: 'protocol-upgrade-proposal-status-icon-rejected', icon: 'protocol-upgrade-proposal-status-icon-rejected',
text: 'Declined by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
icon: 'protocol-upgrade-proposal-status-icon-pending', icon: 'protocol-upgrade-proposal-status-icon-pending',
text: 'Waiting for validator votes',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
icon: 'protocol-upgrade-proposal-status-icon-approved', icon: 'protocol-upgrade-proposal-status-icon-approved',
text: 'Approved by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
icon: 'protocol-upgrade-proposal-status-icon-unspecified', icon: 'protocol-upgrade-proposal-status-icon-unspecified',
text: 'Unspecified',
}, },
]; ];
statuses.forEach(({ status, icon, text }) => { statuses.forEach(({ status, icon }) => {
renderComponent({ ...proposal, status }); renderComponent({ ...proposal, status });
const statusIcon = screen.getByTestId(icon); const statusIcon = screen.getByTestId(icon);
const textContent = screen.getByText(text);
expect(statusIcon).toBeInTheDocument(); expect(statusIcon).toBeInTheDocument();
expect(textContent).toBeInTheDocument();
}); });
}); });
@@ -111,9 +111,7 @@ describe('Vote buttons', () => {
</AppStateProvider> </AppStateProvider>
); );
expect( expect(
screen.getByText( screen.getByText('You need some VEGA tokens to participate in governance')
'You need some VEGA tokens to participate in governance.'
)
).toBeTruthy(); ).toBeTruthy();
}); });
@@ -2,12 +2,7 @@ import { format } from 'date-fns';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { import { AsyncRenderer, Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
AsyncRenderer,
Button,
ButtonLink,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { addDecimal, toBigNum } from '@vegaprotocol/utils'; import { addDecimal, toBigNum } from '@vegaprotocol/utils';
import { ProposalState, VoteValue } from '@vegaprotocol/types'; import { ProposalState, VoteValue } from '@vegaprotocol/types';
import { import {
@@ -166,12 +161,7 @@ export const VoteButtons = ({
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? ( {changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<> <>
{currentStakeAvailable.isLessThanOrEqualTo(0) && ( {currentStakeAvailable.isLessThanOrEqualTo(0) && (
<> <p data-testid="no-stake-available">{t('noGovernanceTokens')}</p>
<p data-testid="no-stake-available">{t('noGovernanceTokens')}.</p>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</>
)} )}
<div className="flex gap-4" data-testid="vote-buttons"> <div className="flex gap-4" data-testid="vote-buttons">
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { RoundedWrapper, Icon, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals'; import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
@@ -202,12 +202,7 @@ export const VoteDetails = ({
)} )}
<section className="mt-10"> <section className="mt-10">
{proposal?.state === ProposalState.STATE_OPEN ? ( <SubHeading title={t('castYourVote')} />
<SubHeading title={t('castYourVote')} />
) : (
<SubHeading title={t('yourVote')} />
)}
{pubKey ? ( {pubKey ? (
proposal && ( proposal && (
<VoteButtonsContainer <VoteButtonsContainer
@@ -229,9 +224,6 @@ export const VoteDetails = ({
<Icon name={'info-sign'} /> <Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div> <div>{t('connectAVegaWalletToVote')}</div>
</div> </div>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</div> </div>
<ConnectToVega /> <ConnectToVega />
</RoundedWrapper> </RoundedWrapper>
@@ -16,6 +16,7 @@ const mockAppState: AppState = {
totalSupply: mockTotalSupply, totalSupply: mockTotalSupply,
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -20,11 +20,8 @@ import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
const orderByDate = (arr: ProposalFieldsFragment[]) => const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy( orderBy(
arr, arr,
[ [(p) => new Date(p?.terms?.closingDatetime).getTime(), (p) => p.id],
(p) => new Date(p?.terms?.closingDatetime).getTime(), ['desc', 'desc']
(p) => new Date(p?.datetime).getTime(),
],
['asc', 'asc']
); );
const orderByUpgradeBlockHeight = ( const orderByUpgradeBlockHeight = (
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -5,7 +5,7 @@ import {
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours, deadlineToRoundedHours,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -17,6 +17,7 @@ import {
import { ProposalMinRequirements } from '../../components/shared'; import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -41,7 +42,7 @@ export const ProposeFreeform = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance, NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -110,13 +111,17 @@ export const ProposeFreeform = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -1,7 +1,10 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { suitableForSyntaxHighlighter } from '@vegaprotocol/utils'; import {
createDocsLinks,
suitableForSyntaxHighlighter,
} from '@vegaprotocol/utils';
import { useNetworkParams } from '@vegaprotocol/network-parameters'; import { useNetworkParams } from '@vegaprotocol/network-parameters';
import { import {
getClosingTimestamp, getClosingTimestamp,
@@ -9,7 +12,7 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -81,7 +84,7 @@ export const ProposeNetworkParameter = () => {
loading: networkParamsLoading, loading: networkParamsLoading,
error: networkParamsError, error: networkParamsError,
} = useNetworkParams(); } = useNetworkParams();
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -172,13 +175,17 @@ export const ProposeNetworkParameter = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -7,8 +7,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -53,7 +53,7 @@ export const ProposeNewAsset = () => {
NetworkParams.governance_proposal_asset_minProposerBalance, NetworkParams.governance_proposal_asset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -142,13 +142,17 @@ export const ProposeNewAsset = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeNewMarket = () => {
NetworkParams.governance_proposal_market_minProposerBalance, NetworkParams.governance_proposal_market_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -132,13 +132,17 @@ export const ProposeNewMarket = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -2,11 +2,12 @@ import Routes from '../../routes';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
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 { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
export const Propose = () => { export const Propose = () => {
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -14,11 +15,14 @@ export const Propose = () => {
<section className="pb-6"> <section className="pb-6">
<Heading title={t('NewProposal')} /> <Heading title={t('NewProposal')} />
<div className="text-sm"> <div className="text-sm">
{DocsLinks && ( {VEGA_DOCS_URL && (
<p> <p>
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank"> <ExternalLink
{DocsLinks.PROPOSALS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink> </ExternalLink>
</p> </p>
)} )}
@@ -1,6 +1,6 @@
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 { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { import {
AsyncRenderer, AsyncRenderer,
@@ -9,7 +9,7 @@ import {
InputError, InputError,
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -39,7 +39,7 @@ export const ProposeRaw = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance, NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -79,11 +79,14 @@ export const ProposeRaw = () => {
spamProtectionMin={params.spam_protection_proposal_min_tokens} spamProtectionMin={params.spam_protection_proposal_min_tokens}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank"> <ExternalLink
{DocsLinks.PROPOSALS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink> </ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeUpdateAsset = () => {
NetworkParams.governance_proposal_updateAsset_minProposerBalance, NetworkParams.governance_proposal_updateAsset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -132,13 +132,17 @@ export const ProposeUpdateAsset = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -7,8 +7,8 @@ import {
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -98,7 +98,7 @@ export const ProposeUpdateMarket = () => {
const [selectedMarket, setSelectedMarket] = useState<string | undefined>( const [selectedMarket, setSelectedMarket] = useState<string | undefined>(
undefined undefined
); );
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -181,13 +181,17 @@ export const ProposeUpdateMarket = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -54,16 +54,22 @@ export const TrancheItem = ({
{formatNumber(total, 2)} {formatNumber(total, 2)}
</span> </span>
</div> </div>
<div className="grid grid-cols-2 my-2"> <table className="w-full">
<div> <tbody>
<span>{t('Starts unlocking')}:</span>{' '} <tr>
<span>{format(tranche.tranche_start, DATE_FORMAT_LONG)}</span> <td>{t('Starts unlocking')}</td>
</div> <td className="text-right">
<div className="justify-self-end"> {format(tranche.tranche_start, DATE_FORMAT_LONG)}
<span>{t('Fully unlocked')}:</span>{' '} </td>
<span>{format(tranche.tranche_end, DATE_FORMAT_LONG)}</span> </tr>
</div> <tr>
</div> <td>{t('Fully unlocked')}</td>
<td className="text-right">
{format(tranche.tranche_end, DATE_FORMAT_LONG)}
</td>
</tr>
</tbody>
</table>
<LockedProgress <LockedProgress
locked={locked} locked={locked}
unlocked={unlocked} unlocked={unlocked}
@@ -25,7 +25,7 @@ const mockData = {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: { ACCOUNT_TYPE_FEES_LIQUIDITY: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -55,9 +55,7 @@ describe('EpochIndividualRewardsTable', () => {
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument(); ).toBeInTheDocument();
@@ -24,7 +24,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward3: RewardFieldsFragment = { const reward3: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
receivedAt: new Date(), receivedAt: new Date(),
@@ -34,7 +34,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward4: RewardFieldsFragment = { const reward4: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
receivedAt: new Date(), receivedAt: new Date(),
@@ -44,7 +44,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward5: RewardFieldsFragment = { const reward5: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '150', amount: '150',
percentageOfTotal: '0.15', percentageOfTotal: '0.15',
receivedAt: new Date(), receivedAt: new Date(),
@@ -105,7 +105,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -172,7 +172,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
}, },
@@ -202,7 +202,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -237,7 +237,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
}, },
@@ -285,7 +285,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '150', amount: '150',
percentageOfTotal: '0.15', percentageOfTotal: '0.15',
}, },
@@ -320,7 +320,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
}, },
@@ -350,7 +350,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -395,7 +395,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
}, },
@@ -79,24 +79,7 @@ export const generateEpochIndividualRewardsList = ({
epoch?.rewards.push(asset); epoch?.rewards.push(asset);
} }
if (!asset.rewardTypes[rewardType]) { asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
} else {
const previousAmount = asset.rewardTypes[rewardType]?.amount;
const previousPercentageOfTotal =
asset.rewardTypes[rewardType]?.percentageOfTotal;
asset.rewardTypes[rewardType] = {
amount: previousAmount
? new BigNumber(previousAmount).plus(amount).toString()
: amount,
percentageOfTotal: previousPercentageOfTotal
? new BigNumber(previousPercentageOfTotal)
.plus(percentageOfTotal)
.toString()
: percentageOfTotal,
};
}
// totalAmount is the sum of all rewardTypes amounts // totalAmount is the sum of all rewardTypes amounts
asset.totalAmount = Object.values(asset.rewardTypes).reduce( asset.totalAmount = Object.values(asset.rewardTypes).reduce(
@@ -29,7 +29,7 @@ const rewardsList = [
amount: '0', amount: '0',
}, },
{ {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '0', amount: '0',
}, },
{ {
@@ -78,9 +78,7 @@ describe('EpochTotalRewardsTable', () => {
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument(); ).toBeInTheDocument();
@@ -162,10 +162,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -228,7 +227,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '5', amount: '5',
}, },
}, },
@@ -288,10 +287,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -353,10 +351,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '5', amount: '5',
}, },
], ],
@@ -419,7 +416,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '6', amount: '6',
}, },
}, },
@@ -427,7 +424,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '27', amount: '27',
}, },
}, },
@@ -500,10 +497,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '33', amount: '33',
}, },
], ],
@@ -565,10 +561,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -642,10 +637,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -11,6 +11,7 @@ import {
ExternalLink, ExternalLink,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { createDocsLinks } from '@vegaprotocol/utils';
import { import {
useNetworkParams, useNetworkParams,
NetworkParams, NetworkParams,
@@ -20,7 +21,7 @@ import { EpochCountdown } from '../../../components/epoch-countdown';
import { Heading, SubHeading } from '../../../components/heading'; import { Heading, SubHeading } from '../../../components/heading';
import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards'; import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { ConnectToSeeRewards } from '../connect-to-see-rewards'; import { ConnectToSeeRewards } from '../connect-to-see-rewards';
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards'; import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
@@ -28,6 +29,7 @@ type RewardsView = 'total' | 'individual';
export const RewardsPage = () => { export const RewardsPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
const { pubKey, pubKeys } = useVegaWallet(); const { pubKey, pubKeys } = useVegaWallet();
const [toggleRewardsView, setToggleRewardsView] = const [toggleRewardsView, setToggleRewardsView] =
useState<RewardsView>('total'); useState<RewardsView>('total');
@@ -66,9 +68,9 @@ export const RewardsPage = () => {
<Heading title={t('pageTitleRewards')} /> <Heading title={t('pageTitleRewards')} />
<p className="mb-12"> <p className="mb-12">
{t('rewardsIntro')}{' '} {t('rewardsIntro')}{' '}
{DocsLinks && ( {VEGA_DOCS_URL && (
<ExternalLink <ExternalLink
href={DocsLinks.REWARDS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).REWARDS_GUIDE}
target="_blank" target="_blank"
data-testid="rewards-guide-link" data-testid="rewards-guide-link"
className="text-white" className="text-white"
@@ -26,7 +26,7 @@ export const RowAccountTypes = {
columnTitle: 'rewardsColPriceMakingHeader', columnTitle: 'rewardsColPriceMakingHeader',
description: 'rewardsColPriceMakingTooltip', description: 'rewardsColPriceMakingTooltip',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
columnTitle: 'rewardsColLiquidityProvisionHeader', columnTitle: 'rewardsColLiquidityProvisionHeader',
description: 'rewardsColLiquidityProvisionTooltip', description: 'rewardsColLiquidityProvisionTooltip',
}, },
+4 -7
View File
@@ -229,7 +229,7 @@ const redirects = [
const routerConfig = [ const routerConfig = [
{ {
path: Routes.HOME, path: Routes.HOME,
element: <LazyHome name="Home" />, element: <LazyHome name="Token" />,
}, },
{ {
path: Routes.PROPOSALS, path: Routes.PROPOSALS,
@@ -291,7 +291,7 @@ const routerConfig = [
// Not lazy as loaded when a user first hits the site // Not lazy as loaded when a user first hits the site
children: [ children: [
{ {
element: <Home name="Token" />, element: <Home name="Home" />,
index: true, index: true,
}, },
{ {
@@ -325,11 +325,8 @@ const routerConfig = [
}, },
], ],
}, },
{ path: 'associate', element: <LazyStakingAssociate name="Associate" /> }, { path: 'associate', element: <LazyStakingAssociate /> },
{ { path: 'disassociate', element: <LazyStakingDisassociate /> },
path: 'disassociate',
element: <LazyStakingDisassociate name="Disassociate" />,
},
], ],
}, },
{ {
@@ -1,14 +1,12 @@
import { useEthereumConfig } from '@vegaprotocol/web3'; import { useEthereumConfig } from '@vegaprotocol/web3';
import { StakingWalletsContainer } from './components/staking-wallets-container'; import { StakingWalletsContainer } from './components/staking-wallets-container/staking-wallets-container';
import { AssociatePage } from './associate-page'; import { AssociatePage } from './associate-page';
import { AssociatePageNoVega } from './associate-page-no-vega'; import { AssociatePageNoVega } from './associate-page-no-vega';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const AssociateContainer = ({ name }: RouteChildProps) => { export const AssociateContainer = () => {
useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { config } = useEthereumConfig(); const { config } = useEthereumConfig();
@@ -39,7 +39,7 @@ jest.mock('@vegaprotocol/wallet', () => ({
})); }));
const renderComponent = () => { const renderComponent = () => {
return render(<Disassociate name="Disassociate" />); return render(<Disassociate />);
}; };
describe('Disassociate', () => { describe('Disassociate', () => {
@@ -3,12 +3,10 @@ import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page'; import { DisassociatePage } from './components/disassociate-page';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const DisassociateContainer = ({ name }: RouteChildProps) => { export const DisassociateContainer = () => {
useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
@@ -1,12 +1,14 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EpochData } from './epoch-data'; import { EpochData } from './epoch-data';
import { DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/utils';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react'; import React from 'react';
export const Staking = () => { export const Staking = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
return ( return (
<> <>
@@ -14,9 +16,9 @@ export const Staking = () => {
<section> <section>
<p className="mb-12"> <p className="mb-12">
{t('stakingIntro')}{' '} {t('stakingIntro')}{' '}
{DocsLinks && ( {VEGA_DOCS_URL && (
<ExternalLink <ExternalLink
href={DocsLinks.STAKING_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
target="_blank" target="_blank"
data-testid="staking-guide-link" data-testid="staking-guide-link"
className="text-white" className="text-white"

Some files were not shown because too many files have changed in this diff Show More