Compare commits

..
Author SHA1 Message Date
Matthew Russell 2a8bcad59f chore: dont pass unused appChainId 2023-09-25 15:07:27 -04:00
Matthew Russell 07ab34044f fix: only check if wallet is running for connect dialog 2023-09-25 14:58:24 -04:00
Sam Keen 2d4be5fcb3 fix(governance): mainnet rewards query looping (#4865) 2023-09-25 09:59:38 +00:00
m.rayandMatthew Russell 835f2b793f chore(trading): remove static assets console coming from vega.xyz (#4825)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-19 23:54:07 -07:00
Matthew Russell d50b988b4a Merge branch 'release/mainnet-trading' of github.com:vegaprotocol/frontend-monorepo into release/mainnet-trading 2023-09-18 09:03:44 -07:00
Madalina Raicu a6aec899c8 chore: update tag metamask snaps to true 2023-09-18 15:08:46 +01:00
Mikołaj Młodzikowski 8b19572dc9 feat(ci): proper run 2023-09-15 13:07:55 -07:00
Mikołaj Młodzikowski 80399d04f7 feat(ci): patch releasing to ipfs 2023-09-15 17:03:50 +02:00
m.ray 8322fc7edd chore: bump version (#4805) 2023-09-15 15:46:02 +01:00
46752816ec chore(trading): get started section for deposit (#4783)
Co-authored-by: maciek <maciek@vegaprotocol.io>
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-09-15 15:10:38 +01:00
Sam Keen f99f78780c fix(markets): market info for liquidity is showing "added" tags (#4796) 2023-09-15 13:54:17 +01:00
Maciek f7037bca80 chore(deal-ticket): 4743 preserve estimate position cache in deal ticket (#4770) 2023-09-15 13:53:24 +01:00
Matthew RussellandMadalina Raicu ba39720f05 fix(trading,web3): prevent deposit toasts reappearing (#4797)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-09-15 13:51:28 +01:00
MaciekandMadalina Raicu 6d35f2b39d chore(trading): 4764 remember closed sidebar (#4799)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-09-15 13:36:08 +01:00
Matthew Russell 78afecc2e5 chore(trading): layout adjustments to accomodate default 13 inch screens (#4798) 2023-09-15 13:24:47 +01:00
Bartłomiej Głownia 3de5b07495 fix(trading): add overflow hidden to candles tab container (#4803) 2023-09-15 13:13:05 +01:00
Matthew Russell e944d3e37c fix(trading): correct link for get started prompt (#4782) 2023-09-15 13:12:39 +01:00
Joe Tsang 5596392835 chore(governance): fix failing staking test (#4801) 2023-09-15 13:04:42 +01:00
Ben 5c57106c04 chore(trading): delete market info tests (#4804) 2023-09-15 14:45:52 +03:00
Ben 2fc688f477 chore(trading): fix wallet test (#4802) 2023-09-15 14:45:15 +03:00
Mikołaj Młodzikowski 9ea8c839db feat(ci): use ipfs daemon in dockerfiles (#4786) 2023-09-15 08:48:25 +02:00
Matthew Russell d7e2da7a54 fix(trading): missing styles for tooltip in positions table (#4780) 2023-09-14 14:00:59 -07:00
m.ray b29c268257 chore(trading): add oracle proofs url to all envs (#4793) 2023-09-14 13:57:28 -07:00
m.rayandMatthew Russell 01ca05a313 chore(trading): update governance variables (#4766)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-14 13:57:15 -07:00
Matthew Russell e3c6dd41c9 fix(trading): simplify onboarding and explore button to use top traded (#4781) 2023-09-14 12:10:33 -07:00
84 changed files with 863 additions and 799 deletions
+3 -3
View File
@@ -155,10 +155,10 @@ jobs:
- name: Sanity check docker image
run: |
echo "Check 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
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3
+15 -1
View File
@@ -113,6 +113,20 @@ 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.
@@ -150,7 +164,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 --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
docker build -f 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.Token);
const tokenLink = useLinks(DApp.Governance);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
@@ -35,7 +35,7 @@ export const BundleSigners = ({
tx,
id,
}: BundleSignersProps) => {
const tokenLink = useLinks(DApp.Token);
const tokenLink = useLinks(DApp.Governance);
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.Token);
const tokenLink = useLinks(DApp.Governance);
return (
<>
@@ -212,7 +212,7 @@ context(
closeStakingDialog();
navigateTo(navigation.validators);
cy.get(`[row-id="${0}"]`)
.eq(1)
.first()
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.should('have.text', '3,002.00')
@@ -222,7 +222,7 @@ context(
.and('be.visible');
});
cy.get(`[row-id="${1}"]`)
.eq(1)
.first()
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.scrollIntoView()
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useState, useCallback } from 'react';
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
@@ -86,12 +86,18 @@ export const EpochIndividualRewards = ({
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
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
refetchData();
}
}, [epochId, data, refetchData]);
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
return (
<AsyncRenderer
@@ -104,7 +104,7 @@ describe('deposit actions', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-1');
});
it('Deposit to trade is visible', () => {
it.skip('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');
@@ -1,256 +0,0 @@
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');
});
+2 -1
View File
@@ -16,13 +16,14 @@ 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=false
NX_METAMASK_SNAPS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
+1 -1
View File
@@ -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.1-core-0.72.14
NX_APP_VERSION=v0.21.2-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+50 -10
View File
@@ -1,18 +1,58 @@
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="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 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>
</div>
);
+10 -15
View File
@@ -1,20 +1,16 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { marketsWithDataProvider } from '@vegaprotocol/markets';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Loader, Splash } 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();
// 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 { data } = useTopTradedMarkets();
const marketId = useGlobalStore((store) => store.marketId);
useEffect(() => {
@@ -32,12 +28,11 @@ export const Home = () => {
navigate(Links[Routes.MARKETS]());
}
}
}, [marketId, data, navigate, update]);
}, [marketId, data, navigate]);
return (
<AsyncRenderer data={data} loading={loading} error={error}>
{/* Render a loading and error state but we will redirect if markets are found */}
{null}
</AsyncRenderer>
<Splash>
<Loader />
</Splash>
);
};
+9 -7
View File
@@ -11,6 +11,7 @@ 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
@@ -58,7 +59,9 @@ const TitleUpdater = ({
export const MarketPage = () => {
const { marketId } = useParams();
const navigate = useNavigate();
const { init, view, setView } = useSidebar();
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update);
@@ -69,15 +72,14 @@ 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]);
// make sidebar open on deal ticket by default
if (view === null) {
setView({ type: ViewType.Order });
useEffect(() => {
if (largeScreen && view === undefined) {
setViews({ type: ViewType.Order }, currentRouteId);
}
}, [update, lastMarketId, data?.id, setView, init, view]);
}, [setViews, view, currentRouteId, largeScreen]);
const tradeView = useMemo(() => {
if (largeScreen) {
@@ -56,6 +56,7 @@ const MainGrid = memo(
<Tabs storageKey="console-trade-grid-main-left">
<Tab
id="chart"
overflowHidden
name={t('Chart')}
menu={<TradingViews.candles.menu />}
>
@@ -72,7 +73,7 @@ const MainGrid = memo(
</ResizableGridPanel>
<ResizableGridPanel
minSize={200}
preferredSize={sizesMiddle[1] || 300}
preferredSize={sizesMiddle[1] || 275}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-right">
@@ -180,7 +181,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
{({ width, height }) => (
<div
style={{ width, height }}
className="border border-default rounded-sm"
className="border rounded-sm border-default"
>
{children}
</div>
@@ -21,8 +21,8 @@ export const MarketsPage = () => {
updateTitle: store.updateTitle,
}));
const tokenLink = useLinks(DApp.Token);
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
const governanceLink = useLinks(DApp.Governance);
const externalLink = governanceLink(TOKEN_NEW_MARKET_PROPOSAL);
useEffect(() => {
updateTitle(titlefy(['Markets']));
@@ -23,6 +23,7 @@ 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();
@@ -37,7 +38,10 @@ const WithdrawalsIndicator = () => {
};
export const Portfolio = () => {
const { init, view, setView } = useSidebar();
const currentRouteId = useGetCurrentRouteId();
const { getView, setViews } = useSidebar();
const view = getView(currentRouteId);
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
@@ -48,10 +52,10 @@ export const Portfolio = () => {
// Make transfer sidebar open by default
useEffect(() => {
if (init && view === null) {
setView({ type: ViewType.Transfer });
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
}
}, [init, view, setView]);
}, [view, setViews, currentRouteId]);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
@@ -12,6 +12,7 @@ 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,
@@ -21,7 +22,8 @@ export const AccountsContainer = ({
const onMarketClick = useMarketClickHandler(true);
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const gridStore = useAccountStore((store) => store.gridStore);
const updateGridStore = useAccountStore((store) => store.updateGridStore);
@@ -49,13 +51,13 @@ export const AccountsContainer = ({
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setView({ type: ViewType.Withdraw, assetId });
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
}}
onClickDeposit={(assetId) => {
setView({ type: ViewType.Deposit, assetId });
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
}}
onClickTransfer={(assetId) => {
setView({ type: ViewType.Transfer, assetId });
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
@@ -1,22 +1,24 @@
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 setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
return (
<>
<TradingButton
size="extra-small"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
onClick={() => setViews({ type: ViewType.Transfer }, currentRouteId)}
>
{t('Transfer')}
</TradingButton>
<TradingButton
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
>
{t('Deposit')}
</TradingButton>
@@ -9,5 +9,11 @@ export const AnnouncementBanner = () => {
return null;
}
return <Banner app="console" configUrl={ANNOUNCEMENTS_CONFIG_URL} />;
return (
<Banner
app="console"
configUrl={ANNOUNCEMENTS_CONFIG_URL}
background="url('/banner-bg.jpg')"
/>
);
};
-1
View File
@@ -1,2 +1 @@
export const THROTTLE_UPDATE_TIME = 500;
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
@@ -1,14 +1,16 @@
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 setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
return (
<TradingButton
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
data-testid="deposit-button"
>
{t('Deposit')}
@@ -4,16 +4,19 @@ 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 sidebarView = useSidebar((store) => store.view);
const currentRouteId = useGetCurrentRouteId();
const views = useSidebar((store) => store.views);
const sidebarView = views[currentRouteId] || null;
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_350px_40px]'
'lg:grid-cols-[1fr_280px_40px]',
'xxxl:grid-cols-[1fr_320px_40px]'
);
return (
@@ -29,7 +29,7 @@ export const MarketSuccessorProposalBanner = ({
?.successorConfiguration?.parentMarketId === marketId
) ?? [];
const [visible, setVisible] = useState(true);
const tokenLink = useLinks(DApp.Token);
const tokenLink = useLinks(DApp.Governance);
if (visible && successors.length) {
return (
<NotificationBanner
+1 -1
View File
@@ -186,7 +186,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLinkExternal to={useLinks(DApp.Token)()}>
<NavbarLinkExternal to={useLinks(DApp.Governance)()}>
{t('Governance')}
</NavbarLinkExternal>
</NavbarItem>
@@ -1,16 +1,18 @@
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 setView = useSidebar((store) => store.setView);
const setViews = useSidebar((store) => store.setViews);
return (
<OrderbookManager
marketId={marketId}
onClick={(values) => {
update(marketId, values);
setView({ type: ViewType.Order });
setViews({ type: ViewType.Order }, currentRouteId);
}}
/>
);
@@ -11,6 +11,7 @@ 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" />,
@@ -115,7 +116,11 @@ describe('SidebarContent', () => {
<VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/markets/ABC']}>
<Routes>
<Route path="/markets/:marketId" element={<SidebarContent />} />
<Route
path="/markets/:marketId"
id={AppRoutes.MARKET}
element={<SidebarContent />}
/>
</Routes>
</MemoryRouter>
</VegaWalletContext.Provider>
@@ -124,13 +129,17 @@ describe('SidebarContent', () => {
expect(container).toBeEmptyDOMElement();
act(() => {
useSidebar.setState({ view: { type: ViewType.Transfer } });
useSidebar.setState({
views: { [AppRoutes.MARKET]: { type: ViewType.Transfer } },
});
});
expect(screen.getByTestId('transfer')).toBeInTheDocument();
act(() => {
useSidebar.setState({ view: { type: ViewType.Deposit } });
useSidebar.setState({
views: { [AppRoutes.MARKET]: { type: ViewType.Deposit } },
});
});
expect(screen.getByTestId('deposit')).toBeInTheDocument();
@@ -141,26 +150,36 @@ describe('SidebarContent', () => {
<VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/portfolio']}>
<Routes>
<Route path="/portfolio" element={<SidebarContent />} />
<Route
path="/portfolio"
id={AppRoutes.PORTFOLIO}
element={<SidebarContent />}
/>
</Routes>
</MemoryRouter>
</VegaWalletContext.Provider>
);
act(() => {
useSidebar.setState({ view: { type: ViewType.Order } });
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Order } },
});
});
expect(container).toBeEmptyDOMElement();
act(() => {
useSidebar.setState({ view: { type: ViewType.Settings } });
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Settings } },
});
});
expect(screen.getByTestId('settings')).toBeInTheDocument();
act(() => {
useSidebar.setState({ view: { type: ViewType.Info } });
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Info } },
});
});
expect(container).toBeEmptyDOMElement();
@@ -178,6 +197,7 @@ describe('SidebarButton', () => {
tooltip="INFO"
onClick={onClick}
view={view}
routeId="current-route-id"
/>
);
+37 -32
View File
@@ -14,8 +14,9 @@ import { Settings } from '../settings';
import { Tooltip } from '../../components/tooltip';
import { WithdrawContainer } from '../withdraw-container';
import { Routes as AppRoutes } from '../../pages/client-router';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { GetStarted } from '../welcome-dialog';
export enum ViewType {
Order = 'Order',
@@ -51,27 +52,31 @@ 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 lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
<div className="flex h-full p-1 lg:flex-col gap-2" 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>
@@ -94,11 +99,13 @@ 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}
/>
</>
}
@@ -114,12 +121,13 @@ 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>
@@ -133,23 +141,25 @@ export const SidebarButton = ({
tooltip,
disabled = false,
onClick,
routeId,
}: {
view?: ViewType;
icon: VegaIconNames;
tooltip: string;
disabled?: boolean;
onClick?: () => void;
routeId: string;
}) => {
const { currView, setView } = useSidebar((store) => ({
currView: store.view,
setView: store.setView,
const { setViews, getView } = useSidebar((store) => ({
setViews: store.setViews,
getView: store.getView,
}));
const currView = getView(routeId);
const onSelect = (view: SidebarView['type']) => {
if (view === currView?.type) {
setView(null);
setViews(null, routeId);
} else {
setView({ type: view });
setViews({ type: view }, routeId);
}
};
@@ -187,7 +197,7 @@ export const SidebarButton = ({
const SidebarDivider = () => {
return (
<div
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-px h-4 lg:w-4 lg:h-px"
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
role="separator"
/>
);
@@ -195,8 +205,10 @@ const SidebarDivider = () => {
export const SidebarContent = () => {
const params = useParams();
const { view, setView } = useSidebar();
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
if (!view) return null;
if (view.type === ViewType.Order) {
@@ -206,7 +218,7 @@ export const SidebarContent = () => {
<DealTicketContainer
marketId={params.marketId}
onDeposit={(assetId) =>
setView({ type: ViewType.Deposit, assetId })
setViews({ type: ViewType.Deposit, assetId }, currentRouteId)
}
/>
<GetStarted />
@@ -233,7 +245,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Deposit')}>
<DepositContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -242,7 +253,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Withdraw')}>
<WithdrawContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -251,7 +261,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Transfer')}>
<TransferContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -276,7 +285,7 @@ const ContentWrapper = ({
}) => {
return (
<TinyScroll
className="h-full overflow-auto py-4 pl-3 pr-4"
className="h-full py-4 pl-3 pr-4 overflow-auto"
// panes have p-1, since sidebar is on the right make pl less to account for additional pane space
data-testid="sidebar-content"
>
@@ -288,25 +297,21 @@ const ContentWrapper = ({
/** If rendered will close sidebar */
const CloseSidebar = () => {
const setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
useEffect(() => {
setView(null);
}, [setView]);
setViews(null, currentRouteId);
}, [setViews, currentRouteId]);
return null;
};
export const useSidebar = create<{
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 };
}),
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],
}));
@@ -0,0 +1 @@
export { Telemetry } from './telemetry';
@@ -0,0 +1,69 @@
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,6 +11,10 @@ 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,13 +22,15 @@ 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 setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const {
pubKey,
pubKeys,
@@ -95,7 +97,7 @@ export const VegaWalletConnectButton = () => {
<TradingDropdownItem
data-testid="wallet-transfer"
onClick={() => {
setView({ type: ViewType.Transfer });
setViews({ type: ViewType.Transfer }, currentRouteId);
setDropdownOpen(false);
}}
>
@@ -10,6 +10,7 @@ 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,
@@ -17,7 +18,8 @@ export const VegaWalletMenu = ({
setMenu: (open: 'nav' | 'wallet' | null) => void;
}) => {
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
const setView = useSidebar((store) => store.setView);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const activeKey = useMemo(() => {
return pubKeys?.find((pk) => pk.publicKey === pubKey);
@@ -46,7 +48,7 @@ export const VegaWalletMenu = ({
<div className="flex flex-col gap-2 m-4">
<Button
onClick={() => {
setView({ type: ViewType.Transfer });
setViews({ type: ViewType.Transfer }, currentRouteId);
setMenu(null);
}}
>
@@ -2,7 +2,8 @@ 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 } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { useOnboardingStore } from './use-get-onboarding-step';
let mockStep = 1;
jest.mock('./use-get-onboarding-step', () => ({
@@ -44,13 +45,15 @@ describe('GetStarted', () => {
globalThis.window.vega = undefined as unknown as Vega;
});
it('renders nothing if connected', () => {
it('renders nothing if dismissed', () => {
useOnboardingStore.setState({ dismissed: true });
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',
@@ -72,7 +75,7 @@ describe('GetStarted', () => {
</MemoryRouter>
);
checkTicks(screen.getAllByRole('listitem'));
expect(screen.getByRole('button', { name: 'Deposit' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Deposit' })).toBeInTheDocument();
mockStep = 4;
rerender(
@@ -84,9 +87,11 @@ describe('GetStarted', () => {
);
checkTicks(screen.getAllByRole('listitem'));
expect(
screen.getByRole('button', { name: 'Ready to trade' })
screen.getByRole('link', { name: 'Ready to trade' })
).toBeInTheDocument();
fireEvent.click(screen.getByRole('link', { name: 'Ready to trade' }));
mockStep = 5;
rerender(
<MemoryRouter>
@@ -3,89 +3,113 @@ 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 setView = useSidebar((store) => store.setView);
let buttonText = t('Get started');
let onClickHandle = () => {
openVegaWalletDialog();
const setViews = useSidebar((store) => store.setViews);
const buttonProps = {
size: 'small' as const,
'data-testid': 'get-started-button',
intent: Intent.Info,
};
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
buttonText = t('Connect');
return (
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Connect')}
</TradingButton>
);
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
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 (
<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>
);
}
return (
<TradingButton
onClick={onClickHandle}
size="small"
data-testid="get-started-button"
intent={Intent.Info}
>
{buttonText}
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Get started')}
</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 getStartedNeeded =
onBoardingViewed !== 'true' &&
currentStep &&
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP;
const currentStep = useGetOnboardingStep();
const dismissed = useOnboardingStore((store) => store.dismissed);
const wrapperClasses = classNames(
'flex flex-col py-4 px-6 gap-4 rounded',
@@ -94,31 +118,13 @@ export const GetStarted = ({ lead }: Props) => {
{ 'mt-8': !lead }
);
if (getStartedNeeded) {
if (!dismissed) {
return (
<div className={wrapperClasses} data-testid="get-started-banner">
{lead && <h2>{lead}</h2>}
<h3 className="text-lg">{t('Get started')}</h3>
<div>
<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>
<GetStartedCheckList />
</div>
<div>
<GetStartedButton step={currentStep} />
@@ -126,7 +132,7 @@ export const GetStarted = ({ lead }: Props) => {
{VEGA_ENV === Networks.MAINNET && (
<p className="text-sm">
{t('Experiment for free with virtual assets on')}{' '}
<ExternalLink href={CANONICAL_URL}>
<ExternalLink href={VEGA_NETWORKS.TESTNET}>
{t('Fairground Testnet')}
</ExternalLink>
</p>
@@ -134,7 +140,7 @@ export const GetStarted = ({ lead }: Props) => {
{VEGA_ENV === Networks.TESTNET && (
<p className="text-sm">
{t('Ready to trade with real funds?')}{' '}
<ExternalLink href={CANONICAL_URL}>
<ExternalLink href={VEGA_NETWORKS.MAINNET}>
{t('Switch to Mainnet')}
</ExternalLink>
</p>
@@ -41,7 +41,7 @@ export const ProposedMarkets = () => {
proposal.terms.change.instrument.code,
}));
const tokenLink = useLinks(DApp.Token);
const tokenLink = useLinks(DApp.Governance);
return useMemo(
() => (
<div className="mt-7 pt-8 border-t border-default">
@@ -1,3 +1,5 @@
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';
@@ -7,6 +9,29 @@ 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,40 +1,24 @@
import { t } from '@vegaprotocol/i18n';
import { GetStarted } from './get-started';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { useNavigate } from 'react-router-dom';
import { TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import type { ReactNode } from 'react';
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';
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
import { useOnboardingStore } from './use-get-onboarding-step';
export const WelcomeDialogContent = () => {
const { VEGA_ENV } = useEnvironment();
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 setOnboardingDialog = useOnboardingStore(
(store) => store.setDialogOpen
);
const explore = () => {
const marketId = markets?.[0].id ?? '';
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKETS]();
navigate(link);
dismiss();
};
const { data } = useTopTradedMarkets();
const marketId = data && data[0]?.id;
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKETS]();
const lead =
VEGA_ENV === Networks.MAINNET
? t('Start trading on the worlds most advanced decentralised exchange.')
@@ -43,7 +27,7 @@ export const WelcomeDialogContent = () => {
);
return (
<div className="flex flex-col sm:flex-row gap-8">
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
<div className="flex flex-col justify-between pt-3 sm:w-1/2">
<ul className="ml-0">
<ListItemContent
icon={<NonCustodialIcon />}
@@ -65,15 +49,16 @@ export const WelcomeDialogContent = () => {
)}
/>
</ul>
<TradingButton
onClick={explore}
<TradingAnchorButton
href={link}
onClick={() => setOnboardingDialog(false)}
className="block w-full"
data-testid="browse-markets-button"
>
{t('Explore')}
</TradingButton>
</TradingAnchorButton>
</div>
<div className="sm:w-1/2 flex grow">
<div className="flex sm:w-1/2 grow">
<GetStarted lead={lead} />
</div>
</div>
@@ -90,10 +75,10 @@ const ListItemContent = ({
text: string;
}) => {
return (
<li className="my-4 flex gap-3">
<div className="shrink-0 pt-1">{icon}</div>
<li className="flex my-4 gap-3">
<div className="pt-1 shrink-0">{icon}</div>
<div>
<h3 className="text-lg leading-snug mb-2">{title}</h3>
<h3 className="mb-2 text-lg leading-snug">{title}</h3>
<p className="text-sm text-secondary">{text}</p>
</div>
</li>
@@ -1,133 +1,32 @@
import { useNavigate } from 'react-router-dom';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { WelcomeDialogContent } from './welcome-dialog-content';
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';
import { useOnboardingStore } from './use-get-onboarding-step';
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
export const useOnboardingStore = create<{
dismissed: boolean;
dismiss: () => void;
}>()(
persist(
(set) => ({
dismissed: false,
dismiss: () => set(() => ({ dismissed: true })),
}),
{
name: ONBOARDING_STORAGE_KEY,
}
)
);
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
export const WelcomeDialog = () => {
const { VEGA_ENV } = useEnvironment();
const navigate = useNavigate();
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
useTelemetryApproval();
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
const dismiss = useOnboardingStore((store) => store.dismiss);
const dismissed = useOnboardingStore((store) => store.dismissed);
const currentStep = useGetOnboardingStep();
const isTelemetryPopupNeeded =
isTelemetryNeeded &&
(onBoardingViewed === 'true' ||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
const dismiss = useOnboardingStore((store) => store.dismiss);
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 ? (
return (
<Dialog
open
title={title}
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>
}
size="medium"
onChange={onClose}
onChange={() => dismiss()}
intent={Intent.None}
dataTestId="welcome-dialog"
>
<WelcomeDialogContent />
</Dialog>
) : null;
);
};
@@ -1,14 +1,15 @@
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 setView = useSidebar((store) => store.setView);
const setViews = useSidebar((store) => store.setViews);
const currentRouteId = useGetCurrentRouteId();
return (
<TradingButton
size="extra-small"
onClick={() => setView({ type: ViewType.Withdraw })}
onClick={() => setViews({ type: ViewType.Withdraw }, currentRouteId)}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
@@ -0,0 +1,15 @@
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 '';
};
@@ -0,0 +1,13 @@
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 };
};
+2
View File
@@ -49,6 +49,7 @@ 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!');
@@ -125,6 +126,7 @@ function AppBody({ Component }: AppProps) {
<InitializeHandlers />
<MaybeConnectEagerly />
<PartyData />
<Telemetry />
</div>
);
}
+8 -17
View File
@@ -4,33 +4,24 @@ 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
*/}
{/* 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 */}
{/* preload fonts */}
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
href="/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* styles */}
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/* icons */}
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" content="/favicon.ico" />
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
+6 -1
View File
@@ -59,7 +59,7 @@ export const Links: ConsoleLinks = {
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
};
const routerConfig: RouteObject[] = [
export const routerConfig: RouteObject[] = [
{
path: '/*',
element: <LayoutWithSidebar />,
@@ -68,6 +68,7 @@ const routerConfig: RouteObject[] = [
{
index: true,
element: <LazyHome />,
id: Routes.HOME,
},
{
path: 'markets',
@@ -76,16 +77,19 @@ 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',
@@ -94,6 +98,7 @@ const routerConfig: RouteObject[] = [
{
path: ':marketId',
element: <LazyLiquidity />,
id: Routes.LIQUIDITY,
},
],
},
+3 -9
View File
@@ -19,17 +19,11 @@ 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="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="og:image" content="./favicon.ico" />
<meta name="twitter:card" content="./favicon.ico" />
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image" content="./favicon.ico" />
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
</Head>
+23 -8
View File
@@ -1,6 +1,13 @@
@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;
@@ -83,8 +90,7 @@ html [data-theme='light'] {
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
/* reduce space between candles */
--pennant-candlestick-inner-padding: 0.175;
--pennant-candlestick-stroke-width: 0.5;
--pennant-candlestick-inner-padding: 0.25;
}
html [data-theme='light'] {
@@ -162,11 +168,22 @@ html [data-theme='dark'] {
@apply font-normal font-alpha;
}
/* Light variables */
.ag-theme-balham {
.ag-theme-balham,
.ag-theme-balham-dark {
--ag-grid-size: 2px; /* Used for compactness */
--ag-row-height: 36px;
--ag-header-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-background-color: theme(colors.white);
--ag-border-color: theme(colors.vega.clight.600);
--ag-header-background-color: theme(colors.vega.clight.700);
@@ -179,9 +196,6 @@ 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);
@@ -191,6 +205,7 @@ 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.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

+11
View File
@@ -0,0 +1,11 @@
#!/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
+2
View File
@@ -20,6 +20,8 @@ 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/*
+2
View File
@@ -1,5 +1,7 @@
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/
+5 -3
View File
@@ -14,6 +14,7 @@ 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)
@@ -36,6 +37,7 @@ const doesEndInTheFuture = (now: Date, data: Announcement) => {
export const AnnouncementBanner = ({
app,
configUrl,
background,
}: AnnouncementBannerProps) => {
const [isVisible, setVisible] = useState(false);
const { data, reload } = useAnnouncement(app, configUrl);
@@ -79,10 +81,10 @@ export const AnnouncementBanner = ({
}
return (
<Banner className="relative px-10">
<Banner className="relative px-10" background={background}>
<div
data-testid="app-announcement"
className="relative font-alpha flex gap-2 justify-center text-center text-lg text-white"
className="relative flex justify-center text-lg text-center text-white font-alpha gap-2"
>
<span>{data.text}</span>{' '}
{data.urlText && data.url && (
@@ -90,7 +92,7 @@ export const AnnouncementBanner = ({
)}
</div>
<button
className="absolute right-0 top-0 p-4 w-10 h-full flex items-center justify-center text-white"
className="absolute top-0 right-0 flex items-center justify-center w-10 h-full p-4 text-white"
data-testid="app-announcement-close"
onClick={() => {
setVisible(false);
+33 -29
View File
@@ -8,13 +8,16 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import { useCandlesChartSettings } from './use-candles-chart-settings';
import {
STUDY_SIZE,
useCandlesChartSettings,
} from './use-candles-chart-settings';
export type CandlesChartContainerProps = {
marketId: string;
};
const CANDLES_TO_WIDTH_FACTOR = 0.15;
const CANDLES_TO_WIDTH_FACTOR = 0.2;
export const CandlesChartContainer = ({
marketId,
@@ -49,33 +52,34 @@ export const CandlesChartContainer = ({
return (
<AutoSizer>
{({ 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>
)}
{({ 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>
);
}}
</AutoSizer>
);
};
@@ -15,7 +15,7 @@ interface StoredSettings {
studySizes: StudySizes;
}
export const STUDY_SIZE = 100;
export const STUDY_SIZE = 90;
const STUDY_ORDER: Study[] = [
Study.FORCE_INDEX,
Study.RELATIVE_STRENGTH_INDEX,
@@ -61,10 +61,15 @@ 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_viewed', 'true');
win.localStorage.setItem(
'vega_onboarding',
JSON.stringify(onboardingViewedState)
);
win.localStorage.setItem('vega_telemetry_approval', 'false');
win.localStorage.setItem('vega_telemetry_viewed', 'true');
win.localStorage.setItem(
@@ -82,7 +87,10 @@ export function addSetVegaWallet() {
export function addSetOnBoardingViewed() {
Cypress.Commands.add('setOnBoardingViewed', () => {
cy.window().then((win) => {
win.localStorage.setItem('vega_onboarding_viewed', 'true');
win.localStorage.setItem(
'vega_onboarding',
JSON.stringify(onboardingViewedState)
);
win.localStorage.setItem('vega_telemetry_approval', 'false');
win.localStorage.setItem('vega_telemetry_viewed', 'true');
});
@@ -26,10 +26,7 @@ import {
Pill,
} from '@vegaprotocol/ui-toolkit';
import {
useEstimatePositionQuery,
useOpenVolume,
} from '@vegaprotocol/positions';
import { useOpenVolume } from '@vegaprotocol/positions';
import {
toBigNum,
removeDecimal,
@@ -63,13 +60,14 @@ import {
useAccountBalance,
} from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type { OrderFormValues } from '../../hooks';
import {
DealTicketType,
dealTicketTypeToOrderType,
isStopOrderType,
} from '../../hooks/use-form-values';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { useDealTicketFormValues } from '../../hooks/use-form-values';
useDealTicketFormValues,
usePositionEstimate,
} from '../../hooks';
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
import noop from 'lodash/noop';
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
@@ -253,16 +251,14 @@ export const DealTicket = ({
side: normalizedOrder.side,
});
}
const { data: positionEstimate } = useEstimatePositionQuery({
variables: {
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
},
const positionEstimate = usePositionEstimate({
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
skip: !normalizedOrder,
fetchPolicy: 'no-cache',
});
const assetSymbol =
+1
View File
@@ -1,3 +1,4 @@
export * from './__generated__/EstimateOrder';
export * from './use-estimate-fees';
export * from './use-form-values';
export * from './use-position-estimate';
@@ -0,0 +1,33 @@
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({});
});
});
@@ -0,0 +1,38 @@
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.Token);
const tokenLink = useLinks(DApp.Governance);
const [isOpen, setOpen] = useState(false);
const [isAdvancedView, setAdvancedView] = useState(false);
+9 -6
View File
@@ -11,7 +11,7 @@ type Net = Exclude<Networks, 'CUSTOM'>;
export enum DApp {
Explorer = 'Explorer',
Console = 'Console',
Token = 'Token',
Governance = 'Governance',
}
type DAppLinks = {
@@ -45,7 +45,7 @@ const ConsoleLinks = {
[Networks.MAINNET_MIRROR]: 'https://console.mainnet-mirror.vega.rocks',
};
const TokenLinks = {
const GovernanceLinks = {
...EmptyLinks,
[Networks.DEVNET]: 'https://dev.governance.vega.xyz',
[Networks.STAGNET1]: 'https://governance.stagnet1.vega.rocks',
@@ -59,7 +59,7 @@ const TokenLinks = {
const Links: { [k in DApp]: DAppLinks } = {
[DApp.Explorer]: ExplorerLinks,
[DApp.Console]: ConsoleLinks,
[DApp.Token]: TokenLinks,
[DApp.Governance]: GovernanceLinks,
};
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.Token]: VEGA_TOKEN_URL,
[DApp.Governance]: VEGA_TOKEN_URL,
[DApp.Console]: VEGA_CONSOLE_URL,
};
@@ -99,7 +99,10 @@ 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.Token] || '', '/');
baseUrl = trim(
fallback[dapp as DApp.Explorer | DApp.Governance] || '',
'/'
);
}
const link = useCallback(
@@ -134,7 +137,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
* Generates link to the protocol upgrade proposal details on Governance
*/
export const useProtocolUpgradeProposalLink = () => {
const governance = useLinks(DApp.Token);
const governance = useLinks(DApp.Governance);
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.Token);
const governanceLink = useLinks(DApp.Governance);
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} />;
};
+4 -2
View File
@@ -368,7 +368,7 @@ export const PositionsTable = ({
DocsLinks?.LOSS_SOCIALIZATION ?? '';
if (!args.data) {
return <>-</>;
return null;
}
const losses = parseInt(
@@ -377,7 +377,9 @@ export const PositionsTable = ({
if (losses <= 0) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{args.valueFormatted}</>;
return (
<TooltipCellComponent {...args} value={args.valueFormatted} />
);
}
const lossesFormatted = addDecimalsFormatNumber(
@@ -10,7 +10,7 @@ type AssetProposalNotificationProps = {
export const AssetProposalNotification = ({
assetId,
}: AssetProposalNotificationProps) => {
const tokenLink = useLinks(DApp.Token);
const tokenLink = useLinks(DApp.Governance);
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.Token);
const tokenLink = useLinks(DApp.Governance);
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.Token);
const linkCreator = useLinks(DApp.Governance);
return (
<ActionsDropdown data-testid="proposal-actions-content">
@@ -26,7 +26,7 @@ const UpdateNetworkParameterToastContent = ({
}: {
proposal: Proposal;
}) => {
const tokenLink = useLinks(DApp.Token);
const tokenLink = useLinks(DApp.Governance);
const change = proposal.terms.change as UpdateNetworkParameter;
const title = t('Network change proposal %s').replace(
'%s',
@@ -4,14 +4,27 @@ import type { ReactNode } from 'react';
export interface BannerProps {
children?: ReactNode;
className?: string;
background?: string;
}
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
);
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}>{children}</div>;
return (
<div
className={bannerClasses}
style={{
background,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
{children}
</div>
);
};
@@ -1,7 +1,8 @@
export const IconEyeOff = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<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" />
<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" />
</svg>
);
};
+4 -1
View File
@@ -113,7 +113,9 @@ export const Tabs = ({
return (
<TabsPrimitive.Content
value={child.props.id}
className="h-full"
className={classNames('h-full', {
'overflow-hidden': child.props.overflowHidden,
})}
data-testid={`tab-${child.props.id}`}
>
{child.props.children}
@@ -131,6 +133,7 @@ interface TabProps {
name: string;
indicator?: ReactNode;
hidden?: boolean;
overflowHidden?: boolean;
menu?: ReactNode;
}
@@ -6,6 +6,7 @@ import type {
ReactNode,
} from 'react';
import { Intent } from '../../utils/intent';
import { Link } from 'react-router-dom';
type TradingButtonProps = {
size?: 'large' | 'medium' | 'small' | 'extra-small';
@@ -119,30 +120,22 @@ export const TradingButton = forwardRef<
)
);
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>
)
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>
);
@@ -155,8 +155,7 @@ const ConnectDialogContainer = ({
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors['jsonRpc'],
appChainId
connectors['jsonRpc']
);
const snapStatus = useSnapStatus(
@@ -0,0 +1,41 @@
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);
});
});
});
@@ -0,0 +1,39 @@
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;
};
@@ -1,47 +0,0 @@
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,6 +15,7 @@ const requestedTransactionUpdate = {
status: EthTxStatus.Requested,
error: null,
confirmations: 0,
notify: true,
};
const mockDepositAsset = jest.fn();
@@ -57,6 +58,7 @@ const createTransaction = (
dialogOpen: false,
txHash: null,
receipt: null,
notify: true,
...transaction,
});
@@ -158,6 +160,7 @@ describe('useVegaTransactionManager', () => {
expect(update.mock.calls[1][1]).toEqual({
status: EthTxStatus.Pending,
txHash,
notify: true,
});
});
@@ -197,6 +200,7 @@ describe('useVegaTransactionManager', () => {
expect(update.mock.calls[3][1]).toEqual({
status: EthTxStatus.Confirmed,
receipt,
notify: true,
});
});
@@ -25,6 +25,7 @@ export const useEthTransactionManager = () => {
status: EthTxStatus.Requested,
error: null,
confirmations: 0,
notify: true,
});
const {
contract,
@@ -48,6 +49,7 @@ export const useEthTransactionManager = () => {
update(transaction.id, {
status: EthTxStatus.Error,
error: err as EthereumError,
notify: true,
});
return;
}
@@ -61,6 +63,7 @@ export const useEthTransactionManager = () => {
update(transaction.id, {
status: EthTxStatus.Pending,
txHash: tx.hash,
notify: true,
});
for (let i = 1; i <= requiredConfirmations; i++) {
@@ -77,19 +80,31 @@ 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 });
update(transaction.id, {
status: EthTxStatus.Confirmed,
receipt,
notify: true,
});
}
} catch (err) {
if (err instanceof Error || isEthereumError(err)) {
if (!isExpectedEthereumError(err)) {
update(transaction.id, { status: EthTxStatus.Error, error: err });
update(transaction.id, {
status: EthTxStatus.Error,
error: err,
notify: true,
});
}
} else {
update(transaction.id, {
status: EthTxStatus.Error,
error: new Error('Something went wrong'),
notify: true,
});
}
return;
@@ -27,7 +27,13 @@ export interface EthStoredTxState extends EthTxState {
methodName: ContractMethod;
args: string[];
requiredConfirmations: number;
requiresConfirmation: boolean; // whether or not the tx needs external confirmation (IE from a subscription even)
// 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;
assetId?: string;
deposit?: DepositBusEventFieldsFragment;
withdrawal?: WithdrawalBusEventFieldsFragment;
@@ -49,7 +55,7 @@ export interface EthTransactionStore {
update?: Partial<
Pick<
EthStoredTxState,
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash'
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash' | 'notify'
>
>
) => void;
@@ -89,6 +95,7 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
requiresConfirmation,
assetId,
withdrawal,
notify: true,
};
set({ transactions: transactions.concat(transaction) });
return transaction.id;
@@ -135,6 +142,7 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
transaction.deposit = deposit;
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
transaction.notify = true;
})
);
},
@@ -1,4 +1,5 @@
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';
@@ -169,11 +170,13 @@ 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) &&
@@ -183,7 +186,7 @@ export const useEthereumTransactionToasts = () => {
closeToastBy({ withdrawalId: tx.withdrawal.id });
}
},
[closeToastBy, dismissTx, removeToast]
[closeToastBy, dismissTx, removeToast, updateTx]
);
const fromEthTransaction = useCallback(
@@ -213,17 +216,25 @@ export const useEthereumTransactionToasts = () => {
loader: [EthTxStatus.Pending, EthTxStatus.Complete].includes(tx.status),
content,
closeAfter,
hidden: !tx.notify,
};
},
[onClose]
);
useEthTransactionStore.subscribe(
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
(txs) => {
txs.forEach((tx) => {
setToast(fromEthTransaction(tx));
});
}
);
// 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]);
};