Compare commits

..
84 changed files with 799 additions and 863 deletions
+3 -3
View File
@@ -155,10 +155,10 @@ jobs:
- name: Sanity check docker image
run: |
echo "Check ipfs-hash"
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash
echo "List html directory"
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'apk add --update tree; tree /usr/share/nginx/html'
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3
+1 -15
View File
@@ -113,20 +113,6 @@ In order to run a container on port 3000:
docker run -p 3000:80 [TAG]
```
On top of that there are two possible scenarios for running docker image - using nginx server (default) of ipfs daemon.
to run ipfs on port 3000:
```bash
docker run -p 3000:80 [TAG] ipfs
```
to run nginx on port 3000:
```bash
docker run -p 3000:80 [TAG]
```
## Build instructions
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
@@ -164,7 +150,7 @@ As a prerequisite you need to perform build of `dist` directory and move its con
You can build any of the containers locally with the following command:
```bash
docker build -f docker/node-outside-docker.Dockerfile . --tag=[TAG]
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
```
### Verifying ipfs-hash of existing current application version
@@ -33,7 +33,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
@@ -35,7 +35,7 @@ export const BundleSigners = ({
tx,
id,
}: BundleSignersProps) => {
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const bridgeFunction: BridgeFunction =
tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20
@@ -139,7 +139,7 @@ export const ValidatorsPage = () => {
const [vegaDialog, setVegaDialog] = useState<boolean>(false);
const [tmDialog, setTmDialog] = useState<boolean>(false);
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
return (
<>
@@ -212,7 +212,7 @@ context(
closeStakingDialog();
navigateTo(navigation.validators);
cy.get(`[row-id="${0}"]`)
.first()
.eq(1)
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.should('have.text', '3,002.00')
@@ -222,7 +222,7 @@ context(
.and('be.visible');
});
cy.get(`[row-id="${1}"]`)
.first()
.eq(1)
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.scrollIntoView()
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useMemo, useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
@@ -86,18 +86,12 @@ export const EpochIndividualRewards = ({
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
if (prevEpochIdRef.current === null) {
prevEpochIdRef.current = epochId;
} else if (epochId !== prevEpochIdRef.current) {
// When the epoch changes, we want to refetch the data to update the current page
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
refetchData();
}
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
}, [epochId, data, refetchData]);
return (
<AsyncRenderer
@@ -104,7 +104,7 @@ describe('deposit actions', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-1');
});
it.skip('Deposit to trade is visible', () => {
it('Deposit to trade is visible', () => {
cy.getByTestId('Collateral').click();
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
@@ -0,0 +1,256 @@
import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { MarketState } from '@vegaprotocol/types';
import compact from 'lodash/compact';
const accordionContent = 'accordion-content';
const blockExplorerLink = 'block-explorer-link';
const dialogClose = 'dialog-close';
const dialogContent = 'dialog-content';
const externalLink = 'external-link';
const githubLink = 'github-link';
const liquidityLink = 'view-liquidity-link';
const marketInfoBtn = 'Info';
const marketTitle = 'accordion-title';
const providerName = 'provider-name';
const row = 'key-value-table-row';
const verifiedProofs = 'verified-proofs';
describe('market info is displayed', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
});
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage(MarketState.STATE_ACTIVE);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(marketInfoBtn).click();
cy.wait('@MarketInfo');
});
it('current fees displayed', () => {
// 6002-MDET-101
cy.getByTestId(marketTitle).contains('Current fees').click();
validateMarketDataRow(0, 'Maker Fee', '0.02%');
validateMarketDataRow(1, 'Infrastructure Fee', '0.05%');
validateMarketDataRow(2, 'Liquidity Fee', '1.00%');
validateMarketDataRow(3, 'Total Fees', '1.07%');
});
it('market price', () => {
// 6002-MDET-102
cy.getByTestId(marketTitle).contains('Market price').click();
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
validateMarketDataRow(2, 'Best Offer Price', '48,126.90058 ');
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
it('market volume displayed', () => {
// 6002-MDET-103
cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(1, 'Open Interest', '-');
validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3');
validateMarketDataRow(4, 'Best Static Bid Volume', '2');
validateMarketDataRow(5, 'Best Static Offer Volume', '4');
});
it('insurance pool displayed', () => {
// 6002-MDET-104
cy.getByTestId(marketTitle).contains('Insurance pool').click();
validateMarketDataRow(0, 'Balance', '0');
});
it('key details displayed', () => {
// 6002-MDET-201
cy.getByTestId(marketTitle).contains('Key details').click();
const rows: [string, string][] = compact([
['Name', 'BTCUSD Monthly (30 Jun 2022)'],
['Market ID', 'market-0'],
Cypress.env('NX_SUCCESSOR_MARKETS') && ['Parent Market ID', 'PARENT-A'],
Cypress.env('NX_SUCCESSOR_MARKETS') && [
'Insurance Pool Fraction',
'0.75',
],
['Trading Mode', MarketTradingModeMapping.TRADING_MODE_CONTINUOUS],
['Market Decimal Places', '5'],
['Position Decimal Places', '0'],
['Settlement Asset Decimal Places', '5'],
]);
for (const rowNumber in rows) {
const [name, value] = rows[rowNumber];
validateMarketDataRow(Number(rowNumber), name, value);
}
});
it('instrument displayed', () => {
// 6002-MDET-202
cy.getByTestId(marketTitle).contains('Instrument').click();
validateMarketDataRow(0, 'Market Name', 'BTCUSD Monthly (30 Jun 2022)');
validateMarketDataRow(1, 'Code', 'BTCUSD.MF21');
validateMarketDataRow(2, 'Product Type', 'Future');
validateMarketDataRow(3, 'Quote Name', 'BTC');
});
it('oracle displayed', () => {
// 6002-MDET-203
cy.getByTestId(marketTitle).contains('Oracle').click();
cy.getByTestId(accordionContent)
.getByTestId(providerName)
.and('contain', 'Another oracle');
cy.getByTestId(providerName).should('be.visible').click();
cy.getByTestId(dialogContent)
.eq(1)
.within(() => {
cy.getByTestId(blockExplorerLink).contains('Block explorer');
cy.getByTestId(githubLink).contains('Oracle repository');
});
cy.getByTestId(dialogClose).click();
cy.getByTestId(accordionContent)
.getByTestId(verifiedProofs)
.and('contain', '1');
});
it('settlement asset displayed', () => {
// 6002-MDET-206
cy.getByTestId(marketTitle).contains('Settlement asset').click();
cy.window().then((win) => {
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
});
validateMarketDataRow(0, 'ID', 'asset-id');
validateMarketDataRow(1, 'Type', 'ERC20');
validateMarketDataRow(2, 'Name', 'Euro');
validateMarketDataRow(3, 'Symbol', 'tEURO');
validateMarketDataRow(4, 'Decimals', '5');
validateMarketDataRow(5, 'Quantum', '1');
validateMarketDataRow(6, 'Status', 'Enabled');
validateMarketDataRow(7, 'Contract address', '0x0158…78a4');
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
validateMarketDataRow(9, 'Lifetime limit', '1,230');
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
validateMarketDataRow(11, 'Global reward pool account balance', '0.00002');
});
it('metadata displayed', () => {
// 6002-MDET-207
cy.getByTestId(marketTitle).contains('Metadata').click();
validateMarketDataRow(0, 'Formerly', '076BB86A5AA41E3E');
validateMarketDataRow(1, 'Base', 'BTC');
validateMarketDataRow(2, 'Quote', 'USD');
validateMarketDataRow(3, 'Class', 'fx/crypto');
validateMarketDataRow(4, 'Sector', 'crypto');
});
it('risk model displayed', () => {
// 6002-MDET-208
cy.getByTestId(marketTitle).contains('Risk model').click();
validateMarketDataRow(0, 'Tau', '0.0001140771161');
validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
});
it('risk parameters displayed', () => {
// 6002-MDET-209
cy.getByTestId(marketTitle).contains('Risk parameters').click();
validateMarketDataRow(0, 'R', '0.016');
validateMarketDataRow(1, 'Sigma', '0.3');
});
it('risk factors displayed', () => {
// 6002-MDET-210
cy.getByTestId(marketTitle).contains('Risk factors').click();
validateMarketDataRow(0, 'Short', '0.008571790367285281');
validateMarketDataRow(1, 'Long', '0.008508132993273576');
});
it('price monitoring bounds displayed', () => {
// 6002-MDET-211
cy.getByTestId(marketTitle).contains('Price monitoring bounds 1').click();
cy.get('p.col-span-1').contains('99.99999% probability price bounds');
cy.get('p.col-span-1').contains('Within 43,200 seconds');
validateMarketDataRow(0, 'Highest Price', '7.97323 ');
validateMarketDataRow(1, 'Lowest Price', '6.54701 ');
});
it('liquidity monitoring parameters displayed', () => {
// 6002-MDET-212
cy.getByTestId(marketTitle)
.contains('Liquidity monitoring parameters')
.click();
validateMarketDataRow(0, 'Triggering Ratio', '0.7');
validateMarketDataRow(1, 'Time Window', '3,600');
validateMarketDataRow(2, 'Scaling Factor', '10');
});
it('liquidity displayed', () => {
// 6002-MDET-213
cy.getByTestId(marketTitle)
.contains(/Liquidity(?! m)/)
.click();
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
cy.getByTestId(liquidityLink).should(
'have.text',
'View liquidity provision table'
);
});
it('liquidity price range displayed', () => {
// 6002-MDET-214
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
validateMarketDataRow(1, 'Lowest Price', '45,204.362 BTC');
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
});
it('proposal displayed', () => {
// 6002-MDET-301
cy.getByTestId(marketTitle).contains('Proposal').click();
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.first()
.should('have.text', 'View governance proposal')
.and('have.attr', 'href')
.and('contain', '/proposals/market-0');
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.eq(1)
.should('have.text', 'Propose a change to market')
.and('have.attr', 'href')
.and('contain', '/proposals/propose/update-market');
});
afterEach('close toggle', () => {
cy.get('[data-state="open"]').then((tab) => {
if (tab) tab.find('button').trigger('click');
});
});
function validateMarketDataRow(
rowNumber: number,
name: string,
value: string
) {
cy.getByTestId(row)
.eq(rowNumber)
.within(() => {
cy.get('dt').should('contain.text', name);
cy.get('dd').should('contain.text', value);
});
}
});
@@ -10,10 +10,10 @@ const dialogContent = 'dialog-content';
describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
});
+1 -2
View File
@@ -16,14 +16,13 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_METAMASK_SNAPS=false
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
+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.2-core-0.72.14
NX_APP_VERSION=v0.21.1-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+10 -50
View File
@@ -1,58 +1,18 @@
import { DepositContainer } from '@vegaprotocol/deposits';
import { GetStarted } from '../../components/welcome-dialog';
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { GetStartedCheckList } from '../../components/welcome-dialog';
import {
useGetOnboardingStep,
useOnboardingStore,
OnboardingStep,
} from '../../components/welcome-dialog/use-get-onboarding-step';
import { Links, Routes } from '../../pages/client-router';
import classNames from 'classnames';
export const Deposit = () => {
return (
<div className="max-w-[600px] px-4 py-8 mx-auto lg:px-8">
<h1 className="mb-6 text-4xl uppercase xl:text-5xl font-alpha calt">
{t('Deposit')}
</h1>
<div className="flex flex-col gap-6">
<DepositContainer />
<DepositGetStarted />
</div>
</div>
);
};
const DepositGetStarted = () => {
const onboardingDismissed = useOnboardingStore((store) => store.dismissed);
const dismiss = useOnboardingStore((store) => store.dismiss);
const step = useGetOnboardingStep();
const wrapperClasses = classNames(
'flex flex-col py-4 px-6 gap-4 rounded',
'bg-vega-blue-300 dark:bg-vega-blue-700',
'border border-vega-blue-350 dark:border-vega-blue-650'
);
// Dont show unless still onboarding
if (onboardingDismissed) {
return null;
}
return (
<div className="pt-6 border-t border-default">
<div className={wrapperClasses}>
<h3 className="text-lg">{t('Get started')}</h3>
<GetStartedCheckList />
{step > OnboardingStep.ONBOARDING_DEPOSIT_STEP && (
<TradingAnchorButton
href={Links[Routes.HOME]()}
onClick={() => dismiss()}
intent={Intent.Info}
>
{t('Start trading')}
</TradingAnchorButton>
)}
<div className="py-16 px-8 flex w-full justify-center">
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
{t('Deposit')}
</h1>
<div className="mt-10">
<DepositContainer />
<GetStarted />
</div>
</div>
</div>
);
+15 -10
View File
@@ -1,16 +1,20 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { marketsWithDataProvider } from '@vegaprotocol/markets';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
import { useGlobalStore } from '../../stores';
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
// The home pages only purpose is to redirect to the users last market,
// the top traded if they are new, or fall back to the list of markets.
// Thats why we just render a loader here
export const Home = () => {
const navigate = useNavigate();
const { data } = useTopTradedMarkets();
// The default market selected in the platform behind the overlay
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithDataProvider,
variables: undefined,
});
const update = useGlobalStore((store) => store.update);
const marketId = useGlobalStore((store) => store.marketId);
useEffect(() => {
@@ -28,11 +32,12 @@ export const Home = () => {
navigate(Links[Routes.MARKETS]());
}
}
}, [marketId, data, navigate]);
}, [marketId, data, navigate, update]);
return (
<Splash>
<Loader />
</Splash>
<AsyncRenderer data={data} loading={loading} error={error}>
{/* Render a loading and error state but we will redirect if markets are found */}
{null}
</AsyncRenderer>
);
};
+7 -9
View File
@@ -11,7 +11,6 @@ import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
return markPrice && decimalPlaces
@@ -59,9 +58,7 @@ const TitleUpdater = ({
export const MarketPage = () => {
const { marketId } = useParams();
const navigate = useNavigate();
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { init, view, setView } = useSidebar();
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update);
@@ -72,14 +69,15 @@ export const MarketPage = () => {
useEffect(() => {
if (data?.id && data.id !== lastMarketId) {
update({ marketId: data.id });
// make sidebar open on market id change
setView({ type: ViewType.Order });
}
}, [update, lastMarketId, data?.id]);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews({ type: ViewType.Order }, currentRouteId);
// make sidebar open on deal ticket by default
if (view === null) {
setView({ type: ViewType.Order });
}
}, [setViews, view, currentRouteId, largeScreen]);
}, [update, lastMarketId, data?.id, setView, init, view]);
const tradeView = useMemo(() => {
if (largeScreen) {
@@ -56,7 +56,6 @@ const MainGrid = memo(
<Tabs storageKey="console-trade-grid-main-left">
<Tab
id="chart"
overflowHidden
name={t('Chart')}
menu={<TradingViews.candles.menu />}
>
@@ -73,7 +72,7 @@ const MainGrid = memo(
</ResizableGridPanel>
<ResizableGridPanel
minSize={200}
preferredSize={sizesMiddle[1] || 275}
preferredSize={sizesMiddle[1] || 300}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-right">
@@ -181,7 +180,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
{({ width, height }) => (
<div
style={{ width, height }}
className="border rounded-sm border-default"
className="border border-default rounded-sm"
>
{children}
</div>
@@ -21,8 +21,8 @@ export const MarketsPage = () => {
updateTitle: store.updateTitle,
}));
const governanceLink = useLinks(DApp.Governance);
const externalLink = governanceLink(TOKEN_NEW_MARKET_PROPOSAL);
const tokenLink = useLinks(DApp.Token);
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
useEffect(() => {
updateTitle(titlefy(['Markets']));
@@ -23,7 +23,6 @@ import { ViewType, useSidebar } from '../../components/sidebar';
import { AccountsMenu } from '../../components/accounts-menu';
import { DepositsMenu } from '../../components/deposits-menu';
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -38,10 +37,7 @@ const WithdrawalsIndicator = () => {
};
export const Portfolio = () => {
const currentRouteId = useGetCurrentRouteId();
const { getView, setViews } = useSidebar();
const view = getView(currentRouteId);
const { init, view, setView } = useSidebar();
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
@@ -52,10 +48,10 @@ export const Portfolio = () => {
// Make transfer sidebar open by default
useEffect(() => {
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
if (init && view === null) {
setView({ type: ViewType.Transfer });
}
}, [view, setViews, currentRouteId]);
}, [init, view, setView]);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
@@ -12,7 +12,6 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { ViewType, useSidebar } from '../sidebar';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const AccountsContainer = ({
pinnedAsset,
@@ -22,8 +21,7 @@ export const AccountsContainer = ({
const onMarketClick = useMarketClickHandler(true);
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
const gridStore = useAccountStore((store) => store.gridStore);
const updateGridStore = useAccountStore((store) => store.updateGridStore);
@@ -51,13 +49,13 @@ export const AccountsContainer = ({
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
setView({ type: ViewType.Withdraw, assetId });
}}
onClickDeposit={(assetId) => {
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
setView({ type: ViewType.Deposit, assetId });
}}
onClickTransfer={(assetId) => {
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
setView({ type: ViewType.Transfer, assetId });
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
@@ -1,24 +1,22 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const AccountsMenu = () => {
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
return (
<>
<TradingButton
size="extra-small"
data-testid="open-transfer"
onClick={() => setViews({ type: ViewType.Transfer }, currentRouteId)}
onClick={() => setView({ type: ViewType.Transfer })}
>
{t('Transfer')}
</TradingButton>
<TradingButton
size="extra-small"
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
onClick={() => setView({ type: ViewType.Deposit })}
>
{t('Deposit')}
</TradingButton>
@@ -9,11 +9,5 @@ export const AnnouncementBanner = () => {
return null;
}
return (
<Banner
app="console"
configUrl={ANNOUNCEMENTS_CONFIG_URL}
background="url('/banner-bg.jpg')"
/>
);
return <Banner app="console" configUrl={ANNOUNCEMENTS_CONFIG_URL} />;
};
+1
View File
@@ -1 +1,2 @@
export const THROTTLE_UPDATE_TIME = 500;
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
@@ -1,16 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const DepositsMenu = () => {
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
size="extra-small"
onClick={() => setViews({ type: ViewType.Deposit }, currentRouteId)}
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
>
{t('Deposit')}
@@ -4,19 +4,16 @@ import classNames from 'classnames';
import { Routes as AppRoutes } from '../../pages/client-router';
import { MarketHeader } from '../market-header';
import { LiquidityHeader } from '../liquidity-header';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const LayoutWithSidebar = () => {
const currentRouteId = useGetCurrentRouteId();
const views = useSidebar((store) => store.views);
const sidebarView = views[currentRouteId] || null;
const sidebarView = useSidebar((store) => store.view);
const sidebarOpen = sidebarView !== null;
const gridClasses = classNames(
'h-full relative z-0 grid',
'grid-rows-[min-content_1fr_40px]',
'lg:grid-rows-[min-content_1fr]',
'lg:grid-cols-[1fr_280px_40px]',
'xxxl:grid-cols-[1fr_320px_40px]'
'lg:grid-cols-[1fr_350px_40px]'
);
return (
@@ -29,7 +29,7 @@ export const MarketSuccessorProposalBanner = ({
?.successorConfiguration?.parentMarketId === marketId
) ?? [];
const [visible, setVisible] = useState(true);
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
if (visible && successors.length) {
return (
<NotificationBanner
+1 -1
View File
@@ -186,7 +186,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLinkExternal to={useLinks(DApp.Governance)()}>
<NavbarLinkExternal to={useLinks(DApp.Token)()}>
{t('Governance')}
</NavbarLinkExternal>
</NavbarItem>
@@ -1,18 +1,16 @@
import { OrderbookManager } from '@vegaprotocol/market-depth';
import { ViewType, useSidebar } from '../sidebar';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
const currentRouteId = useGetCurrentRouteId();
const update = useDealTicketFormValues((state) => state.updateAll);
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
return (
<OrderbookManager
marketId={marketId}
onClick={(values) => {
update(marketId, values);
setViews({ type: ViewType.Order }, currentRouteId);
setView({ type: ViewType.Order });
}}
/>
);
@@ -11,7 +11,6 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { Routes as AppRoutes } from '../../pages/client-router';
jest.mock('../node-health', () => ({
NodeHealthContainer: () => <span data-testid="node-health" />,
@@ -116,11 +115,7 @@ describe('SidebarContent', () => {
<VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/markets/ABC']}>
<Routes>
<Route
path="/markets/:marketId"
id={AppRoutes.MARKET}
element={<SidebarContent />}
/>
<Route path="/markets/:marketId" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
</VegaWalletContext.Provider>
@@ -129,17 +124,13 @@ describe('SidebarContent', () => {
expect(container).toBeEmptyDOMElement();
act(() => {
useSidebar.setState({
views: { [AppRoutes.MARKET]: { type: ViewType.Transfer } },
});
useSidebar.setState({ view: { type: ViewType.Transfer } });
});
expect(screen.getByTestId('transfer')).toBeInTheDocument();
act(() => {
useSidebar.setState({
views: { [AppRoutes.MARKET]: { type: ViewType.Deposit } },
});
useSidebar.setState({ view: { type: ViewType.Deposit } });
});
expect(screen.getByTestId('deposit')).toBeInTheDocument();
@@ -150,36 +141,26 @@ describe('SidebarContent', () => {
<VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/portfolio']}>
<Routes>
<Route
path="/portfolio"
id={AppRoutes.PORTFOLIO}
element={<SidebarContent />}
/>
<Route path="/portfolio" element={<SidebarContent />} />
</Routes>
</MemoryRouter>
</VegaWalletContext.Provider>
);
act(() => {
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Order } },
});
useSidebar.setState({ view: { type: ViewType.Order } });
});
expect(container).toBeEmptyDOMElement();
act(() => {
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Settings } },
});
useSidebar.setState({ view: { type: ViewType.Settings } });
});
expect(screen.getByTestId('settings')).toBeInTheDocument();
act(() => {
useSidebar.setState({
views: { [AppRoutes.PORTFOLIO]: { type: ViewType.Info } },
});
useSidebar.setState({ view: { type: ViewType.Info } });
});
expect(container).toBeEmptyDOMElement();
@@ -197,7 +178,6 @@ describe('SidebarButton', () => {
tooltip="INFO"
onClick={onClick}
view={view}
routeId="current-route-id"
/>
);
+32 -37
View File
@@ -14,9 +14,8 @@ import { Settings } from '../settings';
import { Tooltip } from '../../components/tooltip';
import { WithdrawContainer } from '../withdraw-container';
import { Routes as AppRoutes } from '../../pages/client-router';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
export enum ViewType {
Order = 'Order',
@@ -52,31 +51,27 @@ type SidebarView =
};
export const Sidebar = () => {
const currentRouteId = useGetCurrentRouteId();
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
const { pubKeys } = useVegaWallet();
return (
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
<nav className={navClasses}>
{/* sidebar options that always show */}
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip={t('Deposit')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Withdraw}
icon={VegaIconNames.WITHDRAW}
tooltip={t('Withdraw')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Transfer}
icon={VegaIconNames.TRANSFER}
tooltip={t('Transfer')}
routeId={currentRouteId}
/>
{/* buttons for specific routes */}
<Routes>
@@ -99,13 +94,11 @@ export const Sidebar = () => {
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
tooltip={t('Market specification')}
routeId={currentRouteId}
/>
</>
}
@@ -121,13 +114,12 @@ export const Sidebar = () => {
icon={VegaIconNames.EYE}
tooltip={t('View as party')}
disabled={Boolean(pubKeys)}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Settings}
icon={VegaIconNames.COG}
tooltip={t('Settings')}
routeId={currentRouteId}
/>
<NodeHealthContainer />
</nav>
@@ -141,25 +133,23 @@ export const SidebarButton = ({
tooltip,
disabled = false,
onClick,
routeId,
}: {
view?: ViewType;
icon: VegaIconNames;
tooltip: string;
disabled?: boolean;
onClick?: () => void;
routeId: string;
}) => {
const { setViews, getView } = useSidebar((store) => ({
setViews: store.setViews,
getView: store.getView,
const { currView, setView } = useSidebar((store) => ({
currView: store.view,
setView: store.setView,
}));
const currView = getView(routeId);
const onSelect = (view: SidebarView['type']) => {
if (view === currView?.type) {
setViews(null, routeId);
setView(null);
} else {
setViews({ type: view }, routeId);
setView({ type: view });
}
};
@@ -197,7 +187,7 @@ export const SidebarButton = ({
const SidebarDivider = () => {
return (
<div
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
className="bg-vega-clight-600 dark:bg-vega-cdark-600 w-px h-4 lg:w-4 lg:h-px"
role="separator"
/>
);
@@ -205,10 +195,8 @@ const SidebarDivider = () => {
export const SidebarContent = () => {
const params = useParams();
const currentRouteId = useGetCurrentRouteId();
const { view, setView } = useSidebar();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
if (!view) return null;
if (view.type === ViewType.Order) {
@@ -218,7 +206,7 @@ export const SidebarContent = () => {
<DealTicketContainer
marketId={params.marketId}
onDeposit={(assetId) =>
setViews({ type: ViewType.Deposit, assetId }, currentRouteId)
setView({ type: ViewType.Deposit, assetId })
}
/>
<GetStarted />
@@ -245,6 +233,7 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Deposit')}>
<DepositContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -253,6 +242,7 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Withdraw')}>
<WithdrawContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -261,6 +251,7 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Transfer')}>
<TransferContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -285,7 +276,7 @@ const ContentWrapper = ({
}) => {
return (
<TinyScroll
className="h-full py-4 pl-3 pr-4 overflow-auto"
className="h-full overflow-auto py-4 pl-3 pr-4"
// panes have p-1, since sidebar is on the right make pl less to account for additional pane space
data-testid="sidebar-content"
>
@@ -297,21 +288,25 @@ const ContentWrapper = ({
/** If rendered will close sidebar */
const CloseSidebar = () => {
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
useEffect(() => {
setViews(null, currentRouteId);
}, [setViews, currentRouteId]);
setView(null);
}, [setView]);
return null;
};
export const useSidebar = create<{
views: { [key: string]: SidebarView | null };
setViews: (view: SidebarView | null, routeId: string) => void;
getView: (routeId: string) => SidebarView | null | undefined;
}>()((set, get) => ({
views: {},
setViews: (x, routeId) =>
set(({ views }) => ({ views: { ...views, [routeId]: x } })),
getView: (routeId) => get().views[routeId],
init: boolean;
view: SidebarView | null;
setView: (view: SidebarView | null) => void;
}>()((set) => ({
init: true,
view: null,
setView: (x) =>
set(() => {
if (x == null) {
return { view: null, init: false };
}
return { view: x, init: false };
}),
}));
@@ -1 +0,0 @@
export { Telemetry } from './telemetry';
@@ -1,69 +0,0 @@
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { Intent, useToasts } from '@vegaprotocol/ui-toolkit';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useCallback, useEffect } from 'react';
import { TelemetryApproval } from './telemetry-approval';
import { t } from '@vegaprotocol/i18n';
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
export const Telemetry = () => {
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
useTelemetryApproval();
const [setToast, hasToast, removeToast] = useToasts((store) => [
store.setToast,
store.hasToast,
store.remove,
]);
const onApprovalClose = useCallback(() => {
closeTelemetry();
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
}, [closeTelemetry, removeToast]);
const setTelemetryApprovalAndClose = useCallback(
(value: string) => {
setTelemetryValue(value);
onApprovalClose();
},
[onApprovalClose, setTelemetryValue]
);
useEffect(() => {
if (isTelemetryNeeded && onboardingDissmissed) {
const toast: Toast = {
id: TELEMETRY_APPROVAL_TOAST_ID,
intent: Intent.Primary,
content: (
<>
<h3 className="mb-1 text-sm uppercase">
{t('Improve vega console')}
</h3>
<TelemetryApproval
telemetryValue={telemetryValue}
setTelemetryValue={setTelemetryApprovalAndClose}
/>
</>
),
onClose: onApprovalClose,
};
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
setToast(toast);
}
return;
}
}, [
telemetryValue,
isTelemetryNeeded,
onboardingDissmissed,
setToast,
hasToast,
onApprovalClose,
setTelemetryApprovalAndClose,
]);
return null;
};
@@ -11,10 +11,6 @@ jest.mock('@vegaprotocol/wallet', () => ({
useVegaWalletDialogStore: () => mockUpdateDialogOpen,
}));
jest.mock('../../lib/hooks/use-get-current-route-id', () => ({
useGetCurrentRouteId: jest.fn().mockReturnValue('current-route-id'),
}));
beforeEach(() => {
jest.clearAllMocks();
});
@@ -22,15 +22,13 @@ import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
import { ViewType, useSidebar } from '../sidebar';
import classNames from 'classnames';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const VegaWalletConnectButton = () => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
const {
pubKey,
pubKeys,
@@ -97,7 +95,7 @@ export const VegaWalletConnectButton = () => {
<TradingDropdownItem
data-testid="wallet-transfer"
onClick={() => {
setViews({ type: ViewType.Transfer }, currentRouteId);
setView({ type: ViewType.Transfer });
setDropdownOpen(false);
}}
>
@@ -10,7 +10,6 @@ import { useVegaWallet, type PubKey } from '@vegaprotocol/wallet';
import { useCallback, useMemo } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const VegaWalletMenu = ({
setMenu,
@@ -18,8 +17,7 @@ export const VegaWalletMenu = ({
setMenu: (open: 'nav' | 'wallet' | null) => void;
}) => {
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setView = useSidebar((store) => store.setView);
const activeKey = useMemo(() => {
return pubKeys?.find((pk) => pk.publicKey === pubKey);
@@ -48,7 +46,7 @@ export const VegaWalletMenu = ({
<div className="flex flex-col gap-2 m-4">
<Button
onClick={() => {
setViews({ type: ViewType.Transfer }, currentRouteId);
setView({ type: ViewType.Transfer });
setMenu(null);
}}
>
@@ -2,8 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { GetStarted } from './get-started';
import { render, screen, fireEvent } from '@testing-library/react';
import { useOnboardingStore } from './use-get-onboarding-step';
import { render, screen } from '@testing-library/react';
let mockStep = 1;
jest.mock('./use-get-onboarding-step', () => ({
@@ -45,15 +44,13 @@ describe('GetStarted', () => {
globalThis.window.vega = undefined as unknown as Vega;
});
it('renders nothing if dismissed', () => {
useOnboardingStore.setState({ dismissed: true });
it('renders nothing if connected', () => {
mockStep = 0;
const { container } = renderComponent({ pubKey: 'my-pubkey' });
expect(container).toBeEmptyDOMElement();
});
it('steps should be ticked', () => {
useOnboardingStore.setState({ dismissed: false });
const navigatorGetter: jest.SpyInstance = jest.spyOn(
window.navigator,
'userAgent',
@@ -75,7 +72,7 @@ describe('GetStarted', () => {
</MemoryRouter>
);
checkTicks(screen.getAllByRole('listitem'));
expect(screen.getByRole('link', { name: 'Deposit' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Deposit' })).toBeInTheDocument();
mockStep = 4;
rerender(
@@ -87,11 +84,9 @@ describe('GetStarted', () => {
);
checkTicks(screen.getAllByRole('listitem'));
expect(
screen.getByRole('link', { name: 'Ready to trade' })
screen.getByRole('button', { name: 'Ready to trade' })
).toBeInTheDocument();
fireEvent.click(screen.getByRole('link', { name: 'Ready to trade' }));
mockStep = 5;
rerender(
<MemoryRouter>
@@ -3,113 +3,89 @@ import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
Intent,
TradingAnchorButton,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useNavigate } from 'react-router-dom';
import {
OnboardingStep,
useGetOnboardingStep,
useOnboardingStore,
} from './use-get-onboarding-step';
import { Links, Routes } from '../../pages/client-router';
import { useGlobalStore } from '../../stores';
import { useSidebar, ViewType } from '../sidebar';
import * as constants from '../constants';
import { useOnboardingStore } from './welcome-dialog';
interface Props {
lead?: string;
}
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
const navigate = useNavigate();
const [, setOnboardingViewed] = useLocalStorage(
constants.ONBOARDING_VIEWED_KEY
);
const dismiss = useOnboardingStore((store) => store.dismiss);
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
const marketId = useGlobalStore((store) => store.marketId);
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const setViews = useSidebar((store) => store.setViews);
const buttonProps = {
size: 'small' as const,
'data-testid': 'get-started-button',
intent: Intent.Info,
const setView = useSidebar((store) => store.setView);
let buttonText = t('Get started');
let onClickHandle = () => {
openVegaWalletDialog();
};
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
return (
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Connect')}
</TradingButton>
);
buttonText = t('Connect');
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
return (
<TradingAnchorButton
{...buttonProps}
href={Links[Routes.DEPOSIT]()}
onClick={() => setDialogOpen(false)}
>
{t('Deposit')}
</TradingAnchorButton>
);
} else if (step >= OnboardingStep.ONBOARDING_ORDER_STEP) {
return (
<TradingAnchorButton
{...buttonProps}
href={marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]()}
onClick={() => {
setViews({ type: ViewType.Order }, Routes.MARKET);
dismiss();
}}
>
{t('Ready to trade')}
</TradingAnchorButton>
);
buttonText = t('Deposit');
onClickHandle = () => {
navigate(link);
setView({ type: ViewType.Deposit });
dismiss();
};
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
buttonText = t('Ready to trade');
onClickHandle = () => {
navigate(link);
setView({ type: ViewType.Order });
setOnboardingViewed('true');
};
}
return (
<TradingButton {...buttonProps} onClick={() => openVegaWalletDialog()}>
{t('Get started')}
<TradingButton
onClick={onClickHandle}
size="small"
data-testid="get-started-button"
intent={Intent.Info}
>
{buttonText}
</TradingButton>
);
};
export const GetStartedCheckList = () => {
const { pubKey } = useVegaWallet();
const currentStep = useGetOnboardingStep();
return (
<ul className="list-none">
<Step
step={1}
text={t('Connect')}
complete={Boolean(
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
)}
/>
<Step
step={2}
text={t('Deposit funds')}
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
/>
<Step
step={3}
text={t('Open a position')}
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
/>
</ul>
);
};
export const GetStarted = ({ lead }: Props) => {
const { pubKey } = useVegaWallet();
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
const currentStep = useGetOnboardingStep();
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const currentStep = useGetOnboardingStep();
const dismissed = useOnboardingStore((store) => store.dismissed);
const getStartedNeeded =
onBoardingViewed !== 'true' &&
currentStep &&
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP;
const wrapperClasses = classNames(
'flex flex-col py-4 px-6 gap-4 rounded',
@@ -118,13 +94,31 @@ export const GetStarted = ({ lead }: Props) => {
{ 'mt-8': !lead }
);
if (!dismissed) {
if (getStartedNeeded) {
return (
<div className={wrapperClasses} data-testid="get-started-banner">
{lead && <h2>{lead}</h2>}
<h3 className="text-lg">{t('Get started')}</h3>
<div>
<GetStartedCheckList />
<ul className="list-none">
<Step
step={1}
text={t('Connect')}
complete={Boolean(
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
)}
/>
<Step
step={2}
text={t('Deposit funds')}
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
/>
<Step
step={3}
text={t('Open a position')}
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
/>
</ul>
</div>
<div>
<GetStartedButton step={currentStep} />
@@ -132,7 +126,7 @@ export const GetStarted = ({ lead }: Props) => {
{VEGA_ENV === Networks.MAINNET && (
<p className="text-sm">
{t('Experiment for free with virtual assets on')}{' '}
<ExternalLink href={VEGA_NETWORKS.TESTNET}>
<ExternalLink href={CANONICAL_URL}>
{t('Fairground Testnet')}
</ExternalLink>
</p>
@@ -140,7 +134,7 @@ export const GetStarted = ({ lead }: Props) => {
{VEGA_ENV === Networks.TESTNET && (
<p className="text-sm">
{t('Ready to trade with real funds?')}{' '}
<ExternalLink href={VEGA_NETWORKS.MAINNET}>
<ExternalLink href={CANONICAL_URL}>
{t('Switch to Mainnet')}
</ExternalLink>
</p>
@@ -41,7 +41,7 @@ export const ProposedMarkets = () => {
proposal.terms.change.instrument.code,
}));
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
return useMemo(
() => (
<div className="mt-7 pt-8 border-t border-default">
@@ -1,5 +1,3 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { depositsProvider } from '@vegaprotocol/deposits';
import { useDataProvider } from '@vegaprotocol/data-provider';
@@ -9,29 +7,6 @@ import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
import { positionsDataProvider } from '@vegaprotocol/positions';
import { useGlobalStore } from '../../stores';
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
export const useOnboardingStore = create<{
dialogOpen: boolean;
dismissed: boolean;
dismiss: () => void;
setDialogOpen: (isOpen: boolean) => void;
}>()(
persist(
(set) => ({
dialogOpen: true,
dismissed: false,
dismiss: () => set({ dismissed: true }),
setDialogOpen: (isOpen) => set({ dialogOpen: isOpen }),
}),
{
name: ONBOARDING_STORAGE_KEY,
partialize: (state) => ({
dismissed: state.dismissed,
}),
}
)
);
export enum OnboardingStep {
ONBOARDING_UNKNOWN_STEP,
ONBOARDING_WALLET_STEP,
@@ -1,24 +1,40 @@
import { t } from '@vegaprotocol/i18n';
import { GetStarted } from './get-started';
import { TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import type { ReactNode } from 'react';
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
import { useOnboardingStore } from './use-get-onboarding-step';
import { useOnboardingStore } from './welcome-dialog';
import { useMarketList } from '@vegaprotocol/markets';
import { isMarketActive } from '../../lib/utils';
import orderBy from 'lodash/orderBy';
import { priceChangePercentage } from '@vegaprotocol/utils';
export const WelcomeDialogContent = () => {
const { VEGA_ENV } = useEnvironment();
const setOnboardingDialog = useOnboardingStore(
(store) => store.setDialogOpen
const dismiss = useOnboardingStore((store) => store.dismiss);
const navigate = useNavigate();
const { data } = useMarketList();
const markets = orderBy(
data?.filter((m) => isMarketActive(m.state)) || [],
[
(m) => {
if (!m.candles?.length) return 0;
return Number(priceChangePercentage(m.candles.map((c) => c.close)));
},
],
['desc']
);
const { data } = useTopTradedMarkets();
const marketId = data && data[0]?.id;
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKETS]();
const explore = () => {
const marketId = markets?.[0].id ?? '';
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKETS]();
navigate(link);
dismiss();
};
const lead =
VEGA_ENV === Networks.MAINNET
? t('Start trading on the worlds most advanced decentralised exchange.')
@@ -27,7 +43,7 @@ export const WelcomeDialogContent = () => {
);
return (
<div className="flex flex-col sm:flex-row gap-8">
<div className="flex flex-col justify-between pt-3 sm:w-1/2">
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
<ul className="ml-0">
<ListItemContent
icon={<NonCustodialIcon />}
@@ -49,16 +65,15 @@ export const WelcomeDialogContent = () => {
)}
/>
</ul>
<TradingAnchorButton
href={link}
onClick={() => setOnboardingDialog(false)}
<TradingButton
onClick={explore}
className="block w-full"
data-testid="browse-markets-button"
>
{t('Explore')}
</TradingAnchorButton>
</TradingButton>
</div>
<div className="flex sm:w-1/2 grow">
<div className="sm:w-1/2 flex grow">
<GetStarted lead={lead} />
</div>
</div>
@@ -75,10 +90,10 @@ const ListItemContent = ({
text: string;
}) => {
return (
<li className="flex my-4 gap-3">
<div className="pt-1 shrink-0">{icon}</div>
<li className="my-4 flex gap-3">
<div className="shrink-0 pt-1">{icon}</div>
<div>
<h3 className="mb-2 text-lg leading-snug">{title}</h3>
<h3 className="text-lg leading-snug mb-2">{title}</h3>
<p className="text-sm text-secondary">{text}</p>
</div>
</li>
@@ -1,32 +1,133 @@
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { useNavigate } from 'react-router-dom';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { WelcomeDialogContent } from './welcome-dialog-content';
import { useOnboardingStore } from './use-get-onboarding-step';
import { Links, Routes } from '../../pages/client-router';
import { useGlobalStore } from '../../stores';
import {
useGetOnboardingStep,
OnboardingStep,
} from './use-get-onboarding-step';
import * as constants from '../constants';
import { TelemetryApproval } from './telemetry-approval';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useCallback } from 'react';
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
export const useOnboardingStore = create<{
dismissed: boolean;
dismiss: () => void;
}>()(
persist(
(set) => ({
dismissed: false,
dismiss: () => set(() => ({ dismissed: true })),
}),
{
name: ONBOARDING_STORAGE_KEY,
}
)
);
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
export const WelcomeDialog = () => {
const { VEGA_ENV } = useEnvironment();
const dismissed = useOnboardingStore((store) => store.dismissed);
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
const navigate = useNavigate();
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
useTelemetryApproval();
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
const dismiss = useOnboardingStore((store) => store.dismiss);
const dismissed = useOnboardingStore((store) => store.dismissed);
const currentStep = useGetOnboardingStep();
const isTelemetryPopupNeeded =
isTelemetryNeeded &&
(onBoardingViewed === 'true' ||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
return (
const isOnboardingDialogNeeded =
onBoardingViewed !== 'true' &&
currentStep &&
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP &&
!dismissed;
const marketId = useGlobalStore((store) => store.marketId);
const onClose = () => {
if (isTelemetryPopupNeeded) {
closeTelemetry();
} else {
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.HOME]();
navigate(link);
dismiss();
}
};
const [setToast, hasToast, removeToast] = useToasts((store) => [
store.setToast,
store.hasToast,
store.remove,
]);
const onApprovalClose = useCallback(() => {
closeTelemetry();
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
}, [removeToast, closeTelemetry]);
const setTelemetryApprovalAndClose = useCallback(
(value: string) => {
setTelemetryValue(value);
onApprovalClose();
},
[setTelemetryValue, onApprovalClose]
);
if (isTelemetryPopupNeeded) {
const toast: Toast = {
id: TELEMETRY_APPROVAL_TOAST_ID,
intent: Intent.Primary,
content: (
<>
<h3 className="mb-1 text-sm uppercase">
{t('Improve vega console')}
</h3>
<TelemetryApproval
telemetryValue={telemetryValue}
setTelemetryValue={setTelemetryApprovalAndClose}
/>
</>
),
onClose: onApprovalClose,
};
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
setToast(toast);
}
return;
}
const title = (
<span className="font-alpha calt" data-testid="welcome-title">
{t('Console')}{' '}
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
{VEGA_ENV}
</span>
</span>
);
return isOnboardingDialogNeeded ? (
<Dialog
open={dismissed ? false : dialogOpen}
title={
<span className="font-alpha calt" data-testid="welcome-title">
{t('Console')}{' '}
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
{VEGA_ENV}
</span>
</span>
}
open
title={title}
size="medium"
onChange={() => dismiss()}
onChange={onClose}
intent={Intent.None}
dataTestId="welcome-dialog"
>
<WelcomeDialogContent />
</Dialog>
);
) : null;
};
@@ -1,15 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const WithdrawalsMenu = () => {
const setViews = useSidebar((store) => store.setViews);
const currentRouteId = useGetCurrentRouteId();
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
size="extra-small"
onClick={() => setViews({ type: ViewType.Withdraw }, currentRouteId)}
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
@@ -1,15 +0,0 @@
import { routerConfig } from '../../pages/client-router';
import { matchRoutes, useLocation } from 'react-router-dom';
export const useGetCurrentRouteId = () => {
const location = useLocation();
const currentRoute = matchRoutes(routerConfig, location);
const lastRoute = currentRoute?.pop();
if (lastRoute) {
const {
route: { id },
} = lastRoute;
return id || '';
}
return '';
};
@@ -1,13 +0,0 @@
import orderBy from 'lodash/orderBy';
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
import { isMarketActive } from '../utils';
export const useTopTradedMarkets = () => {
const { data, loading, error } = useMarketList();
const activeMarkets = data?.filter((m) => isMarketActive(m.state));
const marketsByTopTraded = data
? orderBy(activeMarkets, (m) => calcTradedFactor(m), 'desc')
: undefined;
return { data: marketsByTopTraded, loading, error };
};
-2
View File
@@ -49,7 +49,6 @@ import {
import { ViewingBanner } from '../components/viewing-banner';
import { NavHeader } from '../components/navbar/nav-header';
import { Routes as AppRoutes } from './client-router';
import { Telemetry } from '../components/telemetry';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@@ -126,7 +125,6 @@ function AppBody({ Component }: AppProps) {
<InitializeHandlers />
<MaybeConnectEagerly />
<PartyData />
<Telemetry />
</div>
);
}
+17 -8
View File
@@ -4,24 +4,33 @@ export default function Document() {
return (
<>
<Head>
{/*
{/*
meta tags
- next advised against using _document for this, so they exist in our
- next advised against using _document for this, so they exist in our
- single page index.page.tsx
*/}
{/* preload fonts */}
{/* icons */}
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
{/* fonts */}
<link
rel="preload"
href="/AlphaLyrae-Medium.woff2"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* icons */}
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" content="/favicon.ico" />
{/* styles */}
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
+1 -6
View File
@@ -59,7 +59,7 @@ export const Links: ConsoleLinks = {
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
};
export const routerConfig: RouteObject[] = [
const routerConfig: RouteObject[] = [
{
path: '/*',
element: <LayoutWithSidebar />,
@@ -68,7 +68,6 @@ export const routerConfig: RouteObject[] = [
{
index: true,
element: <LazyHome />,
id: Routes.HOME,
},
{
path: 'markets',
@@ -77,19 +76,16 @@ export const routerConfig: RouteObject[] = [
{
path: 'all',
element: <LazyMarkets />,
id: Routes.MARKETS,
},
{
path: ':marketId',
element: <LazyMarket />,
id: Routes.MARKET,
},
],
},
{
path: 'portfolio',
element: <LazyPortfolio />,
id: Routes.PORTFOLIO,
},
{
path: 'liquidity',
@@ -98,7 +94,6 @@ export const routerConfig: RouteObject[] = [
{
path: ':marketId',
element: <LazyLiquidity />,
id: Routes.LIQUIDITY,
},
],
},
+9 -3
View File
@@ -19,11 +19,17 @@ export default function Index() {
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="./favicon.ico" />
<meta name="twitter:card" content="./favicon.ico" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta name="twitter:image" content="./favicon.ico" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
</Head>
+8 -23
View File
@@ -1,13 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
/** Load AlphaLyrae font */
@font-face {
font-family: AlphaLyrae;
src: url('/AlphaLyrae-Medium.woff2') format('woff2'),
url('/AlphaLyrae-Medium.woff') format('woff');
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -90,7 +83,8 @@ html [data-theme='light'] {
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
/* reduce space between candles */
--pennant-candlestick-inner-padding: 0.25;
--pennant-candlestick-inner-padding: 0.175;
--pennant-candlestick-stroke-width: 0.5;
}
html [data-theme='light'] {
@@ -168,22 +162,11 @@ html [data-theme='dark'] {
@apply font-normal font-alpha;
}
.ag-theme-balham,
.ag-theme-balham-dark {
--ag-grid-size: 2px; /* Used for compactness */
--ag-row-height: 36px;
--ag-header-height: 28px;
}
@media (min-width: theme(screens.xxl)) {
.ag-theme-balham,
.ag-theme-balham-dark {
--ag-header-height: 36px;
}
}
/* Light variables */
.ag-theme-balham {
--ag-grid-size: 2px; /* Used for compactness */
--ag-row-height: 36px;
--ag-header-height: 36px;
--ag-background-color: theme(colors.white);
--ag-border-color: theme(colors.vega.clight.600);
--ag-header-background-color: theme(colors.vega.clight.700);
@@ -196,6 +179,9 @@ html [data-theme='dark'] {
/* Dark variables */
.ag-theme-balham-dark {
--ag-grid-size: 2px; /* Used for compactness */
--ag-row-height: 36px;
--ag-header-height: 36px;
--ag-background-color: theme(colors.vega.cdark.900);
--ag-border-color: theme(colors.vega.cdark.600);
--ag-header-background-color: theme(colors.vega.cdark.700);
@@ -205,7 +191,6 @@ html [data-theme='dark'] {
--ag-row-hover-color: theme(colors.vega.cdark.800);
--ag-modal-overlay-background-color: rgb(9 11 16 / 50%);
}
.ag-theme-balham-dark .ag-row.no-hover,
.ag-theme-balham-dark .ag-row.no-hover:hover,
.ag-theme-balham .ag-row.no-hover,
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

-11
View File
@@ -1,11 +0,0 @@
#!/bin/sh
daemon="${1:-nginx}"
if [[ "$daemon" = "nginx" ]]; then
nginx -g 'daemon off;'
elif [[ "$daemon" = "ipfs" ]]; then
ipfs config profile apply server
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
ipfs daemon
fi
-2
View File
@@ -20,8 +20,6 @@ RUN sh docker/docker-build.sh
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
# configuration of system
EXPOSE 80
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
# Copy dist
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
-2
View File
@@ -1,7 +1,5 @@
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
EXPOSE 80
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY ./dist-result/ /usr/share/nginx/html/
+3 -5
View File
@@ -14,7 +14,6 @@ import {
export type AnnouncementBannerProps = {
app: AppNameType;
configUrl: string;
background?: string;
};
// run only if below the allowed maximum delay ~24.8 days (https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value)
@@ -37,7 +36,6 @@ const doesEndInTheFuture = (now: Date, data: Announcement) => {
export const AnnouncementBanner = ({
app,
configUrl,
background,
}: AnnouncementBannerProps) => {
const [isVisible, setVisible] = useState(false);
const { data, reload } = useAnnouncement(app, configUrl);
@@ -81,10 +79,10 @@ export const AnnouncementBanner = ({
}
return (
<Banner className="relative px-10" background={background}>
<Banner className="relative px-10">
<div
data-testid="app-announcement"
className="relative flex justify-center text-lg text-center text-white font-alpha gap-2"
className="relative font-alpha flex gap-2 justify-center text-center text-lg text-white"
>
<span>{data.text}</span>{' '}
{data.urlText && data.url && (
@@ -92,7 +90,7 @@ export const AnnouncementBanner = ({
)}
</div>
<button
className="absolute top-0 right-0 flex items-center justify-center w-10 h-full p-4 text-white"
className="absolute right-0 top-0 p-4 w-10 h-full flex items-center justify-center text-white"
data-testid="app-announcement-close"
onClick={() => {
setVisible(false);
+29 -33
View File
@@ -8,16 +8,13 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import {
STUDY_SIZE,
useCandlesChartSettings,
} from './use-candles-chart-settings';
import { useCandlesChartSettings } from './use-candles-chart-settings';
export type CandlesChartContainerProps = {
marketId: string;
};
const CANDLES_TO_WIDTH_FACTOR = 0.2;
const CANDLES_TO_WIDTH_FACTOR = 0.15;
export const CandlesChartContainer = ({
marketId,
@@ -52,34 +49,33 @@ export const CandlesChartContainer = ({
return (
<AutoSizer>
{({ width, height }) => {
const candlesCount = Math.floor(width * CANDLES_TO_WIDTH_FACTOR);
return (
<div style={{ width, height }}>
<CandlestickChart
dataSource={dataSource}
options={{
chartType,
overlays,
studies,
notEnoughDataText: (
<span className="text-xs text-center">{t('No data')}</span>
),
initialNumCandlesToDisplay: candlesCount,
studySize: STUDY_SIZE,
studySizes,
}}
interval={interval}
theme={theme}
onOptionsChanged={(options) => {
setStudies(options.studies);
setOverlays(options.overlays);
}}
onPaneChanged={handlePaneChange}
/>
</div>
);
}}
{({ width, height }) => (
<div style={{ width, height }}>
<CandlestickChart
dataSource={dataSource}
options={{
chartType,
overlays,
studies,
notEnoughDataText: (
<span className="text-xs text-center">{t('No data')}</span>
),
initialNumCandlesToDisplay: Math.floor(
width * CANDLES_TO_WIDTH_FACTOR
),
studySize: 150, // default size
studySizes,
}}
interval={interval}
theme={theme}
onOptionsChanged={(options) => {
setStudies(options.studies);
setOverlays(options.overlays);
}}
onPaneChanged={handlePaneChange}
/>
</div>
)}
</AutoSizer>
);
};
@@ -15,7 +15,7 @@ interface StoredSettings {
studySizes: StudySizes;
}
export const STUDY_SIZE = 90;
export const STUDY_SIZE = 100;
const STUDY_ORDER: Study[] = [
Study.FORCE_INDEX,
Study.RELATIVE_STRENGTH_INDEX,
@@ -61,15 +61,10 @@ export function addVegaWalletConnect() {
});
}
const onboardingViewedState = { state: { dismissed: true }, version: 0 };
export function addSetVegaWallet() {
Cypress.Commands.add('setVegaWallet', () => {
cy.window().then((win) => {
win.localStorage.setItem(
'vega_onboarding',
JSON.stringify(onboardingViewedState)
);
win.localStorage.setItem('vega_onboarding_viewed', 'true');
win.localStorage.setItem('vega_telemetry_approval', 'false');
win.localStorage.setItem('vega_telemetry_viewed', 'true');
win.localStorage.setItem(
@@ -87,10 +82,7 @@ export function addSetVegaWallet() {
export function addSetOnBoardingViewed() {
Cypress.Commands.add('setOnBoardingViewed', () => {
cy.window().then((win) => {
win.localStorage.setItem(
'vega_onboarding',
JSON.stringify(onboardingViewedState)
);
win.localStorage.setItem('vega_onboarding_viewed', 'true');
win.localStorage.setItem('vega_telemetry_approval', 'false');
win.localStorage.setItem('vega_telemetry_viewed', 'true');
});
@@ -26,7 +26,10 @@ import {
Pill,
} from '@vegaprotocol/ui-toolkit';
import { useOpenVolume } from '@vegaprotocol/positions';
import {
useEstimatePositionQuery,
useOpenVolume,
} from '@vegaprotocol/positions';
import {
toBigNum,
removeDecimal,
@@ -60,14 +63,13 @@ import {
useAccountBalance,
} from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type { OrderFormValues } from '../../hooks';
import {
DealTicketType,
dealTicketTypeToOrderType,
isStopOrderType,
useDealTicketFormValues,
usePositionEstimate,
} from '../../hooks';
} from '../../hooks/use-form-values';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { useDealTicketFormValues } from '../../hooks/use-form-values';
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
import noop from 'lodash/noop';
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
@@ -251,14 +253,16 @@ export const DealTicket = ({
side: normalizedOrder.side,
});
}
const positionEstimate = usePositionEstimate({
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
const { data: positionEstimate } = useEstimatePositionQuery({
variables: {
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
},
skip: !normalizedOrder,
fetchPolicy: 'no-cache',
});
const assetSymbol =
-1
View File
@@ -1,4 +1,3 @@
export * from './__generated__/EstimateOrder';
export * from './use-estimate-fees';
export * from './use-form-values';
export * from './use-position-estimate';
@@ -1,33 +0,0 @@
import { renderHook } from '@testing-library/react';
import { usePositionEstimate } from './use-position-estimate';
import * as positionsModule from '@vegaprotocol/positions';
import type {
EstimatePositionQuery,
EstimatePositionQueryVariables,
} from '@vegaprotocol/positions';
import type { QueryResult } from '@apollo/client';
let mockData: object | undefined = {};
describe('usePositionEstimate', () => {
const args = {
marketId: 'marketId',
openVolume: '10',
orders: [],
collateralAvailable: '200',
skip: false,
};
it('should return proper data', () => {
jest
.spyOn(positionsModule, 'useEstimatePositionQuery')
.mockReturnValue({ data: mockData } as unknown as QueryResult<
EstimatePositionQuery,
EstimatePositionQueryVariables
>);
const { result, rerender } = renderHook(() => usePositionEstimate(args));
expect(result.current).toEqual(mockData);
mockData = undefined;
rerender(true);
expect(result.current).toEqual({});
});
});
@@ -1,38 +0,0 @@
import type {
EstimatePositionQueryVariables,
EstimatePositionQuery,
} from '@vegaprotocol/positions';
import { useEstimatePositionQuery } from '@vegaprotocol/positions';
import { useEffect, useState } from 'react';
interface PositionEstimateProps extends EstimatePositionQueryVariables {
skip: boolean;
}
export const usePositionEstimate = ({
marketId,
openVolume,
orders,
collateralAvailable,
skip,
}: PositionEstimateProps) => {
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
undefined
);
const { data } = useEstimatePositionQuery({
variables: {
marketId,
openVolume,
orders,
collateralAvailable,
},
skip,
fetchPolicy: 'no-cache',
});
useEffect(() => {
if (data) {
setEstimates(data);
}
}, [data]);
return estimates;
};
@@ -87,7 +87,7 @@ export const NetworkSwitcher = ({
className,
}: NetworkSwitcherProps) => {
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const [isOpen, setOpen] = useState(false);
const [isAdvancedView, setAdvancedView] = useState(false);
+6 -9
View File
@@ -11,7 +11,7 @@ type Net = Exclude<Networks, 'CUSTOM'>;
export enum DApp {
Explorer = 'Explorer',
Console = 'Console',
Governance = 'Governance',
Token = 'Token',
}
type DAppLinks = {
@@ -45,7 +45,7 @@ const ConsoleLinks = {
[Networks.MAINNET_MIRROR]: 'https://console.mainnet-mirror.vega.rocks',
};
const GovernanceLinks = {
const TokenLinks = {
...EmptyLinks,
[Networks.DEVNET]: 'https://dev.governance.vega.xyz',
[Networks.STAGNET1]: 'https://governance.stagnet1.vega.rocks',
@@ -59,7 +59,7 @@ const GovernanceLinks = {
const Links: { [k in DApp]: DAppLinks } = {
[DApp.Explorer]: ExplorerLinks,
[DApp.Console]: ConsoleLinks,
[DApp.Governance]: GovernanceLinks,
[DApp.Token]: TokenLinks,
};
export const DocsLinks = VEGA_DOCS_URL
@@ -88,7 +88,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
useEnvironment();
const fallback = {
[DApp.Explorer]: VEGA_EXPLORER_URL,
[DApp.Governance]: VEGA_TOKEN_URL,
[DApp.Token]: VEGA_TOKEN_URL,
[DApp.Console]: VEGA_CONSOLE_URL,
};
@@ -99,10 +99,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
let baseUrl = trim(Links[dapp][net], '/');
if (baseUrl.length === 0 && Object.keys(fallback).includes(dapp)) {
baseUrl = trim(
fallback[dapp as DApp.Explorer | DApp.Governance] || '',
'/'
);
baseUrl = trim(fallback[dapp as DApp.Explorer | DApp.Token] || '', '/');
}
const link = useCallback(
@@ -137,7 +134,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
* Generates link to the protocol upgrade proposal details on Governance
*/
export const useProtocolUpgradeProposalLink = () => {
const governance = useLinks(DApp.Governance);
const governance = useLinks(DApp.Token);
return (releaseTag: string, blockHeight: string) =>
governance(
TOKEN_PROTOCOL_UPGRADE_PROPOSAL.replace(
@@ -277,7 +277,7 @@ const SuccessionLineItem = ({
});
const marketData = data?.market;
const governanceLink = useLinks(DApp.Governance);
const governanceLink = useLinks(DApp.Token);
const proposalLink = marketData?.proposal?.id
? governanceLink(TOKEN_PROPOSAL.replace(':id', marketData?.proposal?.id))
: undefined;
@@ -658,7 +658,7 @@ export const LiquidityMonitoringParametersInfoPanel = ({
parentMarket.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}
: undefined;
: {};
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
};
+2 -4
View File
@@ -368,7 +368,7 @@ export const PositionsTable = ({
DocsLinks?.LOSS_SOCIALIZATION ?? '';
if (!args.data) {
return null;
return <>-</>;
}
const losses = parseInt(
@@ -377,9 +377,7 @@ export const PositionsTable = ({
if (losses <= 0) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return (
<TooltipCellComponent {...args} value={args.valueFormatted} />
);
return <>{args.valueFormatted}</>;
}
const lossesFormatted = addDecimalsFormatNumber(
@@ -10,7 +10,7 @@ type AssetProposalNotificationProps = {
export const AssetProposalNotification = ({
assetId,
}: AssetProposalNotificationProps) => {
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const { data: proposal } = useUpdateProposal({
id: assetId,
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
@@ -10,7 +10,7 @@ type MarketProposalNotificationProps = {
export const MarketProposalNotification = ({
marketId,
}: MarketProposalNotificationProps) => {
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const { data: proposal } = useUpdateProposal({
id: marketId,
proposalType: Schema.ProposalType.TYPE_UPDATE_MARKET,
@@ -9,7 +9,7 @@ import { t } from '@vegaprotocol/i18n';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
export const ProposalActionsDropdown = ({ id }: { id: string }) => {
const linkCreator = useLinks(DApp.Governance);
const linkCreator = useLinks(DApp.Token);
return (
<ActionsDropdown data-testid="proposal-actions-content">
@@ -26,7 +26,7 @@ const UpdateNetworkParameterToastContent = ({
}: {
proposal: Proposal;
}) => {
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const change = proposal.terms.change as UpdateNetworkParameter;
const title = t('Network change proposal %s').replace(
'%s',
@@ -4,27 +4,14 @@ import type { ReactNode } from 'react';
export interface BannerProps {
children?: ReactNode;
className?: string;
background?: string;
}
export const AnnouncementBanner = ({
className,
children,
background = 'url("https://static.vega.xyz/assets/img/banner-bg.jpg")',
}: BannerProps) => {
const bannerClasses = classnames('p-4', className);
return (
<div
className={bannerClasses}
style={{
background,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
{children}
</div>
export const AnnouncementBanner = ({ className, children }: BannerProps) => {
const bannerClasses = classnames(
"bg-[url('https://static.vega.xyz/assets/img/banner-bg.jpg')] bg-cover bg-center bg-no-repeat",
'p-4',
className
);
return <div className={bannerClasses}>{children}</div>;
};
@@ -1,8 +1,7 @@
export const IconEyeOff = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M13.5 13.9989C13.4343 13.999 13.3693 13.9861 13.3086 13.961C13.248 13.9358 13.1929 13.8989 13.1466 13.8524L2.14658 2.85237C2.05677 2.75784 2.00744 2.63197 2.00911 2.50159C2.01077 2.37121 2.06331 2.24664 2.15551 2.15443C2.24771 2.06223 2.37228 2.0097 2.50266 2.00803C2.63304 2.00636 2.75892 2.05569 2.85345 2.1455L13.8534 13.1455C13.9233 13.2154 13.9709 13.3045 13.9902 13.4015C14.0095 13.4984 13.9996 13.5989 13.9617 13.6902C13.9239 13.7816 13.8599 13.8597 13.7777 13.9146C13.6955 13.9695 13.5989 13.9989 13.5 13.9989ZM7.98939 11.9989C6.69282 11.9989 5.44251 11.6152 4.27314 10.8583C3.20845 10.1708 2.25001 9.18612 1.50126 8.01456V8.01206C2.12439 7.11925 2.80689 6.36425 3.54001 5.7555C3.54664 5.74995 3.55205 5.74309 3.5559 5.73535C3.55975 5.72761 3.56194 5.71916 3.56235 5.71052C3.56277 5.70189 3.56138 5.69326 3.55829 5.68519C3.5552 5.67712 3.55046 5.66977 3.54439 5.66362L2.92189 5.04206C2.91083 5.03091 2.89597 5.02433 2.88028 5.02363C2.86458 5.02294 2.8492 5.02818 2.8372 5.03831C2.05845 5.69456 1.33564 6.49956 0.67845 7.44206C0.565383 7.60434 0.503113 7.79658 0.499551 7.99433C0.49599 8.19209 0.551299 8.38644 0.65845 8.55269C1.48376 9.84425 2.54595 10.9321 3.7297 11.698C5.06251 12.5614 6.49689 12.9989 7.98939 12.9989C8.795 12.9964 9.59491 12.8637 10.3581 12.6058C10.3682 12.6024 10.3772 12.5965 10.3844 12.5886C10.3915 12.5807 10.3965 12.5711 10.3989 12.5608C10.4013 12.5504 10.4011 12.5396 10.3981 12.5294C10.3952 12.5192 10.3897 12.5099 10.3822 12.5024L9.70782 11.828C9.6923 11.8129 9.6731 11.802 9.65212 11.7965C9.63113 11.7911 9.60909 11.7911 9.58814 11.7967C9.06587 11.9312 8.52869 11.9992 7.98939 11.9989ZM15.3388 7.45519C14.5119 6.17644 13.4391 5.09019 12.2366 4.31362C10.9063 3.45362 9.43751 2.99894 7.98939 2.99894C7.19232 3.00035 6.40116 3.13589 5.64908 3.39987C5.63905 3.40336 5.63009 3.40934 5.62302 3.41725C5.61595 3.42516 5.61101 3.43474 5.60866 3.44509C5.60632 3.45544 5.60664 3.46621 5.60961 3.4764C5.61258 3.48658 5.6181 3.49585 5.62564 3.50331L6.29908 4.17675C6.31475 4.19216 6.33422 4.20316 6.3555 4.20865C6.37679 4.21414 6.39915 4.21391 6.42032 4.208C6.93188 4.06962 7.45945 3.99932 7.98939 3.99894C9.26095 3.99894 10.5075 4.38737 11.6941 5.15519C12.7788 5.85519 13.7485 6.83894 14.4991 7.99894C14.4996 7.99965 14.4999 8.00053 14.4999 8.00144C14.4999 8.00234 14.4996 8.00322 14.4991 8.00394C13.9542 8.86173 13.2781 9.62867 12.4953 10.2767C12.4886 10.2823 12.4831 10.2891 12.4792 10.2969C12.4753 10.3046 12.4731 10.3131 12.4727 10.3218C12.4722 10.3305 12.4736 10.3392 12.4767 10.3473C12.4798 10.3554 12.4845 10.3628 12.4906 10.3689L13.1125 10.9905C13.1235 11.0016 13.1383 11.0082 13.1539 11.0089C13.1695 11.0097 13.1849 11.0046 13.1969 10.9946C14.0325 10.291 14.7558 9.46395 15.3419 8.54206C15.4455 8.37961 15.5002 8.19083 15.4997 7.99817C15.4991 7.8055 15.4433 7.61704 15.3388 7.45519Z" />
<path d="M8.00002 4.99777C7.77531 4.99765 7.5513 5.02281 7.33221 5.07277C7.32114 5.07507 7.31091 5.08033 7.30259 5.08798C7.29428 5.09563 7.28819 5.1054 7.28498 5.11624C7.28178 5.12707 7.28157 5.13858 7.28438 5.14952C7.2872 5.16047 7.29293 5.17045 7.30096 5.1784L10.8194 8.6959C10.8273 8.70393 10.8373 8.70966 10.8483 8.71247C10.8592 8.71529 10.8707 8.71508 10.8816 8.71187C10.8924 8.70867 10.9022 8.70258 10.9098 8.69427C10.9175 8.68595 10.9227 8.67571 10.925 8.66465C11.0252 8.22529 11.0251 7.769 10.9247 7.32968C10.8244 6.89036 10.6263 6.47929 10.3453 6.127C10.0643 5.77472 9.70756 5.49026 9.30153 5.29477C8.89551 5.09928 8.45066 4.99776 8.00002 4.99777ZM5.18065 7.29965C5.1727 7.29161 5.16272 7.28588 5.15177 7.28307C5.14083 7.28026 5.12932 7.28047 5.11849 7.28367C5.10765 7.28688 5.09788 7.29296 5.09023 7.30128C5.08257 7.3096 5.07732 7.31983 5.07502 7.3309C4.96169 7.82601 4.97592 8.34178 5.11638 8.82989C5.25684 9.318 5.51892 9.76245 5.87807 10.1216C6.23722 10.4807 6.68167 10.7428 7.16978 10.8833C7.65789 11.0237 8.17366 11.038 8.66877 10.9246C8.67984 10.9223 8.69007 10.9171 8.69839 10.9094C8.70671 10.9018 8.71279 10.892 8.716 10.8812C8.7192 10.8703 8.71941 10.8588 8.7166 10.8479C8.71379 10.8369 8.70806 10.827 8.70002 10.819L5.18065 7.29965Z" />
<path d="M16 7.97v-.02-.01-.02-.02a.672.672 0 00-.17-.36c-.49-.63-1.07-1.2-1.65-1.72l-3.16 2.26a2.978 2.978 0 01-2.98 2.9c-.31 0-.6-.06-.88-.15L5.09 12.3c.44.19.9.36 1.37.47.97.23 1.94.24 2.92.05.88-.17 1.74-.54 2.53-.98 1.25-.7 2.39-1.67 3.38-2.75.18-.2.37-.41.53-.62.09-.1.15-.22.17-.36v-.02-.02-.01-.02-.03c.01-.02.01-.03.01-.04zm-.43-4.17c.25-.18.43-.46.43-.8 0-.55-.45-1-1-1-.22 0-.41.08-.57.2l-.01-.01-2.67 1.91c-.69-.38-1.41-.69-2.17-.87a6.8 6.8 0 00-2.91-.05c-.88.18-1.74.54-2.53.99-1.25.7-2.39 1.67-3.38 2.75-.18.2-.37.41-.53.62-.23.29-.23.63-.01.92.51.66 1.11 1.25 1.73 1.79.18.16.38.29.56.44l-2.09 1.5.01.01c-.25.18-.43.46-.43.8 0 .55.45 1 1 1 .22 0 .41-.08.57-.2l.01.01 14-10-.01-.01zm-10.41 5a3.03 3.03 0 01-.11-.8 2.99 2.99 0 012.99-2.98c.62 0 1.19.21 1.66.53L5.16 8.8z" />
</svg>
);
};
+1 -4
View File
@@ -113,9 +113,7 @@ export const Tabs = ({
return (
<TabsPrimitive.Content
value={child.props.id}
className={classNames('h-full', {
'overflow-hidden': child.props.overflowHidden,
})}
className="h-full"
data-testid={`tab-${child.props.id}`}
>
{child.props.children}
@@ -133,7 +131,6 @@ interface TabProps {
name: string;
indicator?: ReactNode;
hidden?: boolean;
overflowHidden?: boolean;
menu?: ReactNode;
}
@@ -6,7 +6,6 @@ import type {
ReactNode,
} from 'react';
import { Intent } from '../../utils/intent';
import { Link } from 'react-router-dom';
type TradingButtonProps = {
size?: 'large' | 'medium' | 'small' | 'extra-small';
@@ -120,22 +119,30 @@ export const TradingButton = forwardRef<
)
);
export const TradingAnchorButton = ({
size = 'medium',
intent = Intent.None,
icon,
href,
children,
className,
subLabel,
...props
}: AnchorHTMLAttributes<HTMLAnchorElement> &
TradingButtonProps & { href: string }) => (
<Link
to={href}
className={getClassName({ size, subLabel, intent }, className)}
{...props}
>
<Content icon={icon} subLabel={subLabel} children={children} />
</Link>
export const TradingAnchorButton = forwardRef<
HTMLAnchorElement,
AnchorHTMLAttributes<HTMLAnchorElement> & TradingButtonProps
>(
(
{
size = 'medium',
intent = Intent.None,
icon,
href,
children,
className,
subLabel,
...props
},
ref
) => (
<a
ref={ref}
href={href}
className={getClassName({ size, subLabel, intent }, className)}
{...props}
>
<Content icon={icon} subLabel={subLabel} children={children} />
</a>
)
);
@@ -155,7 +155,8 @@ const ConnectDialogContainer = ({
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors['jsonRpc']
connectors['jsonRpc'],
appChainId
);
const snapStatus = useSnapStatus(
@@ -1,41 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { JsonRpcConnector } from './connectors';
import { useIsWalletServiceRunning } from './use-is-wallet-service-running';
describe('useIsWalletServiceRunning', () => {
it('returns true if wallet is running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockResolvedValue(true);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(true);
});
});
it('returns false if wallet is not running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockRejectedValue(false);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(false);
});
});
});
@@ -1,39 +0,0 @@
import { useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined
) => {
const [isRunning, setIsRunning] = useState<boolean | null>(null);
useEffect(() => {
if (!connector) return;
if (url && url !== connector.url) {
connector.url = url;
}
const check = async () => {
try {
// we are not checking wallet compatibility here, only that the wallet is running
await connector.checkCompat();
setIsRunning(true);
} catch {
setIsRunning(false);
}
};
// check immediately
check();
// check every second for quick feedback to the user
const interval = setInterval(check, 1000);
return () => {
clearInterval(interval);
};
}, [connector, url]);
return isRunning;
};
@@ -0,0 +1,47 @@
import { useCallback, useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
import { ClientErrors } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined,
appChainId: string
) => {
const [run, setRun] = useState<boolean | null>(null);
const checkState = useCallback(async () => {
if (!connector) return false;
if (url && url !== connector.url) {
connector.url = url;
}
try {
await connector.checkCompat();
const chainIdResult = await connector.getChainId();
if (chainIdResult.chainID !== appChainId) {
throw ClientErrors.WRONG_NETWORK;
}
} catch (e) {
return false;
}
return true;
}, [connector, url, appChainId]);
useEffect(() => {
if (!connector) return;
let interval: NodeJS.Timeout;
checkState().then((value) => {
setRun(value);
interval = setInterval(async () => {
setRun(await checkState());
}, 1000 * 10);
});
return () => {
clearInterval(interval);
};
}, [checkState, connector]);
return run;
};
@@ -15,7 +15,6 @@ const requestedTransactionUpdate = {
status: EthTxStatus.Requested,
error: null,
confirmations: 0,
notify: true,
};
const mockDepositAsset = jest.fn();
@@ -58,7 +57,6 @@ const createTransaction = (
dialogOpen: false,
txHash: null,
receipt: null,
notify: true,
...transaction,
});
@@ -160,7 +158,6 @@ describe('useVegaTransactionManager', () => {
expect(update.mock.calls[1][1]).toEqual({
status: EthTxStatus.Pending,
txHash,
notify: true,
});
});
@@ -200,7 +197,6 @@ describe('useVegaTransactionManager', () => {
expect(update.mock.calls[3][1]).toEqual({
status: EthTxStatus.Confirmed,
receipt,
notify: true,
});
});
@@ -25,7 +25,6 @@ export const useEthTransactionManager = () => {
status: EthTxStatus.Requested,
error: null,
confirmations: 0,
notify: true,
});
const {
contract,
@@ -49,7 +48,6 @@ export const useEthTransactionManager = () => {
update(transaction.id, {
status: EthTxStatus.Error,
error: err as EthereumError,
notify: true,
});
return;
}
@@ -63,7 +61,6 @@ export const useEthTransactionManager = () => {
update(transaction.id, {
status: EthTxStatus.Pending,
txHash: tx.hash,
notify: true,
});
for (let i = 1; i <= requiredConfirmations; i++) {
@@ -80,31 +77,19 @@ export const useEthTransactionManager = () => {
}
if (requiresConfirmation) {
update(transaction.id, {
status: EthTxStatus.Complete,
receipt,
});
update(transaction.id, { status: EthTxStatus.Complete, receipt });
} else {
update(transaction.id, {
status: EthTxStatus.Confirmed,
receipt,
notify: true,
});
update(transaction.id, { status: EthTxStatus.Confirmed, receipt });
}
} catch (err) {
if (err instanceof Error || isEthereumError(err)) {
if (!isExpectedEthereumError(err)) {
update(transaction.id, {
status: EthTxStatus.Error,
error: err,
notify: true,
});
update(transaction.id, { status: EthTxStatus.Error, error: err });
}
} else {
update(transaction.id, {
status: EthTxStatus.Error,
error: new Error('Something went wrong'),
notify: true,
});
}
return;
@@ -27,13 +27,7 @@ export interface EthStoredTxState extends EthTxState {
methodName: ContractMethod;
args: string[];
requiredConfirmations: number;
// whether or not the tx needs external confirmation (IE from a subscription even)
requiresConfirmation: boolean;
// whether or not to notify via toast
// true = force open toast
// false = force close toast
// undefined = leave alone
notify: boolean | undefined;
requiresConfirmation: boolean; // whether or not the tx needs external confirmation (IE from a subscription even)
assetId?: string;
deposit?: DepositBusEventFieldsFragment;
withdrawal?: WithdrawalBusEventFieldsFragment;
@@ -55,7 +49,7 @@ export interface EthTransactionStore {
update?: Partial<
Pick<
EthStoredTxState,
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash' | 'notify'
'status' | 'error' | 'receipt' | 'confirmations' | 'txHash'
>
>
) => void;
@@ -95,7 +89,6 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
requiresConfirmation,
assetId,
withdrawal,
notify: true,
};
set({ transactions: transactions.concat(transaction) });
return transaction.id;
@@ -142,7 +135,6 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
transaction.deposit = deposit;
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
transaction.notify = true;
})
);
},
@@ -1,5 +1,4 @@
import type { ReactNode } from 'react';
import { useEffect } from 'react';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { EtherscanLink } from '@vegaprotocol/environment';
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
@@ -170,13 +169,11 @@ export const useEthereumTransactionToasts = () => {
]);
const dismissTx = useEthTransactionStore((state) => state.dismiss);
const updateTx = useEthTransactionStore((state) => state.update);
const onClose = useCallback(
(tx: EthStoredTxState) => () => {
dismissTx(tx.id);
removeToast(`eth-${tx.id}`);
updateTx(tx.id, { notify: false });
// closes related "Funds released" toast after successful withdrawal
if (
isWithdrawTransaction(tx) &&
@@ -186,7 +183,7 @@ export const useEthereumTransactionToasts = () => {
closeToastBy({ withdrawalId: tx.withdrawal.id });
}
},
[closeToastBy, dismissTx, removeToast, updateTx]
[closeToastBy, dismissTx, removeToast]
);
const fromEthTransaction = useCallback(
@@ -216,25 +213,17 @@ export const useEthereumTransactionToasts = () => {
loader: [EthTxStatus.Pending, EthTxStatus.Complete].includes(tx.status),
content,
closeAfter,
hidden: !tx.notify,
};
},
[onClose]
);
// Only register a subscription once
useEffect(() => {
const unsubscribe = useEthTransactionStore.subscribe(
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
(txs) => {
txs.forEach((tx) => {
setToast(fromEthTransaction(tx));
});
}
);
return () => {
unsubscribe();
};
}, [fromEthTransaction, setToast]);
useEthTransactionStore.subscribe(
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
(txs) => {
txs.forEach((tx) => {
setToast(fromEthTransaction(tx));
});
}
);
};