Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df8dc555a | ||
|
|
c06c580fe3 |
@@ -155,10 +155,10 @@ jobs:
|
||||
- name: Sanity check docker image
|
||||
run: |
|
||||
echo "Check 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
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash
|
||||
echo "List html directory"
|
||||
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'apk add --update tree; tree /usr/share/nginx/html'
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
|
||||
|
||||
- name: Publish dist as docker image (ghcr)
|
||||
uses: docker/build-push-action@v3
|
||||
|
||||
@@ -113,20 +113,6 @@ In order to run a container on port 3000:
|
||||
docker run -p 3000:80 [TAG]
|
||||
```
|
||||
|
||||
On top of that there are two possible scenarios for running docker image - using nginx server (default) of ipfs daemon.
|
||||
|
||||
to run ipfs on port 3000:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:80 [TAG] ipfs
|
||||
```
|
||||
|
||||
to run nginx on port 3000:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:80 [TAG]
|
||||
```
|
||||
|
||||
## Build instructions
|
||||
|
||||
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
|
||||
@@ -164,7 +150,7 @@ As a prerequisite you need to perform build of `dist` directory and move its con
|
||||
You can build any of the containers locally with the following command:
|
||||
|
||||
```bash
|
||||
docker build -f docker/node-outside-docker.Dockerfile . --tag=[TAG]
|
||||
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
|
||||
```
|
||||
|
||||
### Verifying ipfs-hash of existing current application version
|
||||
|
||||
@@ -33,7 +33,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
]);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const requiredMajorityPercentage = useMemo(() => {
|
||||
const requiredMajority =
|
||||
params?.governance_proposal_market_requiredMajority ?? 1;
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export const BundleSigners = ({
|
||||
tx,
|
||||
id,
|
||||
}: BundleSignersProps) => {
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
|
||||
const bridgeFunction: BridgeFunction =
|
||||
tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20
|
||||
|
||||
@@ -139,7 +139,7 @@ export const ValidatorsPage = () => {
|
||||
const [vegaDialog, setVegaDialog] = useState<boolean>(false);
|
||||
const [tmDialog, setTmDialog] = useState<boolean>(false);
|
||||
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -212,7 +212,7 @@ context(
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
cy.get(`[row-id="${0}"]`)
|
||||
.first()
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.should('have.text', '3,002.00')
|
||||
@@ -222,7 +222,7 @@ context(
|
||||
.and('be.visible');
|
||||
});
|
||||
cy.get(`[row-id="${1}"]`)
|
||||
.first()
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.scrollIntoView()
|
||||
|
||||
+4
-10
@@ -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
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('deposit actions', { tags: '@smoke' }, () => {
|
||||
cy.visit('/#/markets/market-1');
|
||||
});
|
||||
|
||||
it.skip('Deposit to trade is visible', () => {
|
||||
it('Deposit to trade is visible', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
const accordionContent = 'accordion-content';
|
||||
const blockExplorerLink = 'block-explorer-link';
|
||||
const dialogClose = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const externalLink = 'external-link';
|
||||
const githubLink = 'github-link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const marketInfoBtn = 'Info';
|
||||
const marketTitle = 'accordion-title';
|
||||
const providerName = 'provider-name';
|
||||
const row = 'key-value-table-row';
|
||||
const verifiedProofs = 'verified-proofs';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId(marketInfoBtn).click();
|
||||
cy.wait('@MarketInfo');
|
||||
});
|
||||
|
||||
it('current fees displayed', () => {
|
||||
// 6002-MDET-101
|
||||
cy.getByTestId(marketTitle).contains('Current fees').click();
|
||||
validateMarketDataRow(0, 'Maker Fee', '0.02%');
|
||||
validateMarketDataRow(1, 'Infrastructure Fee', '0.05%');
|
||||
validateMarketDataRow(2, 'Liquidity Fee', '1.00%');
|
||||
validateMarketDataRow(3, 'Total Fees', '1.07%');
|
||||
});
|
||||
|
||||
it('market price', () => {
|
||||
// 6002-MDET-102
|
||||
cy.getByTestId(marketTitle).contains('Market price').click();
|
||||
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
|
||||
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
|
||||
validateMarketDataRow(2, 'Best Offer Price', '48,126.90058 ');
|
||||
validateMarketDataRow(3, 'Quote Unit', 'BTC');
|
||||
});
|
||||
|
||||
it('market volume displayed', () => {
|
||||
// 6002-MDET-103
|
||||
cy.getByTestId(marketTitle).contains('Market volume').click();
|
||||
validateMarketDataRow(1, 'Open Interest', '-');
|
||||
validateMarketDataRow(2, 'Best Bid Volume', '1');
|
||||
validateMarketDataRow(3, 'Best Offer Volume', '3');
|
||||
validateMarketDataRow(4, 'Best Static Bid Volume', '2');
|
||||
validateMarketDataRow(5, 'Best Static Offer Volume', '4');
|
||||
});
|
||||
|
||||
it('insurance pool displayed', () => {
|
||||
// 6002-MDET-104
|
||||
cy.getByTestId(marketTitle).contains('Insurance pool').click();
|
||||
validateMarketDataRow(0, 'Balance', '0');
|
||||
});
|
||||
|
||||
it('key details displayed', () => {
|
||||
// 6002-MDET-201
|
||||
cy.getByTestId(marketTitle).contains('Key details').click();
|
||||
|
||||
const rows: [string, string][] = compact([
|
||||
['Name', 'BTCUSD Monthly (30 Jun 2022)'],
|
||||
['Market ID', 'market-0'],
|
||||
Cypress.env('NX_SUCCESSOR_MARKETS') && ['Parent Market ID', 'PARENT-A'],
|
||||
Cypress.env('NX_SUCCESSOR_MARKETS') && [
|
||||
'Insurance Pool Fraction',
|
||||
'0.75',
|
||||
],
|
||||
['Trading Mode', MarketTradingModeMapping.TRADING_MODE_CONTINUOUS],
|
||||
['Market Decimal Places', '5'],
|
||||
['Position Decimal Places', '0'],
|
||||
['Settlement Asset Decimal Places', '5'],
|
||||
]);
|
||||
|
||||
for (const rowNumber in rows) {
|
||||
const [name, value] = rows[rowNumber];
|
||||
validateMarketDataRow(Number(rowNumber), name, value);
|
||||
}
|
||||
});
|
||||
|
||||
it('instrument displayed', () => {
|
||||
// 6002-MDET-202
|
||||
cy.getByTestId(marketTitle).contains('Instrument').click();
|
||||
|
||||
validateMarketDataRow(0, 'Market Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
validateMarketDataRow(1, 'Code', 'BTCUSD.MF21');
|
||||
validateMarketDataRow(2, 'Product Type', 'Future');
|
||||
validateMarketDataRow(3, 'Quote Name', 'BTC');
|
||||
});
|
||||
|
||||
it('oracle displayed', () => {
|
||||
// 6002-MDET-203
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(providerName)
|
||||
.and('contain', 'Another oracle');
|
||||
|
||||
cy.getByTestId(providerName).should('be.visible').click();
|
||||
cy.getByTestId(dialogContent)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(blockExplorerLink).contains('Block explorer');
|
||||
cy.getByTestId(githubLink).contains('Oracle repository');
|
||||
});
|
||||
cy.getByTestId(dialogClose).click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(verifiedProofs)
|
||||
.and('contain', '1');
|
||||
});
|
||||
|
||||
it('settlement asset displayed', () => {
|
||||
// 6002-MDET-206
|
||||
cy.getByTestId(marketTitle).contains('Settlement asset').click();
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
|
||||
});
|
||||
validateMarketDataRow(0, 'ID', 'asset-id');
|
||||
validateMarketDataRow(1, 'Type', 'ERC20');
|
||||
validateMarketDataRow(2, 'Name', 'Euro');
|
||||
validateMarketDataRow(3, 'Symbol', 'tEURO');
|
||||
validateMarketDataRow(4, 'Decimals', '5');
|
||||
validateMarketDataRow(5, 'Quantum', '1');
|
||||
validateMarketDataRow(6, 'Status', 'Enabled');
|
||||
validateMarketDataRow(7, 'Contract address', '0x0158…78a4');
|
||||
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
||||
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
||||
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
||||
validateMarketDataRow(11, 'Global reward pool account balance', '0.00002');
|
||||
});
|
||||
|
||||
it('metadata displayed', () => {
|
||||
// 6002-MDET-207
|
||||
cy.getByTestId(marketTitle).contains('Metadata').click();
|
||||
|
||||
validateMarketDataRow(0, 'Formerly', '076BB86A5AA41E3E');
|
||||
validateMarketDataRow(1, 'Base', 'BTC');
|
||||
validateMarketDataRow(2, 'Quote', 'USD');
|
||||
validateMarketDataRow(3, 'Class', 'fx/crypto');
|
||||
validateMarketDataRow(4, 'Sector', 'crypto');
|
||||
});
|
||||
|
||||
it('risk model displayed', () => {
|
||||
// 6002-MDET-208
|
||||
cy.getByTestId(marketTitle).contains('Risk model').click();
|
||||
validateMarketDataRow(0, 'Tau', '0.0001140771161');
|
||||
validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
|
||||
});
|
||||
|
||||
it('risk parameters displayed', () => {
|
||||
// 6002-MDET-209
|
||||
cy.getByTestId(marketTitle).contains('Risk parameters').click();
|
||||
validateMarketDataRow(0, 'R', '0.016');
|
||||
validateMarketDataRow(1, 'Sigma', '0.3');
|
||||
});
|
||||
|
||||
it('risk factors displayed', () => {
|
||||
// 6002-MDET-210
|
||||
cy.getByTestId(marketTitle).contains('Risk factors').click();
|
||||
|
||||
validateMarketDataRow(0, 'Short', '0.008571790367285281');
|
||||
validateMarketDataRow(1, 'Long', '0.008508132993273576');
|
||||
});
|
||||
|
||||
it('price monitoring bounds displayed', () => {
|
||||
// 6002-MDET-211
|
||||
cy.getByTestId(marketTitle).contains('Price monitoring bounds 1').click();
|
||||
cy.get('p.col-span-1').contains('99.99999% probability price bounds');
|
||||
cy.get('p.col-span-1').contains('Within 43,200 seconds');
|
||||
validateMarketDataRow(0, 'Highest Price', '7.97323 ');
|
||||
validateMarketDataRow(1, 'Lowest Price', '6.54701 ');
|
||||
});
|
||||
|
||||
it('liquidity monitoring parameters displayed', () => {
|
||||
// 6002-MDET-212
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0.7');
|
||||
validateMarketDataRow(1, 'Time Window', '3,600');
|
||||
validateMarketDataRow(2, 'Scaling Factor', '10');
|
||||
});
|
||||
|
||||
it('liquidity displayed', () => {
|
||||
// 6002-MDET-213
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains(/Liquidity(?! m)/)
|
||||
.click();
|
||||
|
||||
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
|
||||
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
|
||||
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
});
|
||||
|
||||
it('liquidity price range displayed', () => {
|
||||
// 6002-MDET-214
|
||||
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
|
||||
|
||||
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
|
||||
validateMarketDataRow(1, 'Lowest Price', '45,204.362 BTC');
|
||||
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
|
||||
});
|
||||
|
||||
it('proposal displayed', () => {
|
||||
// 6002-MDET-301
|
||||
cy.getByTestId(marketTitle).contains('Proposal').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.first()
|
||||
.should('have.text', 'View governance proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('contain', '/proposals/market-0');
|
||||
cy.getByTestId(accordionContent)
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.eq(1)
|
||||
.should('have.text', 'Propose a change to market')
|
||||
.and('have.attr', 'href')
|
||||
.and('contain', '/proposals/propose/update-market');
|
||||
});
|
||||
|
||||
afterEach('close toggle', () => {
|
||||
cy.get('[data-state="open"]').then((tab) => {
|
||||
if (tab) tab.find('button').trigger('click');
|
||||
});
|
||||
});
|
||||
|
||||
function validateMarketDataRow(
|
||||
rowNumber: number,
|
||||
name: string,
|
||||
value: string
|
||||
) {
|
||||
cy.getByTestId(row)
|
||||
.eq(rowNumber)
|
||||
.within(() => {
|
||||
cy.get('dt').should('contain.text', name);
|
||||
cy.get('dd').should('contain.text', value);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -10,10 +10,10 @@ const dialogContent = 'dialog-content';
|
||||
describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
// Using portfolio page as it requires vega wallet connection
|
||||
cy.visit('/#/portfolio');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
|
||||
+1
-2
@@ -16,14 +16,13 @@ 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
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
# Cosmic elevator flags
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
|
||||
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.21.2-core-0.72.14
|
||||
NX_APP_VERSION=v0.21.1-core-0.72.14
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -1,58 +1,18 @@
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import { GetStarted } from '../../components/welcome-dialog';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { GetStartedCheckList } from '../../components/welcome-dialog';
|
||||
import {
|
||||
useGetOnboardingStep,
|
||||
useOnboardingStore,
|
||||
OnboardingStep,
|
||||
} from '../../components/welcome-dialog/use-get-onboarding-step';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const Deposit = () => {
|
||||
return (
|
||||
<div className="max-w-[600px] px-4 py-8 mx-auto lg:px-8">
|
||||
<h1 className="mb-6 text-4xl uppercase xl:text-5xl font-alpha calt">
|
||||
{t('Deposit')}
|
||||
</h1>
|
||||
<div className="flex flex-col gap-6">
|
||||
<DepositContainer />
|
||||
<DepositGetStarted />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DepositGetStarted = () => {
|
||||
const onboardingDismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const step = useGetOnboardingStep();
|
||||
const wrapperClasses = classNames(
|
||||
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||
'bg-vega-blue-300 dark:bg-vega-blue-700',
|
||||
'border border-vega-blue-350 dark:border-vega-blue-650'
|
||||
);
|
||||
|
||||
// Dont show unless still onboarding
|
||||
if (onboardingDismissed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pt-6 border-t border-default">
|
||||
<div className={wrapperClasses}>
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<GetStartedCheckList />
|
||||
{step > OnboardingStep.ONBOARDING_DEPOSIT_STEP && (
|
||||
<TradingAnchorButton
|
||||
href={Links[Routes.HOME]()}
|
||||
onClick={() => dismiss()}
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{t('Start trading')}
|
||||
</TradingAnchorButton>
|
||||
)}
|
||||
<div className="py-16 px-8 flex w-full justify-center">
|
||||
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
|
||||
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
|
||||
{t('Deposit')}
|
||||
</h1>
|
||||
<div className="mt-10">
|
||||
<DepositContainer />
|
||||
<GetStarted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { marketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
|
||||
// The home pages only purpose is to redirect to the users last market,
|
||||
// the top traded if they are new, or fall back to the list of markets.
|
||||
// Thats why we just render a loader here
|
||||
export const Home = () => {
|
||||
const navigate = useNavigate();
|
||||
const { data } = useTopTradedMarkets();
|
||||
// The default market selected in the platform behind the overlay
|
||||
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider: marketsWithDataProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -28,11 +32,12 @@ export const Home = () => {
|
||||
navigate(Links[Routes.MARKETS]());
|
||||
}
|
||||
}
|
||||
}, [marketId, data, navigate]);
|
||||
}, [marketId, data, navigate, update]);
|
||||
|
||||
return (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
<AsyncRenderer data={data} loading={loading} error={error}>
|
||||
{/* Render a loading and error state but we will redirect if markets are found */}
|
||||
{null}
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ import { TradePanels } from './trade-panels';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||
return markPrice && decimalPlaces
|
||||
@@ -59,9 +58,7 @@ const TitleUpdater = ({
|
||||
export const MarketPage = () => {
|
||||
const { marketId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { init, view, setView } = useSidebar();
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
@@ -72,14 +69,15 @@ export const MarketPage = () => {
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
update({ marketId: data.id });
|
||||
// make sidebar open on market id change
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews({ type: ViewType.Order }, currentRouteId);
|
||||
// make sidebar open on deal ticket by default
|
||||
if (view === null) {
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
}, [update, lastMarketId, data?.id, setView, init, view]);
|
||||
|
||||
const tradeView = useMemo(() => {
|
||||
if (largeScreen) {
|
||||
|
||||
@@ -56,7 +56,6 @@ const MainGrid = memo(
|
||||
<Tabs storageKey="console-trade-grid-main-left">
|
||||
<Tab
|
||||
id="chart"
|
||||
overflowHidden
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
>
|
||||
@@ -73,7 +72,7 @@ const MainGrid = memo(
|
||||
</ResizableGridPanel>
|
||||
<ResizableGridPanel
|
||||
minSize={200}
|
||||
preferredSize={sizesMiddle[1] || 275}
|
||||
preferredSize={sizesMiddle[1] || 300}
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-right">
|
||||
@@ -181,7 +180,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
|
||||
{({ width, height }) => (
|
||||
<div
|
||||
style={{ width, height }}
|
||||
className="border rounded-sm border-default"
|
||||
className="border border-default rounded-sm"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,8 @@ export const MarketsPage = () => {
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
|
||||
const governanceLink = useLinks(DApp.Governance);
|
||||
const externalLink = governanceLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
|
||||
useEffect(() => {
|
||||
updateTitle(titlefy(['Markets']));
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { AccountsMenu } from '../../components/accounts-menu';
|
||||
import { DepositsMenu } from '../../components/deposits-menu';
|
||||
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
@@ -38,10 +37,7 @@ const WithdrawalsIndicator = () => {
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { getView, setViews } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
|
||||
const { init, view, setView } = useSidebar();
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
@@ -52,10 +48,10 @@ export const Portfolio = () => {
|
||||
|
||||
// Make transfer sidebar open by default
|
||||
useEffect(() => {
|
||||
if (view === undefined) {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
if (init && view === null) {
|
||||
setView({ type: ViewType.Transfer });
|
||||
}
|
||||
}, [view, setViews, currentRouteId]);
|
||||
}, [init, view, setView]);
|
||||
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
||||
|
||||
@@ -12,7 +12,6 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
@@ -22,8 +21,7 @@ export const AccountsContainer = ({
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
const gridStore = useAccountStore((store) => store.gridStore);
|
||||
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
||||
@@ -51,13 +49,13 @@ export const AccountsContainer = ({
|
||||
partyId={pubKey}
|
||||
onClickAsset={onClickAsset}
|
||||
onClickWithdraw={(assetId) => {
|
||||
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
|
||||
setView({ type: ViewType.Withdraw, assetId });
|
||||
}}
|
||||
onClickDeposit={(assetId) => {
|
||||
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
|
||||
setView({ type: ViewType.Deposit, assetId });
|
||||
}}
|
||||
onClickTransfer={(assetId) => {
|
||||
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
|
||||
setView({ type: ViewType.Transfer, assetId });
|
||||
}}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const AccountsMenu = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setViews({ type: ViewType.Transfer }, currentRouteId)}
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
>
|
||||
{t('Transfer')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</TradingButton>
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
export const THROTTLE_UPDATE_TIME = 500;
|
||||
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const DepositsMenu = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
>
|
||||
{t('Deposit')}
|
||||
|
||||
@@ -4,19 +4,16 @@ import classNames from 'classnames';
|
||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||
import { MarketHeader } from '../market-header';
|
||||
import { LiquidityHeader } from '../liquidity-header';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const LayoutWithSidebar = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const views = useSidebar((store) => store.views);
|
||||
const sidebarView = views[currentRouteId] || null;
|
||||
const sidebarView = useSidebar((store) => store.view);
|
||||
const sidebarOpen = sidebarView !== null;
|
||||
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid-rows-[min-content_1fr_40px]',
|
||||
'lg:grid-rows-[min-content_1fr]',
|
||||
'lg:grid-cols-[1fr_280px_40px]',
|
||||
'xxxl:grid-cols-[1fr_320px_40px]'
|
||||
'lg:grid-cols-[1fr_350px_40px]'
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MarketSuccessorProposalBanner = ({
|
||||
?.successorConfiguration?.parentMarketId === marketId
|
||||
) ?? [];
|
||||
const [visible, setVisible] = useState(true);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
if (visible && successors.length) {
|
||||
return (
|
||||
<NotificationBanner
|
||||
|
||||
@@ -186,7 +186,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarLinkExternal to={useLinks(DApp.Governance)()}>
|
||||
<NavbarLinkExternal to={useLinks(DApp.Token)()}>
|
||||
{t('Governance')}
|
||||
</NavbarLinkExternal>
|
||||
</NavbarItem>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { OrderbookManager } from '@vegaprotocol/market-depth';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const update = useDealTicketFormValues((state) => state.updateAll);
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
return (
|
||||
<OrderbookManager
|
||||
marketId={marketId}
|
||||
onClick={(values) => {
|
||||
update(marketId, values);
|
||||
setViews({ type: ViewType.Order }, currentRouteId);
|
||||
setView({ type: ViewType.Order });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,6 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||
|
||||
jest.mock('../node-health', () => ({
|
||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||
@@ -116,11 +115,7 @@ describe('SidebarContent', () => {
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/markets/:marketId"
|
||||
id={AppRoutes.MARKET}
|
||||
element={<SidebarContent />}
|
||||
/>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletContext.Provider>
|
||||
@@ -129,17 +124,13 @@ describe('SidebarContent', () => {
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
|
||||
act(() => {
|
||||
useSidebar.setState({
|
||||
views: { [AppRoutes.MARKET]: { type: ViewType.Transfer } },
|
||||
});
|
||||
useSidebar.setState({ view: { type: ViewType.Transfer } });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('transfer')).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
useSidebar.setState({
|
||||
views: { [AppRoutes.MARKET]: { type: ViewType.Deposit } },
|
||||
});
|
||||
useSidebar.setState({ view: { type: ViewType.Deposit } });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('deposit')).toBeInTheDocument();
|
||||
@@ -150,36 +141,26 @@ describe('SidebarContent', () => {
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/portfolio"
|
||||
id={AppRoutes.PORTFOLIO}
|
||||
element={<SidebarContent />}
|
||||
/>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
useSidebar.setState({
|
||||
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Order } },
|
||||
});
|
||||
useSidebar.setState({ view: { type: ViewType.Order } });
|
||||
});
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
|
||||
act(() => {
|
||||
useSidebar.setState({
|
||||
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Settings } },
|
||||
});
|
||||
useSidebar.setState({ view: { type: ViewType.Settings } });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('settings')).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
useSidebar.setState({
|
||||
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Info } },
|
||||
});
|
||||
useSidebar.setState({ view: { type: ViewType.Info } });
|
||||
});
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
@@ -197,7 +178,6 @@ describe('SidebarButton', () => {
|
||||
tooltip="INFO"
|
||||
onClick={onClick}
|
||||
view={view}
|
||||
routeId="current-route-id"
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ import { Settings } from '../settings';
|
||||
import { Tooltip } from '../../components/tooltip';
|
||||
import { WithdrawContainer } from '../withdraw-container';
|
||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { GetStarted } from '../welcome-dialog';
|
||||
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
|
||||
export enum ViewType {
|
||||
Order = 'Order',
|
||||
@@ -52,31 +51,27 @@ type SidebarView =
|
||||
};
|
||||
|
||||
export const Sidebar = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||
const { pubKeys } = useVegaWallet();
|
||||
return (
|
||||
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
|
||||
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
||||
<nav className={navClasses}>
|
||||
{/* sidebar options that always show */}
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
{/* buttons for specific routes */}
|
||||
<Routes>
|
||||
@@ -99,13 +94,11 @@ export const Sidebar = () => {
|
||||
view={ViewType.Order}
|
||||
icon={VegaIconNames.TICKET}
|
||||
tooltip={t('Order')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
@@ -121,13 +114,12 @@ export const Sidebar = () => {
|
||||
icon={VegaIconNames.EYE}
|
||||
tooltip={t('View as party')}
|
||||
disabled={Boolean(pubKeys)}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
|
||||
<SidebarButton
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
tooltip={t('Settings')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<NodeHealthContainer />
|
||||
</nav>
|
||||
@@ -141,25 +133,23 @@ export const SidebarButton = ({
|
||||
tooltip,
|
||||
disabled = false,
|
||||
onClick,
|
||||
routeId,
|
||||
}: {
|
||||
view?: ViewType;
|
||||
icon: VegaIconNames;
|
||||
tooltip: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
routeId: string;
|
||||
}) => {
|
||||
const { setViews, getView } = useSidebar((store) => ({
|
||||
setViews: store.setViews,
|
||||
getView: store.getView,
|
||||
const { currView, setView } = useSidebar((store) => ({
|
||||
currView: store.view,
|
||||
setView: store.setView,
|
||||
}));
|
||||
const currView = getView(routeId);
|
||||
|
||||
const onSelect = (view: SidebarView['type']) => {
|
||||
if (view === currView?.type) {
|
||||
setViews(null, routeId);
|
||||
setView(null);
|
||||
} else {
|
||||
setViews({ type: view }, routeId);
|
||||
setView({ type: view });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -197,7 +187,7 @@ export const SidebarButton = ({
|
||||
const SidebarDivider = () => {
|
||||
return (
|
||||
<div
|
||||
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
|
||||
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-px h-4 lg:w-4 lg:h-px"
|
||||
role="separator"
|
||||
/>
|
||||
);
|
||||
@@ -205,10 +195,8 @@ const SidebarDivider = () => {
|
||||
|
||||
export const SidebarContent = () => {
|
||||
const params = useParams();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { view, setView } = useSidebar();
|
||||
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
if (!view) return null;
|
||||
|
||||
if (view.type === ViewType.Order) {
|
||||
@@ -218,7 +206,7 @@ export const SidebarContent = () => {
|
||||
<DealTicketContainer
|
||||
marketId={params.marketId}
|
||||
onDeposit={(assetId) =>
|
||||
setViews({ type: ViewType.Deposit, assetId }, currentRouteId)
|
||||
setView({ type: ViewType.Deposit, assetId })
|
||||
}
|
||||
/>
|
||||
<GetStarted />
|
||||
@@ -245,6 +233,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Deposit')}>
|
||||
<DepositContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -253,6 +242,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Withdraw')}>
|
||||
<WithdrawContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -261,6 +251,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Transfer')}>
|
||||
<TransferContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -285,7 +276,7 @@ const ContentWrapper = ({
|
||||
}) => {
|
||||
return (
|
||||
<TinyScroll
|
||||
className="h-full py-4 pl-3 pr-4 overflow-auto"
|
||||
className="h-full overflow-auto py-4 pl-3 pr-4"
|
||||
// panes have p-1, since sidebar is on the right make pl less to account for additional pane space
|
||||
data-testid="sidebar-content"
|
||||
>
|
||||
@@ -297,21 +288,25 @@ const ContentWrapper = ({
|
||||
|
||||
/** If rendered will close sidebar */
|
||||
const CloseSidebar = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
useEffect(() => {
|
||||
setViews(null, currentRouteId);
|
||||
}, [setViews, currentRouteId]);
|
||||
setView(null);
|
||||
}, [setView]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useSidebar = create<{
|
||||
views: { [key: string]: SidebarView | null };
|
||||
setViews: (view: SidebarView | null, routeId: string) => void;
|
||||
getView: (routeId: string) => SidebarView | null | undefined;
|
||||
}>()((set, get) => ({
|
||||
views: {},
|
||||
setViews: (x, routeId) =>
|
||||
set(({ views }) => ({ views: { ...views, [routeId]: x } })),
|
||||
getView: (routeId) => get().views[routeId],
|
||||
init: boolean;
|
||||
view: SidebarView | null;
|
||||
setView: (view: SidebarView | null) => void;
|
||||
}>()((set) => ({
|
||||
init: true,
|
||||
view: null,
|
||||
setView: (x) =>
|
||||
set(() => {
|
||||
if (x == null) {
|
||||
return { view: null, init: false };
|
||||
}
|
||||
return { view: x, init: false };
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { Telemetry } from './telemetry';
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
|
||||
export const Telemetry = () => {
|
||||
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [closeTelemetry, removeToast]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[onApprovalClose, setTelemetryValue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isTelemetryNeeded && onboardingDissmissed) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [
|
||||
telemetryValue,
|
||||
isTelemetryNeeded,
|
||||
onboardingDissmissed,
|
||||
setToast,
|
||||
hasToast,
|
||||
onApprovalClose,
|
||||
setTelemetryApprovalAndClose,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -11,10 +11,6 @@ jest.mock('@vegaprotocol/wallet', () => ({
|
||||
useVegaWalletDialogStore: () => mockUpdateDialogOpen,
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/hooks/use-get-current-route-id', () => ({
|
||||
useGetCurrentRouteId: jest.fn().mockReturnValue('current-route-id'),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -22,15 +22,13 @@ import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import classNames from 'classnames';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const VegaWalletConnectButton = () => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
const {
|
||||
pubKey,
|
||||
pubKeys,
|
||||
@@ -97,7 +95,7 @@ export const VegaWalletConnectButton = () => {
|
||||
<TradingDropdownItem
|
||||
data-testid="wallet-transfer"
|
||||
onClick={() => {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
setView({ type: ViewType.Transfer });
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useVegaWallet, type PubKey } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const VegaWalletMenu = ({
|
||||
setMenu,
|
||||
@@ -18,8 +17,7 @@ export const VegaWalletMenu = ({
|
||||
setMenu: (open: 'nav' | 'wallet' | null) => void;
|
||||
}) => {
|
||||
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
const activeKey = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
@@ -48,7 +46,7 @@ export const VegaWalletMenu = ({
|
||||
<div className="flex flex-col gap-2 m-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
setView({ type: ViewType.Transfer });
|
||||
setMenu(null);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { GetStarted } from './get-started';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
let mockStep = 1;
|
||||
jest.mock('./use-get-onboarding-step', () => ({
|
||||
@@ -45,15 +44,13 @@ describe('GetStarted', () => {
|
||||
globalThis.window.vega = undefined as unknown as Vega;
|
||||
});
|
||||
|
||||
it('renders nothing if dismissed', () => {
|
||||
useOnboardingStore.setState({ dismissed: true });
|
||||
it('renders nothing if connected', () => {
|
||||
mockStep = 0;
|
||||
const { container } = renderComponent({ pubKey: 'my-pubkey' });
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('steps should be ticked', () => {
|
||||
useOnboardingStore.setState({ dismissed: false });
|
||||
const navigatorGetter: jest.SpyInstance = jest.spyOn(
|
||||
window.navigator,
|
||||
'userAgent',
|
||||
@@ -75,7 +72,7 @@ describe('GetStarted', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('link', { name: 'Deposit' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Deposit' })).toBeInTheDocument();
|
||||
|
||||
mockStep = 4;
|
||||
rerender(
|
||||
@@ -87,11 +84,9 @@ describe('GetStarted', () => {
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'Ready to trade' })
|
||||
screen.getByRole('button', { name: 'Ready to trade' })
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('link', { name: 'Ready to trade' }));
|
||||
|
||||
mockStep = 5;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
|
||||
@@ -3,113 +3,89 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
TradingAnchorButton,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
OnboardingStep,
|
||||
useGetOnboardingStep,
|
||||
useOnboardingStore,
|
||||
} from './use-get-onboarding-step';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useSidebar, ViewType } from '../sidebar';
|
||||
import * as constants from '../constants';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
|
||||
const buttonProps = {
|
||||
size: 'small' as const,
|
||||
'data-testid': 'get-started-button',
|
||||
intent: Intent.Info,
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
let buttonText = t('Get started');
|
||||
let onClickHandle = () => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
|
||||
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
return (
|
||||
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
|
||||
{t('Connect')}
|
||||
</TradingButton>
|
||||
);
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
return (
|
||||
<TradingAnchorButton
|
||||
{...buttonProps}
|
||||
href={Links[Routes.DEPOSIT]()}
|
||||
onClick={() => setDialogOpen(false)}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</TradingAnchorButton>
|
||||
);
|
||||
} else if (step >= OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
return (
|
||||
<TradingAnchorButton
|
||||
{...buttonProps}
|
||||
href={marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]()}
|
||||
onClick={() => {
|
||||
setViews({ type: ViewType.Order }, Routes.MARKET);
|
||||
dismiss();
|
||||
}}
|
||||
>
|
||||
{t('Ready to trade')}
|
||||
</TradingAnchorButton>
|
||||
);
|
||||
buttonText = t('Deposit');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Deposit });
|
||||
dismiss();
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Ready to trade');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Order });
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
|
||||
{t('Get started')}
|
||||
<TradingButton
|
||||
onClick={onClickHandle}
|
||||
size="small"
|
||||
data-testid="get-started-button"
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{buttonText}
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const GetStartedCheckList = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const currentStep = useGetOnboardingStep();
|
||||
return (
|
||||
<ul className="list-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export const GetStarted = ({ lead }: Props) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
|
||||
const getStartedNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
|
||||
const wrapperClasses = classNames(
|
||||
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||
@@ -118,13 +94,31 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{ 'mt-8': !lead }
|
||||
);
|
||||
|
||||
if (!dismissed) {
|
||||
if (getStartedNeeded) {
|
||||
return (
|
||||
<div className={wrapperClasses} data-testid="get-started-banner">
|
||||
{lead && <h2>{lead}</h2>}
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<div>
|
||||
<GetStartedCheckList />
|
||||
<ul className="list-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<GetStartedButton step={currentStep} />
|
||||
@@ -132,7 +126,7 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{VEGA_ENV === Networks.MAINNET && (
|
||||
<p className="text-sm">
|
||||
{t('Experiment for free with virtual assets on')}{' '}
|
||||
<ExternalLink href={VEGA_NETWORKS.TESTNET}>
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Fairground Testnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
@@ -140,7 +134,7 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{VEGA_ENV === Networks.TESTNET && (
|
||||
<p className="text-sm">
|
||||
{t('Ready to trade with real funds?')}{' '}
|
||||
<ExternalLink href={VEGA_NETWORKS.MAINNET}>
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Switch to Mainnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ProposedMarkets = () => {
|
||||
proposal.terms.change.instrument.code,
|
||||
}));
|
||||
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
return useMemo(
|
||||
() => (
|
||||
<div className="mt-7 pt-8 border-t border-default">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
@@ -9,29 +7,6 @@ import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
|
||||
export const useOnboardingStore = create<{
|
||||
dialogOpen: boolean;
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
setDialogOpen: (isOpen: boolean) => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dialogOpen: true,
|
||||
dismissed: false,
|
||||
dismiss: () => set({ dismissed: true }),
|
||||
setDialogOpen: (isOpen) => set({ dialogOpen: isOpen }),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
partialize: (state) => ({
|
||||
dismissed: state.dismissed,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export enum OnboardingStep {
|
||||
ONBOARDING_UNKNOWN_STEP,
|
||||
ONBOARDING_WALLET_STEP,
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { GetStarted } from './get-started';
|
||||
import { TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const setOnboardingDialog = useOnboardingStore(
|
||||
(store) => store.setDialogOpen
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const navigate = useNavigate();
|
||||
const { data } = useMarketList();
|
||||
const markets = orderBy(
|
||||
data?.filter((m) => isMarketActive(m.state)) || [],
|
||||
[
|
||||
(m) => {
|
||||
if (!m.candles?.length) return 0;
|
||||
return Number(priceChangePercentage(m.candles.map((c) => c.close)));
|
||||
},
|
||||
],
|
||||
['desc']
|
||||
);
|
||||
|
||||
const { data } = useTopTradedMarkets();
|
||||
const marketId = data && data[0]?.id;
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKETS]();
|
||||
|
||||
const explore = () => {
|
||||
const marketId = markets?.[0].id ?? '';
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
? t('Start trading on the worlds most advanced decentralised exchange.')
|
||||
@@ -27,7 +43,7 @@ export const WelcomeDialogContent = () => {
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row gap-8">
|
||||
<div className="flex flex-col justify-between pt-3 sm:w-1/2">
|
||||
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
|
||||
<ul className="ml-0">
|
||||
<ListItemContent
|
||||
icon={<NonCustodialIcon />}
|
||||
@@ -49,16 +65,15 @@ export const WelcomeDialogContent = () => {
|
||||
)}
|
||||
/>
|
||||
</ul>
|
||||
<TradingAnchorButton
|
||||
href={link}
|
||||
onClick={() => setOnboardingDialog(false)}
|
||||
<TradingButton
|
||||
onClick={explore}
|
||||
className="block w-full"
|
||||
data-testid="browse-markets-button"
|
||||
>
|
||||
{t('Explore')}
|
||||
</TradingAnchorButton>
|
||||
</TradingButton>
|
||||
</div>
|
||||
<div className="flex sm:w-1/2 grow">
|
||||
<div className="sm:w-1/2 flex grow">
|
||||
<GetStarted lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,10 +90,10 @@ const ListItemContent = ({
|
||||
text: string;
|
||||
}) => {
|
||||
return (
|
||||
<li className="flex my-4 gap-3">
|
||||
<div className="pt-1 shrink-0">{icon}</div>
|
||||
<li className="my-4 flex gap-3">
|
||||
<div className="shrink-0 pt-1">{icon}</div>
|
||||
<div>
|
||||
<h3 className="mb-2 text-lg leading-snug">{title}</h3>
|
||||
<h3 className="text-lg leading-snug mb-2">{title}</h3>
|
||||
<p className="text-sm text-secondary">{text}</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,32 +1,133 @@
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import {
|
||||
useGetOnboardingStep,
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import * as constants from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
|
||||
export const useOnboardingStore = create<{
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dismissed: false,
|
||||
dismiss: () => set(() => ({ dismissed: true })),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
|
||||
const navigate = useNavigate();
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const isTelemetryPopupNeeded =
|
||||
isTelemetryNeeded &&
|
||||
(onBoardingViewed === 'true' ||
|
||||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
|
||||
|
||||
return (
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP &&
|
||||
!dismissed;
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onClose = () => {
|
||||
if (isTelemetryPopupNeeded) {
|
||||
closeTelemetry();
|
||||
} else {
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [removeToast, closeTelemetry]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[setTelemetryValue, onApprovalClose]
|
||||
);
|
||||
|
||||
if (isTelemetryPopupNeeded) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{VEGA_ENV}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return isOnboardingDialogNeeded ? (
|
||||
<Dialog
|
||||
open={dismissed ? false : dialogOpen}
|
||||
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>
|
||||
}
|
||||
open
|
||||
title={title}
|
||||
size="medium"
|
||||
onChange={() => dismiss()}
|
||||
onChange={onClose}
|
||||
intent={Intent.None}
|
||||
dataTestId="welcome-dialog"
|
||||
>
|
||||
<WelcomeDialogContent />
|
||||
</Dialog>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const WithdrawalsMenu = () => {
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
onClick={() => setViews({ type: ViewType.Withdraw }, currentRouteId)}
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Make withdrawal')}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { routerConfig } from '../../pages/client-router';
|
||||
import { matchRoutes, useLocation } from 'react-router-dom';
|
||||
|
||||
export const useGetCurrentRouteId = () => {
|
||||
const location = useLocation();
|
||||
const currentRoute = matchRoutes(routerConfig, location);
|
||||
const lastRoute = currentRoute?.pop();
|
||||
if (lastRoute) {
|
||||
const {
|
||||
route: { id },
|
||||
} = lastRoute;
|
||||
return id || '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { isMarketActive } from '../utils';
|
||||
|
||||
export const useTopTradedMarkets = () => {
|
||||
const { data, loading, error } = useMarketList();
|
||||
|
||||
const activeMarkets = data?.filter((m) => isMarketActive(m.state));
|
||||
const marketsByTopTraded = data
|
||||
? orderBy(activeMarkets, (m) => calcTradedFactor(m), 'desc')
|
||||
: undefined;
|
||||
return { data: marketsByTopTraded, loading, error };
|
||||
};
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { NavHeader } from '../components/navbar/nav-header';
|
||||
import { Routes as AppRoutes } from './client-router';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -126,7 +125,6 @@ function AppBody({ Component }: AppProps) {
|
||||
<InitializeHandlers />
|
||||
<MaybeConnectEagerly />
|
||||
<PartyData />
|
||||
<Telemetry />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export const Links: ConsoleLinks = {
|
||||
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
|
||||
};
|
||||
|
||||
export const routerConfig: RouteObject[] = [
|
||||
const routerConfig: RouteObject[] = [
|
||||
{
|
||||
path: '/*',
|
||||
element: <LayoutWithSidebar />,
|
||||
@@ -68,7 +68,6 @@ export const routerConfig: RouteObject[] = [
|
||||
{
|
||||
index: true,
|
||||
element: <LazyHome />,
|
||||
id: Routes.HOME,
|
||||
},
|
||||
{
|
||||
path: 'markets',
|
||||
@@ -77,19 +76,16 @@ export const routerConfig: RouteObject[] = [
|
||||
{
|
||||
path: 'all',
|
||||
element: <LazyMarkets />,
|
||||
id: Routes.MARKETS,
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <LazyMarket />,
|
||||
id: Routes.MARKET,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'portfolio',
|
||||
element: <LazyPortfolio />,
|
||||
id: Routes.PORTFOLIO,
|
||||
},
|
||||
{
|
||||
path: 'liquidity',
|
||||
@@ -98,7 +94,6 @@ export const routerConfig: RouteObject[] = [
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <LazyLiquidity />,
|
||||
id: Routes.LIQUIDITY,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -90,7 +83,8 @@ html [data-theme='light'] {
|
||||
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
|
||||
|
||||
/* reduce space between candles */
|
||||
--pennant-candlestick-inner-padding: 0.25;
|
||||
--pennant-candlestick-inner-padding: 0.175;
|
||||
--pennant-candlestick-stroke-width: 0.5;
|
||||
}
|
||||
|
||||
html [data-theme='light'] {
|
||||
@@ -168,22 +162,11 @@ html [data-theme='dark'] {
|
||||
@apply font-normal font-alpha;
|
||||
}
|
||||
|
||||
.ag-theme-balham,
|
||||
.ag-theme-balham-dark {
|
||||
--ag-grid-size: 2px; /* Used for compactness */
|
||||
--ag-row-height: 36px;
|
||||
--ag-header-height: 28px;
|
||||
}
|
||||
|
||||
@media (min-width: theme(screens.xxl)) {
|
||||
.ag-theme-balham,
|
||||
.ag-theme-balham-dark {
|
||||
--ag-header-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Light variables */
|
||||
.ag-theme-balham {
|
||||
--ag-grid-size: 2px; /* Used for compactness */
|
||||
--ag-row-height: 36px;
|
||||
--ag-header-height: 36px;
|
||||
--ag-background-color: theme(colors.white);
|
||||
--ag-border-color: theme(colors.vega.clight.600);
|
||||
--ag-header-background-color: theme(colors.vega.clight.700);
|
||||
@@ -196,6 +179,9 @@ html [data-theme='dark'] {
|
||||
|
||||
/* Dark variables */
|
||||
.ag-theme-balham-dark {
|
||||
--ag-grid-size: 2px; /* Used for compactness */
|
||||
--ag-row-height: 36px;
|
||||
--ag-header-height: 36px;
|
||||
--ag-background-color: theme(colors.vega.cdark.900);
|
||||
--ag-border-color: theme(colors.vega.cdark.600);
|
||||
--ag-header-background-color: theme(colors.vega.cdark.700);
|
||||
@@ -205,7 +191,6 @@ html [data-theme='dark'] {
|
||||
--ag-row-hover-color: theme(colors.vega.cdark.800);
|
||||
--ag-modal-overlay-background-color: rgb(9 11 16 / 50%);
|
||||
}
|
||||
|
||||
.ag-theme-balham-dark .ag-row.no-hover,
|
||||
.ag-theme-balham-dark .ag-row.no-hover:hover,
|
||||
.ag-theme-balham .ag-row.no-hover,
|
||||
|
||||
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 |
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
daemon="${1:-nginx}"
|
||||
|
||||
if [[ "$daemon" = "nginx" ]]; then
|
||||
nginx -g 'daemon off;'
|
||||
elif [[ "$daemon" = "ipfs" ]]; then
|
||||
ipfs config profile apply server
|
||||
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
|
||||
ipfs daemon
|
||||
fi
|
||||
@@ -20,8 +20,6 @@ RUN sh docker/docker-build.sh
|
||||
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
|
||||
# configuration of system
|
||||
EXPOSE 80
|
||||
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/*
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
|
||||
EXPOSE 80
|
||||
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/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,16 +8,13 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
STUDY_SIZE,
|
||||
useCandlesChartSettings,
|
||||
} from './use-candles-chart-settings';
|
||||
import { useCandlesChartSettings } from './use-candles-chart-settings';
|
||||
|
||||
export type CandlesChartContainerProps = {
|
||||
marketId: string;
|
||||
};
|
||||
|
||||
const CANDLES_TO_WIDTH_FACTOR = 0.2;
|
||||
const CANDLES_TO_WIDTH_FACTOR = 0.15;
|
||||
|
||||
export const CandlesChartContainer = ({
|
||||
marketId,
|
||||
@@ -52,34 +49,33 @@ export const CandlesChartContainer = ({
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
const candlesCount = Math.floor(width * CANDLES_TO_WIDTH_FACTOR);
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
<CandlestickChart
|
||||
dataSource={dataSource}
|
||||
options={{
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
notEnoughDataText: (
|
||||
<span className="text-xs text-center">{t('No data')}</span>
|
||||
),
|
||||
initialNumCandlesToDisplay: candlesCount,
|
||||
studySize: STUDY_SIZE,
|
||||
studySizes,
|
||||
}}
|
||||
interval={interval}
|
||||
theme={theme}
|
||||
onOptionsChanged={(options) => {
|
||||
setStudies(options.studies);
|
||||
setOverlays(options.overlays);
|
||||
}}
|
||||
onPaneChanged={handlePaneChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
{({ width, height }) => (
|
||||
<div style={{ width, height }}>
|
||||
<CandlestickChart
|
||||
dataSource={dataSource}
|
||||
options={{
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
notEnoughDataText: (
|
||||
<span className="text-xs text-center">{t('No data')}</span>
|
||||
),
|
||||
initialNumCandlesToDisplay: Math.floor(
|
||||
width * CANDLES_TO_WIDTH_FACTOR
|
||||
),
|
||||
studySize: 150, // default size
|
||||
studySizes,
|
||||
}}
|
||||
interval={interval}
|
||||
theme={theme}
|
||||
onOptionsChanged={(options) => {
|
||||
setStudies(options.studies);
|
||||
setOverlays(options.overlays);
|
||||
}}
|
||||
onPaneChanged={handlePaneChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ interface StoredSettings {
|
||||
studySizes: StudySizes;
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 90;
|
||||
export const STUDY_SIZE = 100;
|
||||
const STUDY_ORDER: Study[] = [
|
||||
Study.FORCE_INDEX,
|
||||
Study.RELATIVE_STRENGTH_INDEX,
|
||||
|
||||
@@ -61,15 +61,10 @@ export function addVegaWalletConnect() {
|
||||
});
|
||||
}
|
||||
|
||||
const onboardingViewedState = { state: { dismissed: true }, version: 0 };
|
||||
|
||||
export function addSetVegaWallet() {
|
||||
Cypress.Commands.add('setVegaWallet', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem(
|
||||
'vega_onboarding',
|
||||
JSON.stringify(onboardingViewedState)
|
||||
);
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
@@ -87,10 +82,7 @@ export function addSetVegaWallet() {
|
||||
export function addSetOnBoardingViewed() {
|
||||
Cypress.Commands.add('setOnBoardingViewed', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem(
|
||||
'vega_onboarding',
|
||||
JSON.stringify(onboardingViewedState)
|
||||
);
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
});
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useOpenVolume } from '@vegaprotocol/positions';
|
||||
import {
|
||||
useEstimatePositionQuery,
|
||||
useOpenVolume,
|
||||
} from '@vegaprotocol/positions';
|
||||
import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
@@ -60,14 +63,13 @@ import {
|
||||
useAccountBalance,
|
||||
} from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { OrderFormValues } from '../../hooks';
|
||||
import {
|
||||
DealTicketType,
|
||||
dealTicketTypeToOrderType,
|
||||
isStopOrderType,
|
||||
useDealTicketFormValues,
|
||||
usePositionEstimate,
|
||||
} from '../../hooks';
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
@@ -251,14 +253,16 @@ export const DealTicket = ({
|
||||
side: normalizedOrder.side,
|
||||
});
|
||||
}
|
||||
|
||||
const positionEstimate = usePositionEstimate({
|
||||
marketId: market.id,
|
||||
openVolume,
|
||||
orders,
|
||||
collateralAvailable:
|
||||
marginAccountBalance || generalAccountBalance ? balance : undefined,
|
||||
const { data: positionEstimate } = useEstimatePositionQuery({
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
openVolume,
|
||||
orders,
|
||||
collateralAvailable:
|
||||
marginAccountBalance || generalAccountBalance ? balance : undefined,
|
||||
},
|
||||
skip: !normalizedOrder,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
const assetSymbol =
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './__generated__/EstimateOrder';
|
||||
export * from './use-estimate-fees';
|
||||
export * from './use-form-values';
|
||||
export * from './use-position-estimate';
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { usePositionEstimate } from './use-position-estimate';
|
||||
import * as positionsModule from '@vegaprotocol/positions';
|
||||
import type {
|
||||
EstimatePositionQuery,
|
||||
EstimatePositionQueryVariables,
|
||||
} from '@vegaprotocol/positions';
|
||||
import type { QueryResult } from '@apollo/client';
|
||||
|
||||
let mockData: object | undefined = {};
|
||||
|
||||
describe('usePositionEstimate', () => {
|
||||
const args = {
|
||||
marketId: 'marketId',
|
||||
openVolume: '10',
|
||||
orders: [],
|
||||
collateralAvailable: '200',
|
||||
skip: false,
|
||||
};
|
||||
it('should return proper data', () => {
|
||||
jest
|
||||
.spyOn(positionsModule, 'useEstimatePositionQuery')
|
||||
.mockReturnValue({ data: mockData } as unknown as QueryResult<
|
||||
EstimatePositionQuery,
|
||||
EstimatePositionQueryVariables
|
||||
>);
|
||||
const { result, rerender } = renderHook(() => usePositionEstimate(args));
|
||||
expect(result.current).toEqual(mockData);
|
||||
mockData = undefined;
|
||||
rerender(true);
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import type {
|
||||
EstimatePositionQueryVariables,
|
||||
EstimatePositionQuery,
|
||||
} from '@vegaprotocol/positions';
|
||||
import { useEstimatePositionQuery } from '@vegaprotocol/positions';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface PositionEstimateProps extends EstimatePositionQueryVariables {
|
||||
skip: boolean;
|
||||
}
|
||||
|
||||
export const usePositionEstimate = ({
|
||||
marketId,
|
||||
openVolume,
|
||||
orders,
|
||||
collateralAvailable,
|
||||
skip,
|
||||
}: PositionEstimateProps) => {
|
||||
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
|
||||
undefined
|
||||
);
|
||||
const { data } = useEstimatePositionQuery({
|
||||
variables: {
|
||||
marketId,
|
||||
openVolume,
|
||||
orders,
|
||||
collateralAvailable,
|
||||
},
|
||||
skip,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setEstimates(data);
|
||||
}
|
||||
}, [data]);
|
||||
return estimates;
|
||||
};
|
||||
@@ -87,7 +87,7 @@ export const NetworkSwitcher = ({
|
||||
className,
|
||||
}: NetworkSwitcherProps) => {
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [isAdvancedView, setAdvancedView] = useState(false);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ type Net = Exclude<Networks, 'CUSTOM'>;
|
||||
export enum DApp {
|
||||
Explorer = 'Explorer',
|
||||
Console = 'Console',
|
||||
Governance = 'Governance',
|
||||
Token = 'Token',
|
||||
}
|
||||
|
||||
type DAppLinks = {
|
||||
@@ -45,7 +45,7 @@ const ConsoleLinks = {
|
||||
[Networks.MAINNET_MIRROR]: 'https://console.mainnet-mirror.vega.rocks',
|
||||
};
|
||||
|
||||
const GovernanceLinks = {
|
||||
const TokenLinks = {
|
||||
...EmptyLinks,
|
||||
[Networks.DEVNET]: 'https://dev.governance.vega.xyz',
|
||||
[Networks.STAGNET1]: 'https://governance.stagnet1.vega.rocks',
|
||||
@@ -59,7 +59,7 @@ const GovernanceLinks = {
|
||||
const Links: { [k in DApp]: DAppLinks } = {
|
||||
[DApp.Explorer]: ExplorerLinks,
|
||||
[DApp.Console]: ConsoleLinks,
|
||||
[DApp.Governance]: GovernanceLinks,
|
||||
[DApp.Token]: TokenLinks,
|
||||
};
|
||||
|
||||
export const DocsLinks = VEGA_DOCS_URL
|
||||
@@ -88,7 +88,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
|
||||
useEnvironment();
|
||||
const fallback = {
|
||||
[DApp.Explorer]: VEGA_EXPLORER_URL,
|
||||
[DApp.Governance]: VEGA_TOKEN_URL,
|
||||
[DApp.Token]: VEGA_TOKEN_URL,
|
||||
[DApp.Console]: VEGA_CONSOLE_URL,
|
||||
};
|
||||
|
||||
@@ -99,10 +99,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
|
||||
|
||||
let baseUrl = trim(Links[dapp][net], '/');
|
||||
if (baseUrl.length === 0 && Object.keys(fallback).includes(dapp)) {
|
||||
baseUrl = trim(
|
||||
fallback[dapp as DApp.Explorer | DApp.Governance] || '',
|
||||
'/'
|
||||
);
|
||||
baseUrl = trim(fallback[dapp as DApp.Explorer | DApp.Token] || '', '/');
|
||||
}
|
||||
|
||||
const link = useCallback(
|
||||
@@ -137,7 +134,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
|
||||
* Generates link to the protocol upgrade proposal details on Governance
|
||||
*/
|
||||
export const useProtocolUpgradeProposalLink = () => {
|
||||
const governance = useLinks(DApp.Governance);
|
||||
const governance = useLinks(DApp.Token);
|
||||
return (releaseTag: string, blockHeight: string) =>
|
||||
governance(
|
||||
TOKEN_PROTOCOL_UPGRADE_PROPOSAL.replace(
|
||||
|
||||
@@ -277,7 +277,7 @@ const SuccessionLineItem = ({
|
||||
});
|
||||
|
||||
const marketData = data?.market;
|
||||
const governanceLink = useLinks(DApp.Governance);
|
||||
const governanceLink = useLinks(DApp.Token);
|
||||
const proposalLink = marketData?.proposal?.id
|
||||
? governanceLink(TOKEN_PROPOSAL.replace(':id', marketData?.proposal?.id))
|
||||
: undefined;
|
||||
@@ -658,7 +658,7 @@ export const LiquidityMonitoringParametersInfoPanel = ({
|
||||
parentMarket.liquidityMonitoringParameters.targetStakeParameters
|
||||
.scalingFactor,
|
||||
}
|
||||
: undefined;
|
||||
: {};
|
||||
|
||||
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
|
||||
};
|
||||
|
||||
@@ -368,7 +368,7 @@ export const PositionsTable = ({
|
||||
DocsLinks?.LOSS_SOCIALIZATION ?? '';
|
||||
|
||||
if (!args.data) {
|
||||
return null;
|
||||
return <>-</>;
|
||||
}
|
||||
|
||||
const losses = parseInt(
|
||||
@@ -377,9 +377,7 @@ export const PositionsTable = ({
|
||||
|
||||
if (losses <= 0) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return (
|
||||
<TooltipCellComponent {...args} value={args.valueFormatted} />
|
||||
);
|
||||
return <>{args.valueFormatted}</>;
|
||||
}
|
||||
|
||||
const lossesFormatted = addDecimalsFormatNumber(
|
||||
|
||||
@@ -10,7 +10,7 @@ type AssetProposalNotificationProps = {
|
||||
export const AssetProposalNotification = ({
|
||||
assetId,
|
||||
}: AssetProposalNotificationProps) => {
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const { data: proposal } = useUpdateProposal({
|
||||
id: assetId,
|
||||
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
|
||||
|
||||
@@ -10,7 +10,7 @@ type MarketProposalNotificationProps = {
|
||||
export const MarketProposalNotification = ({
|
||||
marketId,
|
||||
}: MarketProposalNotificationProps) => {
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const { data: proposal } = useUpdateProposal({
|
||||
id: marketId,
|
||||
proposalType: Schema.ProposalType.TYPE_UPDATE_MARKET,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const ProposalActionsDropdown = ({ id }: { id: string }) => {
|
||||
const linkCreator = useLinks(DApp.Governance);
|
||||
const linkCreator = useLinks(DApp.Token);
|
||||
|
||||
return (
|
||||
<ActionsDropdown data-testid="proposal-actions-content">
|
||||
|
||||
@@ -26,7 +26,7 @@ const UpdateNetworkParameterToastContent = ({
|
||||
}: {
|
||||
proposal: Proposal;
|
||||
}) => {
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const change = proposal.terms.change as UpdateNetworkParameter;
|
||||
const title = t('Network change proposal %s').replace(
|
||||
'%s',
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
export const IconEyeOff = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M13.5 13.9989C13.4343 13.999 13.3693 13.9861 13.3086 13.961C13.248 13.9358 13.1929 13.8989 13.1466 13.8524L2.14658 2.85237C2.05677 2.75784 2.00744 2.63197 2.00911 2.50159C2.01077 2.37121 2.06331 2.24664 2.15551 2.15443C2.24771 2.06223 2.37228 2.0097 2.50266 2.00803C2.63304 2.00636 2.75892 2.05569 2.85345 2.1455L13.8534 13.1455C13.9233 13.2154 13.9709 13.3045 13.9902 13.4015C14.0095 13.4984 13.9996 13.5989 13.9617 13.6902C13.9239 13.7816 13.8599 13.8597 13.7777 13.9146C13.6955 13.9695 13.5989 13.9989 13.5 13.9989ZM7.98939 11.9989C6.69282 11.9989 5.44251 11.6152 4.27314 10.8583C3.20845 10.1708 2.25001 9.18612 1.50126 8.01456V8.01206C2.12439 7.11925 2.80689 6.36425 3.54001 5.7555C3.54664 5.74995 3.55205 5.74309 3.5559 5.73535C3.55975 5.72761 3.56194 5.71916 3.56235 5.71052C3.56277 5.70189 3.56138 5.69326 3.55829 5.68519C3.5552 5.67712 3.55046 5.66977 3.54439 5.66362L2.92189 5.04206C2.91083 5.03091 2.89597 5.02433 2.88028 5.02363C2.86458 5.02294 2.8492 5.02818 2.8372 5.03831C2.05845 5.69456 1.33564 6.49956 0.67845 7.44206C0.565383 7.60434 0.503113 7.79658 0.499551 7.99433C0.49599 8.19209 0.551299 8.38644 0.65845 8.55269C1.48376 9.84425 2.54595 10.9321 3.7297 11.698C5.06251 12.5614 6.49689 12.9989 7.98939 12.9989C8.795 12.9964 9.59491 12.8637 10.3581 12.6058C10.3682 12.6024 10.3772 12.5965 10.3844 12.5886C10.3915 12.5807 10.3965 12.5711 10.3989 12.5608C10.4013 12.5504 10.4011 12.5396 10.3981 12.5294C10.3952 12.5192 10.3897 12.5099 10.3822 12.5024L9.70782 11.828C9.6923 11.8129 9.6731 11.802 9.65212 11.7965C9.63113 11.7911 9.60909 11.7911 9.58814 11.7967C9.06587 11.9312 8.52869 11.9992 7.98939 11.9989ZM15.3388 7.45519C14.5119 6.17644 13.4391 5.09019 12.2366 4.31362C10.9063 3.45362 9.43751 2.99894 7.98939 2.99894C7.19232 3.00035 6.40116 3.13589 5.64908 3.39987C5.63905 3.40336 5.63009 3.40934 5.62302 3.41725C5.61595 3.42516 5.61101 3.43474 5.60866 3.44509C5.60632 3.45544 5.60664 3.46621 5.60961 3.4764C5.61258 3.48658 5.6181 3.49585 5.62564 3.50331L6.29908 4.17675C6.31475 4.19216 6.33422 4.20316 6.3555 4.20865C6.37679 4.21414 6.39915 4.21391 6.42032 4.208C6.93188 4.06962 7.45945 3.99932 7.98939 3.99894C9.26095 3.99894 10.5075 4.38737 11.6941 5.15519C12.7788 5.85519 13.7485 6.83894 14.4991 7.99894C14.4996 7.99965 14.4999 8.00053 14.4999 8.00144C14.4999 8.00234 14.4996 8.00322 14.4991 8.00394C13.9542 8.86173 13.2781 9.62867 12.4953 10.2767C12.4886 10.2823 12.4831 10.2891 12.4792 10.2969C12.4753 10.3046 12.4731 10.3131 12.4727 10.3218C12.4722 10.3305 12.4736 10.3392 12.4767 10.3473C12.4798 10.3554 12.4845 10.3628 12.4906 10.3689L13.1125 10.9905C13.1235 11.0016 13.1383 11.0082 13.1539 11.0089C13.1695 11.0097 13.1849 11.0046 13.1969 10.9946C14.0325 10.291 14.7558 9.46395 15.3419 8.54206C15.4455 8.37961 15.5002 8.19083 15.4997 7.99817C15.4991 7.8055 15.4433 7.61704 15.3388 7.45519Z" />
|
||||
<path d="M8.00002 4.99777C7.77531 4.99765 7.5513 5.02281 7.33221 5.07277C7.32114 5.07507 7.31091 5.08033 7.30259 5.08798C7.29428 5.09563 7.28819 5.1054 7.28498 5.11624C7.28178 5.12707 7.28157 5.13858 7.28438 5.14952C7.2872 5.16047 7.29293 5.17045 7.30096 5.1784L10.8194 8.6959C10.8273 8.70393 10.8373 8.70966 10.8483 8.71247C10.8592 8.71529 10.8707 8.71508 10.8816 8.71187C10.8924 8.70867 10.9022 8.70258 10.9098 8.69427C10.9175 8.68595 10.9227 8.67571 10.925 8.66465C11.0252 8.22529 11.0251 7.769 10.9247 7.32968C10.8244 6.89036 10.6263 6.47929 10.3453 6.127C10.0643 5.77472 9.70756 5.49026 9.30153 5.29477C8.89551 5.09928 8.45066 4.99776 8.00002 4.99777ZM5.18065 7.29965C5.1727 7.29161 5.16272 7.28588 5.15177 7.28307C5.14083 7.28026 5.12932 7.28047 5.11849 7.28367C5.10765 7.28688 5.09788 7.29296 5.09023 7.30128C5.08257 7.3096 5.07732 7.31983 5.07502 7.3309C4.96169 7.82601 4.97592 8.34178 5.11638 8.82989C5.25684 9.318 5.51892 9.76245 5.87807 10.1216C6.23722 10.4807 6.68167 10.7428 7.16978 10.8833C7.65789 11.0237 8.17366 11.038 8.66877 10.9246C8.67984 10.9223 8.69007 10.9171 8.69839 10.9094C8.70671 10.9018 8.71279 10.892 8.716 10.8812C8.7192 10.8703 8.71941 10.8588 8.7166 10.8479C8.71379 10.8369 8.70806 10.827 8.70002 10.819L5.18065 7.29965Z" />
|
||||
<path d="M16 7.97v-.02-.01-.02-.02a.672.672 0 00-.17-.36c-.49-.63-1.07-1.2-1.65-1.72l-3.16 2.26a2.978 2.978 0 01-2.98 2.9c-.31 0-.6-.06-.88-.15L5.09 12.3c.44.19.9.36 1.37.47.97.23 1.94.24 2.92.05.88-.17 1.74-.54 2.53-.98 1.25-.7 2.39-1.67 3.38-2.75.18-.2.37-.41.53-.62.09-.1.15-.22.17-.36v-.02-.02-.01-.02-.03c.01-.02.01-.03.01-.04zm-.43-4.17c.25-.18.43-.46.43-.8 0-.55-.45-1-1-1-.22 0-.41.08-.57.2l-.01-.01-2.67 1.91c-.69-.38-1.41-.69-2.17-.87a6.8 6.8 0 00-2.91-.05c-.88.18-1.74.54-2.53.99-1.25.7-2.39 1.67-3.38 2.75-.18.2-.37.41-.53.62-.23.29-.23.63-.01.92.51.66 1.11 1.25 1.73 1.79.18.16.38.29.56.44l-2.09 1.5.01.01c-.25.18-.43.46-.43.8 0 .55.45 1 1 1 .22 0 .41-.08.57-.2l.01.01 14-10-.01-.01zm-10.41 5a3.03 3.03 0 01-.11-.8 2.99 2.99 0 012.99-2.98c.62 0 1.19.21 1.66.53L5.16 8.8z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -113,9 +113,7 @@ export const Tabs = ({
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
value={child.props.id}
|
||||
className={classNames('h-full', {
|
||||
'overflow-hidden': child.props.overflowHidden,
|
||||
})}
|
||||
className="h-full"
|
||||
data-testid={`tab-${child.props.id}`}
|
||||
>
|
||||
{child.props.children}
|
||||
@@ -133,7 +131,6 @@ interface TabProps {
|
||||
name: string;
|
||||
indicator?: ReactNode;
|
||||
hidden?: boolean;
|
||||
overflowHidden?: boolean;
|
||||
menu?: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { Intent } from '../../utils/intent';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
type TradingButtonProps = {
|
||||
size?: 'large' | 'medium' | 'small' | 'extra-small';
|
||||
@@ -120,22 +119,30 @@ export const TradingButton = forwardRef<
|
||||
)
|
||||
);
|
||||
|
||||
export const TradingAnchorButton = ({
|
||||
size = 'medium',
|
||||
intent = Intent.None,
|
||||
icon,
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
subLabel,
|
||||
...props
|
||||
}: AnchorHTMLAttributes<HTMLAnchorElement> &
|
||||
TradingButtonProps & { href: string }) => (
|
||||
<Link
|
||||
to={href}
|
||||
className={getClassName({ size, subLabel, intent }, className)}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
</Link>
|
||||
export const TradingAnchorButton = forwardRef<
|
||||
HTMLAnchorElement,
|
||||
AnchorHTMLAttributes<HTMLAnchorElement> & TradingButtonProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
size = 'medium',
|
||||
intent = Intent.None,
|
||||
icon,
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
subLabel,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<a
|
||||
ref={ref}
|
||||
href={href}
|
||||
className={getClassName({ size, subLabel, intent }, className)}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
</a>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -155,7 +155,8 @@ const ConnectDialogContainer = ({
|
||||
|
||||
const isDesktopWalletRunning = useIsWalletServiceRunning(
|
||||
walletUrl,
|
||||
connectors['jsonRpc']
|
||||
connectors['jsonRpc'],
|
||||
appChainId
|
||||
);
|
||||
|
||||
const snapStatus = useSnapStatus(
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { JsonRpcConnector } from './connectors';
|
||||
import { ClientErrors } from './connectors';
|
||||
|
||||
export const useIsWalletServiceRunning = (
|
||||
url: string,
|
||||
connector: JsonRpcConnector | undefined,
|
||||
appChainId: string
|
||||
) => {
|
||||
const [run, setRun] = useState<boolean | null>(null);
|
||||
|
||||
const checkState = useCallback(async () => {
|
||||
if (!connector) return false;
|
||||
|
||||
if (url && url !== connector.url) {
|
||||
connector.url = url;
|
||||
}
|
||||
|
||||
try {
|
||||
await connector.checkCompat();
|
||||
const chainIdResult = await connector.getChainId();
|
||||
if (chainIdResult.chainID !== appChainId) {
|
||||
throw ClientErrors.WRONG_NETWORK;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, [connector, url, appChainId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connector) return;
|
||||
|
||||
let interval: NodeJS.Timeout;
|
||||
checkState().then((value) => {
|
||||
setRun(value);
|
||||
interval = setInterval(async () => {
|
||||
setRun(await checkState());
|
||||
}, 1000 * 10);
|
||||
});
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [checkState, connector]);
|
||||
|
||||
return run;
|
||||
};
|
||||
@@ -15,7 +15,6 @@ const requestedTransactionUpdate = {
|
||||
status: EthTxStatus.Requested,
|
||||
error: null,
|
||||
confirmations: 0,
|
||||
notify: true,
|
||||
};
|
||||
|
||||
const mockDepositAsset = jest.fn();
|
||||
@@ -58,7 +57,6 @@ const createTransaction = (
|
||||
dialogOpen: false,
|
||||
txHash: null,
|
||||
receipt: null,
|
||||
notify: true,
|
||||
...transaction,
|
||||
});
|
||||
|
||||
@@ -160,7 +158,6 @@ describe('useVegaTransactionManager', () => {
|
||||
expect(update.mock.calls[1][1]).toEqual({
|
||||
status: EthTxStatus.Pending,
|
||||
txHash,
|
||||
notify: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,7 +197,6 @@ describe('useVegaTransactionManager', () => {
|
||||
expect(update.mock.calls[3][1]).toEqual({
|
||||
status: EthTxStatus.Confirmed,
|
||||
receipt,
|
||||
notify: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ export const useEthTransactionManager = () => {
|
||||
status: EthTxStatus.Requested,
|
||||
error: null,
|
||||
confirmations: 0,
|
||||
notify: true,
|
||||
});
|
||||
const {
|
||||
contract,
|
||||
@@ -49,7 +48,6 @@ export const useEthTransactionManager = () => {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Error,
|
||||
error: err as EthereumError,
|
||||
notify: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -63,7 +61,6 @@ export const useEthTransactionManager = () => {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Pending,
|
||||
txHash: tx.hash,
|
||||
notify: true,
|
||||
});
|
||||
|
||||
for (let i = 1; i <= requiredConfirmations; i++) {
|
||||
@@ -80,31 +77,19 @@ export const useEthTransactionManager = () => {
|
||||
}
|
||||
|
||||
if (requiresConfirmation) {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Complete,
|
||||
receipt,
|
||||
});
|
||||
update(transaction.id, { status: EthTxStatus.Complete, receipt });
|
||||
} else {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Confirmed,
|
||||
receipt,
|
||||
notify: true,
|
||||
});
|
||||
update(transaction.id, { status: EthTxStatus.Confirmed, receipt });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error || isEthereumError(err)) {
|
||||
if (!isExpectedEthereumError(err)) {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Error,
|
||||
error: err,
|
||||
notify: true,
|
||||
});
|
||||
update(transaction.id, { status: EthTxStatus.Error, error: err });
|
||||
}
|
||||
} else {
|
||||
update(transaction.id, {
|
||||
status: EthTxStatus.Error,
|
||||
error: new Error('Something went wrong'),
|
||||
notify: true,
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -27,13 +27,7 @@ export interface EthStoredTxState extends EthTxState {
|
||||
methodName: ContractMethod;
|
||||
args: string[];
|
||||
requiredConfirmations: number;
|
||||
// whether or not the tx needs external confirmation (IE from a subscription even)
|
||||
requiresConfirmation: boolean;
|
||||
// whether or not to notify via toast
|
||||
// true = force open toast
|
||||
// false = force close toast
|
||||
// undefined = leave alone
|
||||
notify: boolean | undefined;
|
||||
requiresConfirmation: boolean; // whether or not the tx needs external confirmation (IE from a subscription even)
|
||||
assetId?: string;
|
||||
deposit?: DepositBusEventFieldsFragment;
|
||||
withdrawal?: WithdrawalBusEventFieldsFragment;
|
||||
@@ -55,7 +49,7 @@ export interface EthTransactionStore {
|
||||
update?: Partial<
|
||||
Pick<
|
||||
EthStoredTxState,
|
||||
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash' | 'notify'
|
||||
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash'
|
||||
>
|
||||
>
|
||||
) => void;
|
||||
@@ -95,7 +89,6 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
|
||||
requiresConfirmation,
|
||||
assetId,
|
||||
withdrawal,
|
||||
notify: true,
|
||||
};
|
||||
set({ transactions: transactions.concat(transaction) });
|
||||
return transaction.id;
|
||||
@@ -142,7 +135,6 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
|
||||
transaction.deposit = deposit;
|
||||
transaction.dialogOpen = true;
|
||||
transaction.updatedAt = new Date();
|
||||
transaction.notify = true;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
@@ -170,13 +169,11 @@ export const useEthereumTransactionToasts = () => {
|
||||
]);
|
||||
|
||||
const dismissTx = useEthTransactionStore((state) => state.dismiss);
|
||||
const updateTx = useEthTransactionStore((state) => state.update);
|
||||
|
||||
const onClose = useCallback(
|
||||
(tx: EthStoredTxState) => () => {
|
||||
dismissTx(tx.id);
|
||||
removeToast(`eth-${tx.id}`);
|
||||
updateTx(tx.id, { notify: false });
|
||||
// closes related "Funds released" toast after successful withdrawal
|
||||
if (
|
||||
isWithdrawTransaction(tx) &&
|
||||
@@ -186,7 +183,7 @@ export const useEthereumTransactionToasts = () => {
|
||||
closeToastBy({ withdrawalId: tx.withdrawal.id });
|
||||
}
|
||||
},
|
||||
[closeToastBy, dismissTx, removeToast, updateTx]
|
||||
[closeToastBy, dismissTx, removeToast]
|
||||
);
|
||||
|
||||
const fromEthTransaction = useCallback(
|
||||
@@ -216,25 +213,17 @@ export const useEthereumTransactionToasts = () => {
|
||||
loader: [EthTxStatus.Pending, EthTxStatus.Complete].includes(tx.status),
|
||||
content,
|
||||
closeAfter,
|
||||
hidden: !tx.notify,
|
||||
};
|
||||
},
|
||||
[onClose]
|
||||
);
|
||||
|
||||
// Only register a subscription once
|
||||
useEffect(() => {
|
||||
const unsubscribe = useEthTransactionStore.subscribe(
|
||||
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
|
||||
(txs) => {
|
||||
txs.forEach((tx) => {
|
||||
setToast(fromEthTransaction(tx));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [fromEthTransaction, setToast]);
|
||||
useEthTransactionStore.subscribe(
|
||||
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
|
||||
(txs) => {
|
||||
txs.forEach((tx) => {
|
||||
setToast(fromEthTransaction(tx));
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user