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
30 changed files with 403 additions and 191 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
@@ -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
+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>
);
@@ -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')"
/>
);
};
+4 -7
View File
@@ -14,9 +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',
@@ -57,7 +57,7 @@ export const Sidebar = () => {
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
@@ -197,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"
/>
);
@@ -245,7 +245,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Deposit')}>
<DepositContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -254,7 +253,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Withdraw')}>
<WithdrawContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -263,7 +261,6 @@ export const SidebarContent = () => {
return (
<ContentWrapper title={t('Transfer')}>
<TransferContainer assetId={view.assetId} />
<GetStarted />
</ContentWrapper>
);
}
@@ -288,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"
>
@@ -75,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(
@@ -87,10 +87,10 @@ 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('button', { name: 'Ready to trade' }));
fireEvent.click(screen.getByRole('link', { name: 'Ready to trade' }));
mockStep = 5;
rerender(
@@ -3,13 +3,13 @@ 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 { useNavigate } from 'react-router-dom';
import {
OnboardingStep,
useGetOnboardingStep,
@@ -24,49 +24,84 @@ interface Props {
}
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
const navigate = useNavigate();
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);
let buttonText = t('Get started');
let onClickHandle = () => {
openVegaWalletDialog();
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);
setViews({ type: ViewType.Deposit }, Routes.MARKET);
setDialogOpen(false);
};
return (
<TradingAnchorButton
{...buttonProps}
href={Links[Routes.DEPOSIT]()}
onClick={() => setDialogOpen(false)}
>
{t('Deposit')}
</TradingAnchorButton>
);
} else if (step >= OnboardingStep.ONBOARDING_ORDER_STEP) {
buttonText = t('Ready to trade');
onClickHandle = () => {
navigate(link);
setViews({ type: ViewType.Order }, Routes.MARKET);
dismiss();
};
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();
@@ -89,25 +124,7 @@ export const GetStarted = ({ lead }: Props) => {
{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} />
+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" />
+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>
+7
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;
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

+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);
@@ -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;
};
@@ -658,7 +658,7 @@ export const LiquidityMonitoringParametersInfoPanel = ({
parentMarket.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}
: {};
: undefined;
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
};
@@ -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>
);
};
@@ -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]);
};