Compare commits

..
104 changed files with 1621 additions and 711 deletions
+17 -33
View File
@@ -5,12 +5,12 @@ on:
branches:
- release/*
- develop
- main
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
jobs:
node-modules:
@@ -42,6 +42,13 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-format:
timeout-minutes: 20
needs: node-modules
@@ -168,42 +175,19 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }}
# console-e2e:
# needs: build-sources
# name: '(CI) console python'
# uses: ./.github/workflows/console-test-run.yml
# secrets: inherit
# if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
# with:
# github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
check-e2e-needed:
runs-on: ubuntu-latest
console-e2e:
needs: build-sources
name: '(CI) check if e2e needed'
outputs:
run-tests: ${{ steps.check-test.outputs.e2e-needed }}
steps:
- name: Check branch
id: check-test
run: |
if [ '${{ github.base_ref }}' == 'develop' ]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [ '${{ github.base_ref }}' == 'main' ]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ '${{ github.event_name }}' == 'push' && ${{ contains(github.ref_name, 'release/') }} ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
else
echo "e2e-needed=false" >> $GITHUB_OUTPUT
fi
- name: Print result
run: |
echo "e2e-needed: ${{ steps.check-test.outputs.e2e-needed }}"
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
cypress:
needs: [build-sources, check-e2e-needed]
needs: build-sources
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
+28 -43
View File
@@ -1,42 +1,31 @@
name: (CI) Console tests
env:
VEGA_VERSION: v0.72.14
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs:
run-tests:
name: run-tests
runs-on: 8-cores
timeout-minutes: 20
runs-on: console-test
timeout-minutes: 40
steps:
#----------------------------------------------
# check-out frontend-monorepo
#----------------------------------------------
- name: Checkout frontend-monorepo
- name: Checkout console test repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha || github.sha }}
ref: ${{ inputs.github-sha }}
#----------------------------------------------
# cache node modules
#----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules
id: cache
uses: actions/cache@v3
@@ -79,30 +68,36 @@ jobs:
uses: actions/checkout@v3
with:
repository: vegaprotocol/console-test
ref: ${{ inputs.console-test-branch }}
path: './console-test'
- name: Load console test envs
id: console-test-env
uses: falti/dotenv-action@v1.0.4
with:
path: './console-test/.env.${{ inputs.console-test-branch }}'
export-variables: true
keys-case: upper
log-variables: true
#----------------------------------------------
# install dependencies if cache does not exist
# install dependencies
#----------------------------------------------
- name: Install dependencies
working-directory: ./console-test
run: poetry install --no-interaction --no-root
#----------------------------------------------
# install vega binaries
# find vega binaries path
#----------------------------------------------
- name: Find vega binaries path
id: vega_bin_path
working-directory: ./console-test
run: echo path=$(poetry run python -c "import vega_sim; print(vega_sim.vega_bin_path)") >> $GITHUB_OUTPUT
#----------------------------------------------
# vega binaries cache
#----------------------------------------------
- name: Vega binaries cache
uses: actions/cache@v3
id: vega_binaries_cache
with:
path: ${{ steps.vega_bin_path.outputs.path }}
key: ${{ runner.os }}-vega-binaries-${{ env.VEGA_VERSION }}
#----------------------------------------------
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
working-directory: ./console-test
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
if: steps.vega_binaries_cache.outputs.cache-hit != 'true'
run: poetry run python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
#----------------------------------------------
# install playwright
#----------------------------------------------
@@ -114,7 +109,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
@@ -129,13 +124,3 @@ jobs:
name: playwright-trace
path: ./traces/
retention-days: 15
#----------------------------------------------
# ----- upload logs -----
#----------------------------------------------
- name: Upload worker logs
uses: actions/upload-artifact@v3
if: always()
with:
name: worker-logs
path: ./logs/
retention-days: 15
+1 -24
View File
@@ -13,35 +13,13 @@ on:
type: string
jobs:
runner-choice:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.step.outputs.runner }}
steps:
- name: Check branch
id: step
run: |
if [ '${{ github.base_ref }}' == 'main' ]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ '${{ github.base_ref }}' == 'develop' && '${{ github.ref_name }}' == 'main' ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ '${{ github.event_name }}' == 'push' && ${{ contains(github.ref_name, 'release/mainnet') }} ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
else
echo "runner=self-hosted-runner" >> $GITHUB_OUTPUT
fi
- name: Print runner
run: echo ${{ steps.step.outputs.runner }}
e2e:
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
needs: runner-choice
runs-on: ${{ needs.runner-choice.outputs.runner }}
runs-on: self-hosted-runner
timeout-minutes: 120
steps:
# Checks if skip cache was requested
@@ -85,7 +63,6 @@ jobs:
- name: Run Vegacapsule network and Vega wallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
######
## Run some tests
+11 -11
View File
@@ -2,12 +2,7 @@
name: Verify PR title
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
workflow_call:
jobs:
lint_pr:
@@ -16,16 +11,21 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: Install dependencies
run: |
rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+12 -20
View File
@@ -30,18 +30,12 @@ jobs:
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- name: Is dev image
if: ${{ github.ref_name == 'develop' && github.event_name == 'push' && matrix.app == 'trading' }}
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
- name: Is main image
if: ${{ github.ref_name == 'main' && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_MAIN_IMAGE=true >> $GITHUB_ENV
- name: Is PR
if: ${{ github.event_name == 'pull_request' }}
run: |
@@ -63,7 +57,7 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' && github.ref_name != 'main'}}
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }}
run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
@@ -87,7 +81,7 @@ jobs:
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -161,10 +155,10 @@ jobs:
- name: Sanity check docker image
run: |
echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash'
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'apk add --update tree; tree /usr/share/nginx/html'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3
@@ -185,7 +179,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -195,7 +189,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
@@ -222,7 +216,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3
@@ -335,9 +329,7 @@ jobs:
fi
# create commit
if ! git diff --cached --exit-code; then
commit_msg="Automated hash update from ${{ github.ref }}"
git commit -m "$commit_msg"
git push -u origin "main"
fi
commit_msg="Automated hash update from ${{ github.ref }}"
git commit -m "$commit_msg"
git push -u origin "main"
)
+1 -1
View File
@@ -118,7 +118,7 @@ On top of that there are two possible scenarios for running docker image - using
to run ipfs on port 3000:
```bash
docker run -p 3000:80 [TAG] /run-ipfs.sh
docker run -p 3000:80 [TAG] ipfs
```
to run nginx on port 3000:
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -4,7 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="flex items-center justify-center h-full pt-2 uppercase">
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
@@ -8,9 +8,7 @@ import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading, error } = useExplorerOracleSpecsQuery({
errorPolicy: 'ignore',
});
const { data, loading, error } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
@@ -6,11 +6,6 @@ import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { ProposalDocument } from './__generated__/Proposal';
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn(() => ({ data: [], loading: false })),
}));
jest.mock('../components/proposal', () => ({
Proposal: () => <div data-testid="proposal" />,
}));
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useMemo, useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
@@ -86,18 +86,12 @@ export const EpochIndividualRewards = ({
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
if (prevEpochIdRef.current === null) {
prevEpochIdRef.current = epochId;
} else if (epochId !== prevEpochIdRef.current) {
// When the epoch changes, we want to refetch the data to update the current page
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
refetchData();
}
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
}, [epochId, data, refetchData]);
return (
<AsyncRenderer
@@ -3,7 +3,7 @@ import { forwardRef, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -85,17 +85,17 @@ const TopThirdCellRenderer = (
}}
className="grid grid-cols-[60px_1fr] w-full h-full py-4 px-0 text-sm text-white text-center overflow-scroll"
>
<div className="px-3 text-xs text-left">
<div className="text-xs text-left px-3">
{params?.data?.rankingDisplay}
</div>
<div className="px-3 whitespace-normal">
<div className="whitespace-normal px-3">
<div className="mb-4">
<Button
data-testid="show-all-validators"
rightIcon={
<Icon
name="arrow-right"
className="mr-2 align-text-top fill-current"
className="fill-current mr-2 align-text-top"
/>
}
className="inline-flex items-center"
@@ -103,7 +103,7 @@ const TopThirdCellRenderer = (
{t('Reveal top validators')}
</Button>
</div>
<p className="mb-0 font-semibold text-white">
<p className="font-semibold text-white mb-0">
{t(
'Validators with too great a stake share will have the staking rewards for their delegators penalised.'
)}
@@ -1,7 +1,7 @@
import { forwardRef, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -9,7 +9,7 @@ import {
testOrderAmendment,
} from '../support/order-validation';
const orderSymbol = 'instrument-code';
const orderSymbol = 'market.tradableInstrument.instrument.code';
const orderSize = 'size';
const orderType = 'type';
const orderStatus = 'status';
@@ -229,7 +229,10 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
status: Schema.OrderStatus.STATUS_FILLED,
});
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
cy.get(`[col-id="${orderSymbol}"]`).contains('[title="Future"]', 'Futr');
cy.get('[col-id="market.tradableInstrument.instrument.code"]').contains(
'[title="Future"]',
'Futr'
);
});
it('must see a rejected order', () => {
+1 -2
View File
@@ -12,7 +12,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
@@ -24,7 +23,7 @@ NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_METAMASK_SNAPS=false
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
+4
View File
@@ -17,6 +17,10 @@ NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-ma
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.21.2-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

@@ -128,7 +128,7 @@ const MainGrid = memo(
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<TradingViews.fills.component />
<TradingViews.fills.component marketId={marketId} />
</Tab>
<Tab
id="accounts"
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n';
import type { ProductType } from '@vegaprotocol/types';
@@ -1,5 +1,5 @@
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
import { useColumnDefs } from './use-column-defs';
@@ -0,0 +1,83 @@
import { Input, InputError } from '@vegaprotocol/ui-toolkit';
import type { FieldValues } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import classNames from 'classnames';
import { useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';
import { Button } from './buttons';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const ApplyCodeForm = () => {
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const {
register,
handleSubmit,
formState: { errors },
setValue,
setError,
} = useForm();
const [params] = useSearchParams();
useEffect(() => {
const code = params.get('code');
if (code) setValue('code', code);
}, [params, setValue]);
const onSubmit = ({ code }: FieldValues) => {
if (isReadOnly || !pubKey || !code || code.length === 0) {
return;
}
sendTx(pubKey, {
applyReferralCode: {
id: code as string,
},
})
.then((res) => {
// TODO: Do something with response
})
.catch((err) => {
setError('code', {
type: 'required',
message: 'Your code has been rejected',
});
});
};
return (
<div className="mx-auto lg:w-1/2">
<h3 className="mb-5 text-xl text-center uppercase calt">
Apply a referral code
</h3>
<p className="mb-6 text-center">Enter a referral code</p>
<form
className={classNames('w-full flex flex-col gap-3', {
'animate-shake': Boolean(errors.code),
})}
onSubmit={handleSubmit(onSubmit)}
>
<label className="flex-grow">
<span className="block mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Your referral code
</span>
<Input
hasError={Boolean(errors.code)}
{...register('code', {
required: 'You have to provide a code to apply it.',
})}
/>
</label>
<Button
disabled={isReadOnly || !pubKey}
className="w-full"
type="submit"
>
Apply
</Button>
</form>
{errors.code && (
<InputError>{errors.code.message?.toString()}</InputError>
)}
</div>
);
};
@@ -0,0 +1,83 @@
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ComponentProps, HTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { NavLink } from 'react-router-dom';
type RainbowButtonProps = {
variant?: 'full' | 'border';
};
export const RainbowButton = ({
variant = 'full',
children,
className,
...props
}: RainbowButtonProps & HTMLAttributes<HTMLButtonElement>) => (
<button
className={classNames(
'bg-rainbow hover:bg-none hover:bg-vega-pink-500 rounded-lg overflow-hidden',
{
'px-5 py-3 text-white': variant === 'full',
'p-[0.125rem]': variant === 'border',
},
className
)}
{...props}
>
<div
className={classNames({
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white px-5 py-3 rounded-[0.35rem] overflow-hidden':
variant === 'border',
})}
>
{children}
</div>
</button>
);
const RAINBOW_TAB_STYLE = classNames(
'inline-block',
'bg-vega-clight-500 dark:bg-vega-cdark-500 hover:bg-vega-clight-400 dark:hover:bg-vega-cdark-400',
'data-[state="active"]:text-white data-[state="active"]:bg-rainbow data-[state="active"]:hover:bg-none data-[state="active"]:hover:bg-vega-pink-500 dark:data-[state="active"]:hover:bg-vega-pink-500',
'[&.active]:text-white [&.active]:bg-rainbow [&.active]:hover:bg-none [&.active]:hover:bg-vega-pink-500 dark:[&.active]:hover:bg-vega-pink-500',
'px-5 py-3',
'first:rounded-tl-lg last:rounded-tr-lg'
);
export const RainbowTabButton = forwardRef<
HTMLButtonElement,
HTMLAttributes<HTMLButtonElement>
>(({ children, ...props }, ref) => (
<button ref={ref} className={RAINBOW_TAB_STYLE} {...props}>
{children}
</button>
));
RainbowTabButton.displayName = 'RainbowTabButton';
export const RainbowTabLink = ({
to,
children,
...props
}: ComponentProps<typeof NavLink>) => (
<NavLink to={to} className={RAINBOW_TAB_STYLE} {...props}>
{children}
</NavLink>
);
export const Button = forwardRef<
HTMLButtonElement,
ComponentProps<typeof TradingButton>
>(({ children, intent, type, ...props }, ref) => {
return (
<TradingButton
ref={ref}
intent={intent || type === 'submit' ? Intent.Primary : Intent.None}
type={type}
{...props}
>
{children}
</TradingButton>
);
});
Button.displayName = 'TradingButton';
@@ -0,0 +1,6 @@
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND =
'bg-[url(/assets/sky-light.png)] dark:bg-[url(/assets/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat';
@@ -0,0 +1,61 @@
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { RainbowButton } from './buttons';
import { useState } from 'react';
import classNames from 'classnames';
export const CreateCodeForm = () => {
const [err, setErr] = useState<boolean | null>(null);
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const onSubmit = () => {
if (isReadOnly || !pubKey) {
setErr(true);
} else {
setErr(false);
sendTx(pubKey, {
createReferralSet: {
isTeam: false,
},
})
.then((res) => {
// TODO: Do something with response
})
.catch((err) => {
// TODO: Do something with rejection
});
}
};
return (
<div className="mx-auto lg:w-1/2">
<h3 className="mb-5 text-xl text-center uppercase calt">
Create a referral code
</h3>
<p className="mb-6 text-center">
Generate a referral code to share with your friends and start earning
commission.
</p>
{pubKey ? (
<div className="text-center">
<RainbowButton
className={classNames({
'animate-shake': err,
})}
variant="border"
onClick={() => onSubmit()}
>
Create a referral code
</RainbowButton>
</div>
) : (
<div className="text-center">
<RainbowButton variant="border" onClick={() => openWalletDialog()}>
Connect wallet
</RainbowButton>
</div>
)}
</div>
);
};
@@ -0,0 +1,46 @@
import { isRouteErrorResponse, useNavigate, useRouteError } from 'react-router';
import { RainbowButton } from './buttons';
import { AnimatedDudeWithWire } from './graphics/dude';
import { LayoutWithSky } from './layout';
export const ErrorBoundary = () => {
const error = useRouteError();
const navigate = useNavigate();
const title = isRouteErrorResponse(error)
? `${error.status} ${error.statusText}`
: 'Something went wrong';
const code = isRouteErrorResponse(error) ? error.status : 0;
const messages: Record<number, string> = {
0: 'An unknown error occurred.',
404: "The page you're looking for doesn't exists.",
};
return (
<LayoutWithSky className="pt-32">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire className="animate-spin" />
</div>
<h1 className="text-6xl font-alpha calt mb-10">{title}</h1>
{Object.keys(messages).includes(code.toString()) ? (
<p className="text-lg mb-10">{messages[code]}</p>
) : null}
<p className="text-lg mb-10">
<RainbowButton
onClick={() => navigate('..')}
variant="border"
className="text-xs"
>
Go back and try again
</RainbowButton>
</p>
</LayoutWithSky>
);
};
@@ -0,0 +1,70 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
export const Dude = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="41"
height="47"
viewBox="0 0 41 47"
fill="none"
className={className}
>
<path
d="M21.1895 0.298767L5.08827 27.4101L8.96133 29.7103L4.36099 37.4564L8.23404 39.7566L12.8344 32.0105L16.7074 34.3107L12.1071 42.0568L15.9801 44.3569L20.5805 36.6108L24.4535 38.911L40.5547 11.7996L21.1895 0.298767Z"
className="fill-black dark:fill-white"
/>
<path
d="M35.9346 15.1683L20.4424 5.96765L14.3086 16.2958L29.8008 25.4965L35.9346 15.1683Z"
className="fill-white dark:fill-black"
/>
<path
d="M25.646 17.7895L23.064 16.2561L21.5305 18.8381L24.1126 20.3716L25.646 17.7895Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.7612 16.7412L27.1792 15.2077L25.6458 17.7898L28.2278 19.3232L29.7612 16.7412Z"
className="fill-black dark:fill-white"
/>
<path
d="M33.877 15.6925L31.2949 14.159L29.7615 16.7411L32.3435 18.2745L33.877 15.6925Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.0342 26.7874L26.4521 25.2539L24.9187 27.836L27.5007 29.3694L29.0342 26.7874Z"
fill="#FF077F"
/>
</svg>
);
};
export const Wire = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="157"
height="88"
viewBox="0 0 157 88"
fill="none"
className={className}
>
<path
d="M109.398 6.12235C127.37 -3.81898 146.791 1.45045 153.465 14.307C160.138 27.1636 154.195 43.9948 140.438 52.1164C126.68 60.238 105.767 54.9998 84.9212 43.464C64.0752 31.9281 32.2412 6.42016 18.8175 24.185C6.90871 40.719 41.9332 68.4495 29.2664 82.7049C23.187 88.4974 11.1379 88.2645 0.968295 80.3398"
className="stroke-black dark:stroke-white"
strokeWidth="1.5"
strokeMiterlimit="10"
/>
</svg>
);
};
export const AnimatedDudeWithWire = ({ className }: { className?: string }) => (
<div className="relative">
<Wire className="absolute top-[25px]" />
<Dude
className={classNames(
'absolute left-[96px] animate-[wave_20s_ease-in-out_infinite]',
className
)}
/>
</div>
);
@@ -0,0 +1,52 @@
import { Table } from './table';
export const HowItWorksTable = () => (
<Table
className="bg-none bg-vega-clight-800 dark:bg-vega-cdark-800"
noHeader
noCollapse
columns={[{ name: 'number', className: 'px-0 pl-5' }, { name: 'step' }]}
data={[
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
1
</span>
),
step: 'Referrers generate a code assigned to their key via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
2
</span>
),
step: 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
3
</span>
),
step: 'Discounts are applied automatically during trading based on the key(s) used',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
4
</span>
),
step: 'Referrers earn commission based on a percentage of the taker fees their referees pay',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
5
</span>
),
step: 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee',
},
]}
></Table>
);
@@ -0,0 +1,31 @@
import classNames from 'classnames';
import { AnimatedDudeWithWire } from './graphics/dude';
export const LandingBanner = () => {
return (
<div className={classNames('relative mb-20')}>
<div className="">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire />
</div>
<div className="pt-32 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
Earn commission & stake rewards
</h1>
<p className="text-lg mb-10">
Invite friends and earn commission in the form of Vega rewards from
the trading fees they pay. Stake those rewards to earn multipliers
on future rewards.
</p>
<p className="text-lg">
Any friends that join using the code will receive discounts off
trading fees.
</p>
</div>
</div>
</div>
);
};
@@ -0,0 +1,36 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { SKY_BACKGROUND } from './constants';
import { Outlet } from 'react-router-dom';
export const Layout = ({
className,
children,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
'max-w-[1440px]',
'mx-auto px-4 md:px-32 lg:px-16 pb-32',
'relative z-0',
'h-full overflow-auto',
className
)}
{...props}
>
{children || <Outlet />}
</div>
);
};
export const LayoutWithSky = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div className={classNames('h-full', SKY_BACKGROUND)}>
<Layout className={className} {...props} />
</div>
);
};
@@ -0,0 +1,79 @@
import { Tile } from './tile';
import {
CopyWithTooltip,
Input,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Button } from './buttons';
import { Tag } from './tag';
export const ReferralStatistics = () => {
return (
<div className="grid grid-cols-1 grid-rows-1 gap-5">
<div className="grid lg:grid-cols-3 grid-rows-3 lg:grid-rows-1 gap-5">
<Tile>
<h3 className="mb-1 text-4xl text-center">3</h3>
<p className="mb-3 text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your referral tier
</p>
<Tag className="mx-auto" color="purple">
10,000 until Tier 2
</Tag>
</Tile>
<Tile>
<h3 className="mb-1 text-4xl text-center">0.1%</h3>
<p className="mb-3 text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your commission
</p>
<Tag className="mx-auto" color="purple">
10,000 until 0.5%x
</Tag>
</Tile>
<Tile variant="rainbow">
<h3 className="mb-1 text-lg calt">Your referral code</h3>
<p className="mb-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Share this code with friends
</p>
<div className="flex gap-2">
<Input size={1} readOnly value="1234567890" />
<CopyWithTooltip text="1234567890">
<Button
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</Button>
</CopyWithTooltip>
</div>
</Tile>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 grid-rows-1 gap-5">
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">10,000</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your total trading volume
</p>
</Tile>
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">7</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Total traders referred
</p>
</Tile>
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">0.1%</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Maximum trader discount
</p>
</Tile>
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">10,000</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your commission (last 30d)
</p>
</Tile>
</div>
</div>
);
};
@@ -0,0 +1,148 @@
import {
TradingAnchorButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { HowItWorksTable } from './how-it-works-table';
import { LandingBanner } from './landing-banner';
import { TiersTable } from './tiers-table';
import { BORDER_COLOR, GRADIENT } from './constants';
import { RainbowTabLink } from './buttons';
import { Outlet } from 'react-router-dom';
import { Tag } from './tag';
import { Routes } from '../../pages/client-router';
export const Referrals = () => {
return (
<>
<LandingBanner />
<div>
<div className="flex justify-center">
<RainbowTabLink end to={Routes.REFERRALS}>
Your referrals
</RainbowTabLink>
<RainbowTabLink to={Routes.REFERRALS_CREATE_CODE}>
Your referrals
</RainbowTabLink>
<RainbowTabLink to={Routes.REFERRALS_APPLY_CODE}>
Apply a code
</RainbowTabLink>
</div>
<div className="py-16 border-t border-b border-vega-cdark-500">
<Outlet />
</div>
</div>
<div className="flex flex-row items-baseline justify-between mt-10 mb-5">
<h2 className="text-2xl">Referral tiers</h2>
<span className="text-base">
<span className="text-vega-clight-200 dark:text-vega-cdark-200">
Program ends:
</span>{' '}
16 epochs
</span>
</div>
<div className="mb-20">
<TiersTable />
</div>
<div className="flex flex-row items-baseline justify-between mb-5">
<h2 className="text-2xl">Staking multipliers</h2>
</div>
<div className="flex flex-col mb-20 jjustify-items-stretch md:flex-row gap-5">
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
<img
src="/1x.png"
alt="1x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Tradestarter</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 100 $VEGA tokens
</p>
<Tag color="green">Reward multiplier 1x</Tag>
</div>
</div>
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
<img
src="/2x.png"
alt="2x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Mid level degen</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 1000 $VEGA tokens
</p>
<Tag color="blue">Reward multiplier 2x</Tag>
</div>
</div>
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
<img
src="/3x.png"
alt="3x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Reward hoarder</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 10,000 $VEGA tokens
</p>
<Tag color="pink">Reward multiplier 3x</Tag>
</div>
</div>
</div>
<div className="mt-10 mb-5 text-center">
<h2 className="text-2xl">How it works</h2>
</div>
<div className="md:w-[60%] mx-auto">
<HowItWorksTable />
<div className="mt-5">
<TradingAnchorButton
className="mx-auto w-max"
href="https://docs.vega.xyz/"
target="_blank"
>
Read the terms <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</TradingAnchorButton>
</div>
</div>
</>
);
};
export default Referrals;
@@ -0,0 +1,116 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TableColumnDefinition = {
displayName?: string;
name: string;
tooltip?: string;
className?: string;
};
type TableProps = {
columns: TableColumnDefinition[];
data: Record<TableColumnDefinition['name'] | 'className', React.ReactNode>[];
noHeader?: boolean;
noCollapse?: boolean;
};
const INNER_BORDER_STYLE = `border-b ${BORDER_COLOR}`;
export const Table = ({
columns,
data,
noHeader = false,
noCollapse = false,
className,
...props
}: TableProps & HTMLAttributes<HTMLTableElement>) => {
const header = (
<thead className={classNames({ 'max-md:hidden': !noCollapse })}>
<tr>
{columns.map(({ displayName, name, tooltip }) => (
<th
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100',
INNER_BORDER_STYLE
)}
>
<span className="flex flex-row gap-2 items-center">
<span>{displayName}</span>
{tooltip ? (
<Tooltip description={tooltip}>
<button className="text-vega-clight-400 dark:text-vega-cdark-400 no-underline decoration-transparent w-[12px] h-[12px] inline-flex">
<VegaIcon size={12} name={VegaIconNames.INFO} />
</button>
</Tooltip>
) : null}
</span>
</th>
))}
</tr>
</thead>
);
return (
<table
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
BORDER_COLOR,
GRADIENT,
className
)}
{...props}
>
{!noHeader && header}
<tbody>
{data.map((d, i) => (
<tr
key={i}
className={classNames(d['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
},
INNER_BORDER_STYLE,
{
'border-none': i === data.length - 1 && noCollapse,
'md:border-none': i === data.length - 1,
'max-md:border-none':
i === data.length - 1 && j === columns.length - 1,
},
className
)}
key={`${i}-${name}`}
>
{/** display column name in mobile view */}
{!noCollapse &&
!noHeader &&
displayName &&
displayName.length > 0 && (
<span
aria-hidden
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
>
{displayName}
</span>
)}
<span>{d[name]}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
@@ -0,0 +1,32 @@
import type { HTMLAttributes } from 'react';
import classNames from 'classnames';
type TagProps = {
color?: 'yellow' | 'green' | 'blue' | 'purple' | 'pink' | 'orange' | 'none';
};
export const Tag = ({
color = 'none',
children,
className,
...props
}: TagProps & HTMLAttributes<HTMLDivElement>) => (
<div
className={classNames(
'mt-3 w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
{
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
'border-vega-green-500 text-vega-green-500': color === 'green',
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
'border-vega-orange-500 text-vega-orange-500': color === 'orange',
'border-vega-clight-100 text-vega-clight-100 dark:border-vega-cdark-100 dark:text-vega-cdark-100':
color === 'none',
},
className
)}
{...props}
>
{children}
</div>
);
@@ -0,0 +1,42 @@
import { Table } from './table';
export const TiersTable = () => (
<Table
columns={[
{ name: 'tier', displayName: 'Tier' },
{
name: 'commission',
displayName: 'Referrer commission',
tooltip: 'A percentage of commission earned by the referrer',
},
{ name: 'discount', displayName: 'Referrer trading discount' },
{ name: 'volume', displayName: 'Min. trading volume' },
]}
data={[
{
tier: '1',
commission: '1.0%',
discount: '1.0%',
volume: '€30,000',
className:
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight',
},
{
tier: '2',
commission: '0.5%',
discount: '0.5%',
volume: '€20,000',
className:
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight',
},
{
tier: '3',
commission: '0.1%',
discount: '0.1%',
volume: '€10,000',
className:
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight',
},
]}
></Table>
);
@@ -0,0 +1,39 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TileProps = {
variant?: 'rainbow' | 'default';
};
export const Tile = ({
variant = 'default',
className,
children,
}: TileProps & HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
{
'bg-rainbow p-[0.125rem]': variant === 'rainbow',
[`border-2 ${BORDER_COLOR} p-0`]: variant === 'default',
},
'rounded-lg overflow-hidden'
)}
>
<div
className={classNames(
{
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white rounded-[0.35rem] overflow-hidden':
variant === 'rainbow',
},
'p-6',
GRADIENT,
className
)}
>
{children}
</div>
</div>
);
};
@@ -9,11 +9,5 @@ export const AnnouncementBanner = () => {
return null;
}
return (
<Banner
app="console"
configUrl={ANNOUNCEMENTS_CONFIG_URL}
background="url('/banner-bg.jpg')"
/>
);
return <Banner app="console" configUrl={ANNOUNCEMENTS_CONFIG_URL} />;
};
+1
View File
@@ -1 +1,2 @@
export * from './announcement-banner';
export * from './upgrade-banner';
@@ -0,0 +1,84 @@
import { useMemo, useState } from 'react';
import { gt, prerelease } from 'semver';
import {
ReleasesFeed,
useEnvironment,
useReleases,
Networks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import {
CopyWithTooltip,
ExternalLink,
Intent,
NotificationBanner,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
// v0.20.12-core-0.71.4 -> v0.20.12
// we need to strip the "core" suffix in order to determine whether a release
// is a pre-release (candidate); example: v.0.21.0-beta.1-core-0.71.4
const parseTagName = (tagName: string) => tagName.replace(/-core-[\d.]+$/i, '');
type UpgradeBannerProps = {
showVersionChange: boolean;
};
export const UpgradeBanner = ({ showVersionChange }: UpgradeBannerProps) => {
const [visible, setVisible] = useState(true);
const { data } = useReleases(ReleasesFeed.FrontEnd);
const { APP_VERSION, VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
const newest = useMemo(() => {
if (!APP_VERSION || !data) return undefined;
const newer = data.filter((r) => gt(r.tagName, APP_VERSION));
const valid =
// filter pre-releases on mainnet
VEGA_ENV === Networks.MAINNET
? newer?.filter((r) => !prerelease(parseTagName(r.tagName)))
: newer;
return valid.sort((a, b) => (gt(a.tagName, b.tagName) ? -1 : 1))[0];
}, [APP_VERSION, VEGA_ENV, data]);
if (!visible || !newest) {
return null;
}
return (
<NotificationBanner
intent={Intent.Warning}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase mb-1">
<ExternalLink href={CANONICAL_URL}>
{t('Upgrade to the latest version of Console')}
</ExternalLink>
</div>
<div data-testid="bookmark-message">
<a
className="underline"
href={newest.htmlUrl}
rel="noreferrer nofollow noopener"
target="_blank"
>
{t("View what's changed")}
</a>{' '}
{t(' or bookmark')}{' '}
<a className="underline" href={CANONICAL_URL}>
{t('console.vega.xyz')}
</a>{' '}
<CopyWithTooltip text={CANONICAL_URL}>
<button title={t('Copy %s', CANONICAL_URL)}>
<span className="sr-only">{t('Copy %s', CANONICAL_URL)}</span>
<VegaIcon size={14} name={VegaIconNames.COPY} />
</button>
</CopyWithTooltip>{' '}
{'to always see the latest version.'}
</div>
</NotificationBanner>
);
};
@@ -9,7 +9,7 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const FillsContainer = () => {
export const FillsContainer = ({ marketId }: { marketId?: string }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey } = useVegaWallet();
@@ -31,6 +31,7 @@ export const FillsContainer = () => {
return (
<FillsManager
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
gridProps={gridStoreCallbacks}
/>
+1 -1
View File
@@ -53,7 +53,7 @@ export const HeaderStat = ({
<div data-testid="item-header" id={id}>
{heading}
</div>
<Tooltip description={description} underline>
<Tooltip description={description}>
<div
data-testid="item-value"
aria-labelledby={id}
@@ -180,6 +180,11 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
{t('Trading')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links[Routes.REFERRALS]()} onClick={onClick}>
{t('Referrals')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links[Routes.PORTFOLIO]()} onClick={onClick}>
{t('Portfolio')}
@@ -28,20 +28,14 @@ export interface OrderContainerProps {
filter?: Filter;
}
const AUTO_SIZE_COLUMNS = ['instrument-code'];
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const { gridState, updateGridState } = useOrderListGridState(filter);
const gridStoreCallbacks = useDataGridEvents(
gridState,
(newState) => {
updateGridState(filter, newState);
},
AUTO_SIZE_COLUMNS
);
const gridStoreCallbacks = useDataGridEvents(gridState, (newState) => {
updateGridState(filter, newState);
});
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
@@ -10,8 +10,6 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
const AUTO_SIZE_COLUMNS = ['marketCode'];
export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
@@ -19,11 +17,7 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const gridStore = usePositionsStore((store) => store.gridStore);
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(
gridStore,
updateGridStore,
AUTO_SIZE_COLUMNS
);
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
if (!pubKey) {
return (
@@ -27,8 +27,8 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
const dismiss = useOnboardingStore((store) => store.dismiss);
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
const marketId = useGlobalStore((store) => store.marketId);
const setWalletDialogOpen = useOnboardingStore(
(store) => store.setWalletDialogOpen
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const setViews = useSidebar((store) => store.setViews);
@@ -40,7 +40,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
return (
<TradingButton {...buttonProps} onClick={() => setWalletDialogOpen(true)}>
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Connect')}
</TradingButton>
);
@@ -70,7 +70,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
}
return (
<TradingButton {...buttonProps} onClick={() => setWalletDialogOpen(true)}>
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Get started')}
</TradingButton>
);
@@ -12,20 +12,16 @@ import { useGlobalStore } from '../../stores';
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
export const useOnboardingStore = create<{
dialogOpen: boolean;
walletDialogOpen: boolean;
dismissed: boolean;
dismiss: () => void;
setDialogOpen: (isOpen: boolean) => void;
setWalletDialogOpen: (isOpen: boolean) => void;
}>()(
persist(
(set) => ({
dialogOpen: true,
walletDialogOpen: false,
dismissed: false,
dismiss: () => set({ dismissed: true }),
setDialogOpen: (isOpen) => set({ dialogOpen: isOpen }),
setWalletDialogOpen: (isOpen) => set({ walletDialogOpen: isOpen }),
}),
{
name: ONBOARDING_STORAGE_KEY,
@@ -3,54 +3,30 @@ import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '@vegaprotocol/environment';
import { WelcomeDialogContent } from './welcome-dialog-content';
import { useOnboardingStore } from './use-get-onboarding-step';
import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
export const WelcomeDialog = () => {
const { VEGA_ENV } = useEnvironment();
const dismissed = useOnboardingStore((store) => store.dismissed);
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
const dismiss = useOnboardingStore((store) => store.dismiss);
const walletDialogOpen = useOnboardingStore(
(store) => store.walletDialogOpen
);
const setWalletDialogOpen = useOnboardingStore(
(store) => store.setWalletDialogOpen
);
const content = walletDialogOpen ? (
<VegaConnectDialog
connectors={Connectors}
riskMessage={<RiskMessage />}
onClose={() => setWalletDialogOpen(false)}
contentOnly
/>
) : (
<WelcomeDialogContent />
);
const onClose = walletDialogOpen ? () => setWalletDialogOpen(false) : dismiss;
const title = walletDialogOpen ? null : (
<span className="font-alpha calt" data-testid="welcome-title">
{t('Console')}{' '}
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
{VEGA_ENV}
</span>
</span>
);
return (
<Dialog
open={dismissed ? false : dialogOpen}
title={title}
title={
<span className="font-alpha calt" data-testid="welcome-title">
{t('Console')}{' '}
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
{VEGA_ENV}
</span>
</span>
}
size="medium"
onChange={onClose}
onChange={() => dismiss()}
intent={Intent.None}
dataTestId="welcome-dialog"
>
{content}
<WelcomeDialogContent />
</Dialog>
);
};
+2 -1
View File
@@ -38,7 +38,7 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
import { AnnouncementBanner } from '../components/banner';
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
import { Navbar } from '../components/navbar';
import classNames from 'classnames';
import {
@@ -115,6 +115,7 @@ function AppBody({ Component }: AppProps) {
/>
<ProtocolUpgradeInProgressNotification />
<ViewingBanner />
<UpgradeBanner showVersionChange={true} />
</div>
<div data-testid={`pathname-${location.pathname}`}>
<Component />
+17 -8
View File
@@ -4,24 +4,33 @@ export default function Document() {
return (
<>
<Head>
{/*
{/*
meta tags
- next advised against using _document for this, so they exist in our
- next advised against using _document for this, so they exist in our
- single page index.page.tsx
*/}
{/* preload fonts */}
{/* icons */}
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
{/* fonts */}
<link
rel="preload"
href="/AlphaLyrae-Medium.woff2"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* icons */}
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" content="/favicon.ico" />
{/* styles */}
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
+43
View File
@@ -6,6 +6,10 @@ import { t } from '@vegaprotocol/i18n';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import trimEnd from 'lodash/trimEnd';
import { LayoutWithSidebar } from '../components/layouts';
import { LayoutWithSky } from '../client-pages/referrals/layout';
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
import { ApplyCodeForm } from '../client-pages/referrals/apply-code-form';
import { CreateCodeForm } from '../client-pages/referrals/create-code-form';
const LazyHome = dynamic(() => import('../client-pages/home'), {
ssr: false,
@@ -35,6 +39,13 @@ const LazyDeposit = dynamic(() => import('../client-pages/deposit'), {
ssr: false,
});
const LazyReferrals = dynamic(
() => import('../client-pages/referrals/referrals'),
{
ssr: false,
}
);
export enum Routes {
HOME = '/',
MARKET = '/markets/:marketId',
@@ -43,6 +54,10 @@ export enum Routes {
LIQUIDITY = '/liquidity/:marketId',
DISCLAIMER = '/disclaimer',
DEPOSIT = '/deposit',
REFERRALS = '/referrals',
REFERRALS_APPLY_CODE = '/referrals/apply-code',
REFERRALS_CREATE_CODE = '/referrals/create-code',
TEAMS = '/teams',
}
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
@@ -57,6 +72,10 @@ export const Links: ConsoleLinks = {
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
[Routes.REFERRALS]: () => Routes.REFERRALS,
[Routes.REFERRALS_APPLY_CODE]: () => Routes.REFERRALS_APPLY_CODE,
[Routes.REFERRALS_CREATE_CODE]: () => Routes.REFERRALS_CREATE_CODE,
[Routes.TEAMS]: () => Routes.TEAMS,
};
export const routerConfig: RouteObject[] = [
@@ -109,6 +128,30 @@ export const routerConfig: RouteObject[] = [
element: <LazyDisclaimer />,
},
{ path: Routes.DEPOSIT, element: <LazyDeposit /> },
// Referrals routing:
{
path: Routes.REFERRALS,
element: <LayoutWithSky />,
children: [
{
element: <LazyReferrals />,
children: [
{
index: true,
element: <ReferralStatistics />,
},
{
path: Routes.REFERRALS_APPLY_CODE,
element: <ApplyCodeForm />,
},
{
path: Routes.REFERRALS_CREATE_CODE,
element: <CreateCodeForm />,
},
],
},
],
},
{
path: '*',
element: (
+9 -3
View File
@@ -19,11 +19,17 @@ export default function Index() {
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="./favicon.ico" />
<meta name="twitter:card" content="./favicon.ico" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta name="twitter:image" content="./favicon.ico" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
</Head>
+3 -10
View File
@@ -1,13 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
/** Load AlphaLyrae font */
@font-face {
font-family: AlphaLyrae;
src: url('/AlphaLyrae-Medium.woff2') format('woff2'),
url('/AlphaLyrae-Medium.woff') format('woff');
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -110,8 +103,8 @@ html [data-theme='light'] {
--pennant-color-depth-sell-fill: theme(colors.market.red.DEFAULT);
--pennant-color-depth-sell-stroke: theme(colors.market.red.650);
--pennant-color-volume-buy: theme(colors.market.green.DEFAULT);
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
--pennant-color-volume-buy: theme(colors.market.green.300);
--pennant-color-volume-sell: theme(colors.market.red.300);
}
html [data-theme='dark'] {
@@ -132,7 +125,7 @@ html [data-theme='dark'] {
--pennant-color-depth-sell-stroke: theme(colors.market.red.DEFAULT);
--pennant-color-volume-buy: theme(colors.market.green.600);
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
--pennant-color-volume-sell: theme(colors.market.red.650);
}
/**
Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

+31 -1
View File
@@ -13,7 +13,37 @@ module.exports = {
],
darkMode: 'class',
theme: {
extend: theme,
extend: {
...theme,
colors: {
transparent: 'transparent',
current: 'currentColor',
...theme.colors,
},
backgroundImage: {
...theme.backgroundImage,
rainbow:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
'rainbow-shifted':
'linear-gradient(103.47deg, #0075FF 1.68%, #8028FF 47.49%, #FF077F 100%)',
highlight:
'linear-gradient(170deg, var(--tw-gradient-from), transparent var(--tw-gradient-to-position))',
},
keyframes: {
...theme.keyframes,
shake: {
'0%': { transform: 'translateX(0)' },
'25%': { transform: 'translateX(5px)' },
'50%': { transform: 'translateX(-5px)' },
'75%': { transform: 'translateX(5px)' },
'100%': { transform: 'translateX(0)' },
},
},
animation: {
...theme.animation,
shake: 'shake 200ms linear',
},
},
},
plugins: [vegaCustomClasses],
};
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
entrypoint="${1:-nginx}"
if [[ "$entrypoint" = "nginx" ]]; then
nginx -g 'daemon off;'
elif [[ "$entrypoint" = "ipfs" ]]; then
ipfs config profile apply server
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
ipfs daemon
elif [[ "-c" ]]; then
shift
/bin/sh -c "$@"
fi
+2 -1
View File
@@ -20,7 +20,8 @@ RUN sh docker/docker-build.sh
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
# configuration of system
EXPOSE 80
COPY docker/run-ipfs.sh /run-ipfs.sh
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
# Copy dist
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
+2 -1
View File
@@ -1,6 +1,7 @@
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
EXPOSE 80
COPY docker/run-ipfs.sh /run-ipfs.sh
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY ./dist-result/ /usr/share/nginx/html/
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
ipfs config profile apply server
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
ipfs daemon
+1 -1
View File
@@ -18,7 +18,7 @@ import {
VegaIconNames,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
IGetRowsParams,
IRowNode,
+1 -1
View File
@@ -13,7 +13,7 @@ import type {
VegaICellRendererParams,
} from '@vegaprotocol/datagrid';
import { ProgressBarCell } from '@vegaprotocol/datagrid';
import { AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import type { ColDef } from 'ag-grid-community';
import { accountValuesComparator } from './accounts-table';
import { MarginHealthChart } from './margin-health-chart';
+3 -5
View File
@@ -14,7 +14,6 @@ import {
export type AnnouncementBannerProps = {
app: AppNameType;
configUrl: string;
background?: string;
};
// run only if below the allowed maximum delay ~24.8 days (https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value)
@@ -37,7 +36,6 @@ const doesEndInTheFuture = (now: Date, data: Announcement) => {
export const AnnouncementBanner = ({
app,
configUrl,
background,
}: AnnouncementBannerProps) => {
const [isVisible, setVisible] = useState(false);
const { data, reload } = useAnnouncement(app, configUrl);
@@ -81,10 +79,10 @@ export const AnnouncementBanner = ({
}
return (
<Banner className="relative px-10" background={background}>
<Banner className="relative px-10">
<div
data-testid="app-announcement"
className="relative flex justify-center text-lg text-center text-white font-alpha gap-2"
className="relative font-alpha flex gap-2 justify-center text-center text-lg text-white"
>
<span>{data.text}</span>{' '}
{data.urlText && data.url && (
@@ -92,7 +90,7 @@ export const AnnouncementBanner = ({
)}
</div>
<button
className="absolute top-0 right-0 flex items-center justify-center w-10 h-full p-4 text-white"
className="absolute right-0 top-0 p-4 w-10 h-full flex items-center justify-center text-white"
data-testid="app-announcement-close"
onClick={() => {
setVisible(false);
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './lib/ag-grid/ag-grid';
export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/column-definitions';
@@ -0,0 +1,17 @@
import { forwardRef, lazy } from 'react';
import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
};
export const AgGridLazyInternal = lazy(() =>
import('./ag-grid-lazy-themed').then((module) => ({
default: module.AgGridThemed,
}))
);
export const AgGridLazy = forwardRef<AgGridReact, Props>((props, ref) => (
<AgGridLazyInternal {...props} gridRef={ref} />
));
-12
View File
@@ -1,12 +0,0 @@
import { forwardRef } from 'react';
import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
import { AgGridThemed } from './ag-grid-themed';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
};
export const AgGrid = forwardRef<AgGridReact, Props>((props, ref) => (
<AgGridThemed {...props} gridRef={ref} />
));
@@ -1,6 +1,9 @@
import { act, render, waitFor } from '@testing-library/react';
import { useDataGridEvents } from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-themed';
import {
useDataGridEvents,
GRID_EVENT_DEBOUNCE_TIME,
} from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-lazy-themed';
import type { MutableRefObject } from 'react';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
@@ -16,29 +19,25 @@ const gridProps = {
],
style: { width: 500, height: 300 },
};
const GRID_EVENT_DEBOUNCE_TIME = 300;
let gridRef: MutableRefObject<AgGridReact | null>;
function TestComponent({
hookParams,
}: {
hookParams: Parameters<typeof useDataGridEvents>;
}) {
const hookCallbacks = useDataGridEvents(...hookParams);
gridRef = useRef<AgGridReact | null>(null);
return <AgGridThemed gridRef={gridRef} {...gridProps} {...hookCallbacks} />;
}
// Not using render hook so I can pass event callbacks
// to a rendered grid
function setup(...args: Parameters<typeof useDataGridEvents>) {
return render(<TestComponent hookParams={args} />);
let gridRef;
function TestComponent() {
const hookCallbacks = useDataGridEvents(...args);
gridRef = useRef<AgGridReact | null>(null);
return <AgGridThemed gridRef={gridRef} {...gridProps} {...hookCallbacks} />;
}
render(<TestComponent />);
return gridRef as unknown as MutableRefObject<AgGridReact>;
}
describe('useDataGridEvents', () => {
const originalWarn = console.warn;
beforeAll(() => {
gridRef = undefined;
jest.useFakeTimers();
// disabling some ag grid warnings that are caused by test setup only
@@ -57,15 +56,15 @@ describe('useDataGridEvents', () => {
columnState: undefined,
};
setup(initialState, callback);
const result = setup(initialState, callback);
// column state was not updated, so the default width provided by the
// col def should be set
expect(gridRef.current?.columnApi.getColumnState()[0].width).toEqual(
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
gridProps.columnDefs[0].width
);
// no filters set
expect(gridRef.current?.api.getFilterModel()).toEqual({});
expect(result.current.api.getFilterModel()).toEqual({});
// Set filter
const idFilter = {
@@ -74,7 +73,7 @@ describe('useDataGridEvents', () => {
type: 'equals',
};
await act(async () => {
gridRef.current?.api.setFilterModel({
result.current.api.setFilterModel({
id: idFilter,
});
});
@@ -90,7 +89,7 @@ describe('useDataGridEvents', () => {
},
});
callback.mockClear();
expect(gridRef.current?.api.getFilterModel()['id']).toEqual(idFilter);
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
});
it('applies grid state on ready', async () => {
@@ -107,11 +106,11 @@ describe('useDataGridEvents', () => {
columnState: [colState],
};
setup(initialState, jest.fn());
const result = setup(initialState, jest.fn());
await waitFor(() => {
expect(gridRef.current?.api.getFilterModel()['id']).toEqual(idFilter);
expect(gridRef.current?.columnApi.getColumnState()[0]).toEqual(
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
expect(result.current.columnApi.getColumnState()[0]).toEqual(
expect.objectContaining(colState)
);
});
@@ -124,13 +123,13 @@ describe('useDataGridEvents', () => {
columnState: undefined,
};
setup(initialState, callback);
const result = setup(initialState, callback);
const newWidth = 400;
// Set col width multiple times
await act(async () => {
gridRef.current?.columnApi.setColumnWidth('id', newWidth);
result.current.columnApi.setColumnWidth('id', newWidth);
});
expect(callback).not.toHaveBeenCalled();
@@ -141,23 +140,4 @@ describe('useDataGridEvents', () => {
expect(callback).toHaveBeenCalledTimes(0);
});
it('columns for autosizing should be handle', () => {
const callback = jest.fn();
const initialState = {
filterModel: undefined,
columnState: undefined,
};
const { rerender } = setup(initialState, callback, ['id']);
jest.spyOn(gridRef.current?.columnApi, 'autoSizeColumns');
rerender(<TestComponent hookParams={[initialState, callback, ['id']]} />);
act(() => {
gridRef.current?.api.setRowData([{ id: 'test-id' }]);
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(gridRef.current?.columnApi.autoSizeColumns).toHaveBeenCalledWith([
'id',
]);
});
});
+2 -15
View File
@@ -6,7 +6,6 @@ import type {
FilterChangedEvent,
FirstDataRenderedEvent,
SortChangedEvent,
GridReadyEvent,
} from 'ag-grid-community';
import { useCallback } from 'react';
@@ -18,8 +17,7 @@ type State = {
export const useDataGridEvents = (
state: State,
callback: (data: State) => void,
autoSizeColumns?: string[]
callback: (data: State) => void
) => {
/**
* Callback for filter events
@@ -80,7 +78,7 @@ export const useDataGridEvents = (
* State only applied if found, otherwise columns sized to fit available space
*/
const onGridReady = useCallback(
({ api, columnApi }: GridReadyEvent) => {
({ api, columnApi }: FirstDataRenderedEvent) => {
if (!api || !columnApi) return;
if (state.columnState) {
@@ -99,16 +97,6 @@ export const useDataGridEvents = (
[state]
);
const onFirstDataRendered = useCallback(
({ columnApi }: FirstDataRenderedEvent) => {
if (!columnApi) return;
if (!state?.columnState && autoSizeColumns?.length) {
columnApi.autoSizeColumns(autoSizeColumns);
}
},
[state, autoSizeColumns]
);
return {
onGridReady,
// these events don't use the 'finished' flag
@@ -118,6 +106,5 @@ export const useDataGridEvents = (
// these trigger a lot so this callback uses the 'finished' flag
onColumnMoved: onDebouncedColumnChange,
onColumnResized: onDebouncedColumnChange,
onFirstDataRendered,
};
};
@@ -12,7 +12,6 @@ import { formatRange, formatValue } from '@vegaprotocol/utils';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import * as Schema from '@vegaprotocol/types';
import {
MARGIN_DIFF_TOOLTIP_TEXT,
@@ -38,16 +37,14 @@ export interface DealTicketFeeDetailsProps {
assetSymbol: string;
order: OrderSubmissionBody['orderSubmission'];
market: Market;
isMarketInAuction?: boolean;
}
export const DealTicketFeeDetails = ({
assetSymbol,
order,
market,
isMarketInAuction,
}: DealTicketFeeDetailsProps) => {
const feeEstimate = useEstimateFees(order, isMarketInAuction);
const feeEstimate = useEstimateFees(order);
const { settlementAsset: asset } =
market.tradableInstrument.instrument.product;
const { decimals: assetDecimals, quantum } = asset;
@@ -67,7 +64,7 @@ export const DealTicketFeeDetails = ({
<>
<span>
{t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
)}
</span>
<FeesBreakdown
@@ -90,7 +87,6 @@ export interface DealTicketMarginDetailsProps {
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
assetSymbol: string;
positionEstimate: EstimatePositionQuery['estimatePosition'];
side: Schema.Side;
}
export const DealTicketMarginDetails = ({
@@ -100,7 +96,6 @@ export const DealTicketMarginDetails = ({
market,
onMarketClick,
positionEstimate,
side,
}: DealTicketMarginDetailsProps) => {
const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet();
@@ -170,7 +165,10 @@ export const DealTicketMarginDetails = ({
: '0',
assetDecimals
)}
formattedValue={formatValue(
formattedValue={formatRange(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
@@ -189,7 +187,8 @@ export const DealTicketMarginDetails = ({
marginEstimate?.worstCase.initialLevel,
assetDecimals
)}
formattedValue={formatValue(
formattedValue={formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals,
quantum
@@ -201,7 +200,6 @@ export const DealTicketMarginDetails = ({
}
let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateRange = emptyValue;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
@@ -211,7 +209,8 @@ export const DealTicketMarginDetails = ({
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCase =
side === Schema.Side.SIDE_BUY
liquidationEstimateBestCaseIncludingBuyOrders >
liquidationEstimateBestCaseIncludingSellOrders
? liquidationEstimateBestCaseIncludingBuyOrders
: liquidationEstimateBestCaseIncludingSellOrders;
@@ -222,19 +221,14 @@ export const DealTicketMarginDetails = ({
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCase =
side === Schema.Side.SIDE_BUY
liquidationEstimateWorstCaseIncludingBuyOrders >
liquidationEstimateWorstCaseIncludingSellOrders
? liquidationEstimateWorstCaseIncludingBuyOrders
: liquidationEstimateWorstCaseIncludingSellOrders;
// The estimate order query API gives us the liquidation price in formatted by asset decimals.
// We need to calculate it with asset decimals, but display it with market decimals precision until the API changes.
liquidationPriceEstimate = formatValue(
liquidationEstimateWorstCase.toString(),
assetDecimals,
undefined,
market.decimalPlaces
);
liquidationPriceEstimateRange = formatRange(
liquidationPriceEstimate = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
@@ -290,9 +284,11 @@ export const DealTicketMarginDetails = ({
assetDecimals
) ?? '-'
}
noUnderline
>
<div className="font-mono text-right">
{formatValue(
{formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals,
quantum
@@ -350,7 +346,7 @@ export const DealTicketMarginDetails = ({
{projectedMargin}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}
value={liquidationPriceEstimate}
formattedValue={liquidationPriceEstimate}
symbol={quoteName}
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}
@@ -36,7 +36,7 @@ import {
formatValue,
} from '@vegaprotocol/utils';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { getDerivedPrice, isMarketInAuction } from '@vegaprotocol/markets';
import { getDerivedPrice } from '@vegaprotocol/markets';
import {
validateExpiration,
validateMarketState,
@@ -182,7 +182,6 @@ export const DealTicket = ({
const iceberg = watch('iceberg');
const peakSize = watch('peakSize');
const expiresAt = watch('expiresAt');
const postOnly = watch('postOnly');
useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size;
@@ -212,7 +211,6 @@ export const DealTicket = ({
size: rawSize,
timeInForce,
type,
postOnly,
},
market.id,
market.decimalPlaces,
@@ -221,7 +219,8 @@ export const DealTicket = ({
const price =
normalizedOrder &&
getDerivedPrice(normalizedOrder, marketPrice ?? undefined);
marketPrice &&
getDerivedPrice(normalizedOrder, marketPrice);
const notionalSize = getNotionalSize(
price,
@@ -475,7 +474,6 @@ export const DealTicket = ({
}
assetSymbol={assetSymbol}
market={market}
isMarketInAuction={isMarketInAuction(marketData.marketTradingMode)}
/>
</div>
<Controller
@@ -678,7 +676,6 @@ export const DealTicket = ({
)}
</Button>
<DealTicketMarginDetails
side={normalizedOrder.side}
onMarketClick={onMarketClick}
assetSymbol={assetSymbol}
marginAccountBalance={marginAccountBalance}
@@ -47,7 +47,7 @@ export const KeyValue = ({
<Tooltip description={labelDescription}>
<div className="text-muted">{label}</div>
</Tooltip>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`} noUnderline>
{valueElement}
</Tooltip>
</div>
@@ -1,78 +0,0 @@
import { renderHook } from '@testing-library/react';
import { useEstimateFees } from './use-estimate-fees';
import { Side, OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
const data: EstimateFeesQuery = {
estimateFees: {
totalFeeAmount: '12',
fees: {
infrastructureFee: '2',
liquidityFee: '4',
makerFee: '6',
},
},
};
const mockUseEstimateFeesQuery = jest.fn((...args) => ({
data,
}));
jest.mock('./__generated__/EstimateOrder', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useEstimateFeesQuery: jest.fn((...args) => mockUseEstimateFeesQuery(...args)),
}));
jest.mock('@vegaprotocol/wallet', () => ({
useVegaWallet: () => ({ pubKey: 'pubKey' }),
}));
describe('useEstimateFees', () => {
it('returns 0 as estimated values if order is postOnly', () => {
const { result } = renderHook(() =>
useEstimateFees({
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
postOnly: true,
})
);
expect(result.current).toEqual({
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
});
expect(mockUseEstimateFeesQuery.mock.lastCall?.[0].skip).toBeTruthy();
});
it('divide values by 2 if market is in auction', () => {
const { result } = renderHook(() =>
useEstimateFees(
{
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
},
true
)
);
expect(result.current).toEqual({
totalFeeAmount: '6',
fees: {
infrastructureFee: '1',
liquidityFee: '2',
makerFee: '3',
},
});
});
});
@@ -1,16 +1,13 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
isMarketInAuction?: boolean
): EstimateFeesQuery['estimateFees'] | undefined => {
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
const { data } = useEstimateFeesQuery({
variables: order && {
marketId: order.marketId,
@@ -22,28 +19,7 @@ export const useEstimateFees = (
type: order.type,
},
fetchPolicy: 'no-cache',
skip: !pubKey || !order?.size || !order?.price || order.postOnly,
skip: !pubKey || !order?.size || !order?.price,
});
if (order?.postOnly) {
return {
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
};
}
return isMarketInAuction && data?.estimateFees
? {
totalFeeAmount: divideByTwo(data.estimateFees.totalFeeAmount),
fees: {
infrastructureFee: divideByTwo(
data.estimateFees.fees.infrastructureFee
),
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
},
}
: data?.estimateFees;
return data?.estimateFees;
};
+1 -1
View File
@@ -6,7 +6,7 @@ import {
isNumeric,
} from '@vegaprotocol/utils';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
+5
View File
@@ -9,12 +9,14 @@ import { fillsWithMarketProvider } from './fills-data-provider';
interface FillsManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
gridProps: ReturnType<typeof useDataGridEvents>;
}
export const FillsManager = ({
partyId,
marketId,
onMarketClick,
gridProps,
}: FillsManagerProps) => {
@@ -22,6 +24,9 @@ export const FillsManager = ({
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
partyIds: [partyId],
};
if (marketId) {
filter.marketIds = [marketId];
}
const { data, error } = useDataProvider({
dataProvider: fillsWithMarketProvider,
update: ({ data }) => {
+2 -2
View File
@@ -15,7 +15,7 @@ import {
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import {
AgGrid,
AgGridLazy as AgGrid,
positiveClassNames,
negativeClassNames,
MarketNameCell,
@@ -300,7 +300,7 @@ const FeesBreakdownTooltip = ({
return (
<div
data-testid="fee-breakdown-tooltip"
className="z-20 max-w-sm px-4 py-2 text-sm text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white"
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
>
{role === MAKER && (
<>
+1 -1
View File
@@ -7,7 +7,7 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import type {
ColDef,
+1 -1
View File
@@ -175,7 +175,7 @@ export const Orderbook = ({
);
return (
<div className="h-full text-xs grid grid-rows-[1fr_min-content] overflow-hidden">
<div className="h-full text-xs grid grid-rows-[1fr_min-content]">
<div>
<ReactVirtualizedAutoSizer>
{({ width, height }) => {
@@ -60,7 +60,6 @@ export const FeesBreakdown = ({
.plus(fees.infrastructureFee)
.plus(fees.liquidityFee)
.toString();
if (totalFees === '0') return null;
const formatValue = (value: string | number | null | undefined): string => {
return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals)
+1 -1
View File
@@ -33,7 +33,7 @@ export const getDerivedPrice = (
type: Schema.OrderType;
price?: string | undefined;
},
marketPrice?: string
marketPrice: string
) => {
// If order type is market we should use either the mark price
// or the uncrossing price. If order type is limit use the price
+6 -2
View File
@@ -96,11 +96,15 @@ export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => {
const volume = Number(calcCandleVolume(m.candles || []) || 0);
const price = m.data?.markPrice ? Number(m.data.markPrice) : 0;
const quantum = Number(
m.tradableInstrument.instrument.product.settlementAsset.quantum
m.tradableInstrument.instrument.product.settlementAsset?.quantum
);
const decimals = Number(
m.tradableInstrument.instrument.product.settlementAsset.decimals
m.tradableInstrument.instrument.product.settlementAsset?.decimals
);
// `settlementAsset` can be undefined hence there's no quantum or decimals
if (isNaN(quantum) || isNaN(decimals)) return 0;
const fp = toBigNum(price, decimals);
const fq = toBigNum(quantum, decimals);
const factor = fq.multipliedBy(fp).multipliedBy(volume);
@@ -17,7 +17,7 @@ import {
import type { ForwardedRef } from 'react';
import { memo, forwardRef, useMemo } from 'react';
import {
AgGrid,
AgGridLazy as AgGrid,
SetFilter,
DateRangeFilter,
negativeClassNames,
@@ -79,7 +79,6 @@ export const OrderListTable = memo<
() => [
{
headerName: t('Market'),
colId: 'instrument-code',
field: 'market.tradableInstrument.instrument.code',
cellRenderer: 'MarketNameCell',
cellRendererParams: { idPath: 'market.id', onMarketClick },
@@ -18,7 +18,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { memo, useMemo } from 'react';
import {
AgGrid,
AgGridLazy as AgGrid,
SetFilter,
DateRangeFilter,
negativeClassNames,
+1 -1
View File
@@ -8,7 +8,7 @@ import type {
VegaICellRendererParams,
} from '@vegaprotocol/datagrid';
import {
AgGrid,
AgGridLazy as AgGrid,
COL_DEFS,
PriceFlashCell,
signedNumberCssClassRules,
@@ -100,8 +100,7 @@ describe('ProposalsList', () => {
const container = within(
document.querySelector(rowContainerSelector) as HTMLElement
);
expect(await container.findAllByRole('row')).toHaveLength(
expect(container.getAllByRole('row')).toHaveLength(
// @ts-ignore data is mocked
mock?.result?.data.proposalsConnection.edges.length
);
@@ -1,5 +1,5 @@
import type { FC } from 'react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types';
import { removePaginationWrapper } from '@vegaprotocol/utils';
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid, NumericCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, NumericCell } from '@vegaprotocol/datagrid';
import {
addDecimal,
addDecimalsFormatNumber,
@@ -4,27 +4,14 @@ import type { ReactNode } from 'react';
export interface BannerProps {
children?: ReactNode;
className?: string;
background?: string;
}
export const AnnouncementBanner = ({
className,
children,
background = 'url("https://static.vega.xyz/assets/img/banner-bg.jpg")',
}: BannerProps) => {
const bannerClasses = classnames('p-4', className);
return (
<div
className={bannerClasses}
style={{
background,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
{children}
</div>
export const AnnouncementBanner = ({ className, children }: BannerProps) => {
const bannerClasses = classnames(
"bg-[url('https://static.vega.xyz/assets/img/banner-bg.jpg')] bg-cover bg-center bg-no-repeat",
'p-4',
className
);
return <div className={bannerClasses}>{children}</div>;
};
@@ -19,14 +19,14 @@ export interface TooltipProps {
align?: 'start' | 'center' | 'end';
side?: 'top' | 'right' | 'bottom' | 'left';
sideOffset?: number;
underline?: boolean;
noUnderline?: boolean;
}
export const TOOLTIP_TRIGGER_CLASS_NAME = (underline?: boolean) =>
classNames({
'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed':
underline,
});
export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) =>
classNames(
{ 'underline underline-offset-2': !noUnderline },
'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed'
);
// Conditionally rendered tooltip if description content is provided.
export const Tooltip = ({
@@ -36,12 +36,12 @@ export const Tooltip = ({
sideOffset,
align = 'start',
side = 'bottom',
underline,
noUnderline,
}: TooltipProps) =>
description ? (
<Provider delayDuration={200} skipDelayDuration={100}>
<Root open={open}>
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(underline)}>
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(noUnderline)}>
{children}
</Trigger>
{description && (
@@ -50,15 +50,11 @@ export type WalletType = 'injected' | 'jsonRpc' | 'view' | 'snap';
export interface VegaConnectDialogProps {
connectors: Connectors;
riskMessage?: ReactNode;
contentOnly?: boolean;
onClose?: () => void;
}
export const VegaConnectDialog = ({
connectors,
riskMessage,
contentOnly,
onClose,
}: VegaConnectDialogProps) => {
const { disconnect, acknowledgeNeeded } = useVegaWallet();
const vegaWalletDialogOpen = useVegaWalletDialogStore(
@@ -84,24 +80,19 @@ export const VegaConnectDialog = ({
// This value will already be in the cache, if it failed the app wont render
const { data } = useChainIdQuery();
const content = data && (
<ConnectDialogContainer
connectors={connectors}
appChainId={data.statistics.chainId}
riskMessage={riskMessage}
onClose={onClose}
/>
);
if (contentOnly) {
return content;
}
return (
<Dialog
open={vegaWalletDialogOpen}
size="small"
onChange={onVegaWalletDialogChange}
>
{content}
{data && (
<ConnectDialogContainer
connectors={connectors}
appChainId={data.statistics.chainId}
riskMessage={riskMessage}
/>
)}
</Dialog>
);
};
@@ -110,20 +101,15 @@ const ConnectDialogContainer = ({
connectors,
appChainId,
riskMessage,
onClose,
}: {
connectors: Connectors;
appChainId: string;
riskMessage?: ReactNode;
onClose?: () => void;
}) => {
const { vegaUrl, vegaWalletServiceUrl } = useVegaWallet();
const closeVegaWalletDialog = useVegaWalletDialogStore(
const closeDialog = useVegaWalletDialogStore(
(store) => store.closeVegaWalletDialog
);
const closeDialog = useCallback(() => {
onClose ? onClose() : closeVegaWalletDialog();
}, [closeVegaWalletDialog, onClose]);
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
const [walletUrl, setWalletUrl] = useState(vegaWalletServiceUrl);
@@ -169,7 +155,8 @@ const ConnectDialogContainer = ({
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors['jsonRpc']
connectors['jsonRpc'],
appChainId
);
const snapStatus = useSnapStatus(
@@ -268,7 +255,7 @@ const ConnectorList = ({
</>
}
description={t(
`Connect with Vega Wallet extension
`Connect Vega Wallet extension
for %s to access all features including key
management and detailed transaction views from your
browser.`,
@@ -300,18 +287,8 @@ const ConnectorList = ({
{connectors['snap'] !== undefined ? (
<div>
{snapStatus === SnapStatus.INSTALLED ? (
<ConnectionOptionWithDescription
<ConnectionOption
type="snap"
title={
<>
<span>{t('Metamask Snap')}</span>
{' '}
<span className="text-xs"> {t('quick start')}</span>
</>
}
description={t(
`Connect directly via Metamask with the Vega Snap for single key support without advanced features.`
)}
text={
<>
<div className="flex items-center justify-center w-full h-full text-base gap-1">
@@ -339,7 +316,7 @@ const ConnectorList = ({
</>
}
description={t(
`Install Metamask with the Vega Snap for single key support without advanced features.`
`Connect directly via Metamask with the Vega Snap for single key support without advanced features.`
)}
text={
<>
@@ -131,12 +131,7 @@ const Error = ({
);
if (error) {
if (error.message === InjectedConnectorErrors.USER_REJECTED.message) {
title = t('User rejected');
text = t('The user rejected the wallet connection');
} else if (
error.message === InjectedConnectorErrors.INVALID_CHAIN.message
) {
if (error.message === InjectedConnectorErrors.INVALID_CHAIN.message) {
title = t('Wrong network');
text = t(
'To complete your wallet connection, set your wallet network in your app to "%s".',
@@ -42,7 +42,6 @@ declare global {
}
export const InjectedConnectorErrors = {
USER_REJECTED: new Error('Connection denied'),
VEGA_UNDEFINED: new Error('window.vega not found'),
INVALID_CHAIN: new Error('Invalid chain'),
};
+21 -1
View File
@@ -388,6 +388,24 @@ export interface TransferBody {
transfer: Transfer;
}
export type ApplyReferralCode = {
applyReferralCode: {
id: string;
};
};
export type CreateReferralSet = {
createReferralSet: {
isTeam: boolean;
team?: {
name: string;
teamUrl?: string;
avatarUrl?: string;
closed: boolean;
};
};
};
export type Transaction =
| StopOrdersSubmissionBody
| StopOrdersCancellationBody
@@ -401,7 +419,9 @@ export type Transaction =
| ProposalSubmissionBody
| BatchMarketInstructionSubmissionBody
| TransferBody
| LiquidityProvisionSubmission;
| LiquidityProvisionSubmission
| ApplyReferralCode
| CreateReferralSet;
export const isWithdrawTransaction = (
transaction: Transaction
@@ -1,41 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { JsonRpcConnector } from './connectors';
import { useIsWalletServiceRunning } from './use-is-wallet-service-running';
describe('useIsWalletServiceRunning', () => {
it('returns true if wallet is running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockResolvedValue(true);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(true);
});
});
it('returns false if wallet is not running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockRejectedValue(false);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(false);
});
});
});
@@ -1,39 +0,0 @@
import { useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined
) => {
const [isRunning, setIsRunning] = useState<boolean | null>(null);
useEffect(() => {
if (!connector) return;
if (url && url !== connector.url) {
connector.url = url;
}
const check = async () => {
try {
// we are not checking wallet compatibility here, only that the wallet is running
await connector.checkCompat();
setIsRunning(true);
} catch {
setIsRunning(false);
}
};
// check immediately
check();
// check every second for quick feedback to the user
const interval = setInterval(check, 1000);
return () => {
clearInterval(interval);
};
}, [connector, url]);
return isRunning;
};

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