feat(trading): error boundaries for panes, sidebars, pages (#5438)
This commit is contained in:
parent
f56d34fe6e
commit
cf9f313e4c
@ -77,6 +77,7 @@
|
|||||||
"fixStyle": "inline-type-imports"
|
"fixStyle": "inline-type-imports"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"@typescript-eslint/no-useless-constructor": 0,
|
||||||
"curly": ["error", "multi-line"]
|
"curly": ["error", "multi-line"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { titlefy } from '@vegaprotocol/utils';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
import { FeesContainer } from '../../components/fees-container';
|
import { FeesContainer } from '../../components/fees-container';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { usePageTitleStore } from '../../stores';
|
import { usePageTitleStore } from '../../stores';
|
||||||
import { titlefy } from '@vegaprotocol/utils';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export const Fees = () => {
|
export const Fees = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -10,13 +11,17 @@ export const Fees = () => {
|
|||||||
const { updateTitle } = usePageTitleStore((store) => ({
|
const { updateTitle } = usePageTitleStore((store) => ({
|
||||||
updateTitle: store.updateTitle,
|
updateTitle: store.updateTitle,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateTitle(titlefy([title]));
|
updateTitle(titlefy([title]));
|
||||||
}, [updateTitle, title]);
|
}, [updateTitle, title]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container p-4 mx-auto">
|
<ErrorBoundary feature="fees">
|
||||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
<div className="container p-4 mx-auto">
|
||||||
<FeesContainer />
|
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||||
</div>
|
<FeesContainer />
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
const enum LiquidityTabs {
|
const enum LiquidityTabs {
|
||||||
Active = 'active',
|
Active = 'active',
|
||||||
@ -58,19 +59,28 @@ export const LiquidityViewContainer = ({
|
|||||||
name={t('My liquidity provision')}
|
name={t('My liquidity provision')}
|
||||||
hidden={!pubKey}
|
hidden={!pubKey}
|
||||||
>
|
>
|
||||||
<LiquidityContainer
|
<ErrorBoundary feature="liquidity-party">
|
||||||
marketId={marketId}
|
<LiquidityContainer
|
||||||
filter={{ partyId: pubKey || undefined }}
|
marketId={marketId}
|
||||||
/>
|
filter={{ partyId: pubKey || undefined }}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id={LiquidityTabs.Active} name={t('Active')}>
|
<Tab id={LiquidityTabs.Active} name={t('Active')}>
|
||||||
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
|
<ErrorBoundary feature="liquidity-active">
|
||||||
|
<LiquidityContainer
|
||||||
|
marketId={marketId}
|
||||||
|
filter={{ active: true }}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
|
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
|
||||||
<LiquidityContainer
|
<ErrorBoundary feature="liquidity-inactive">
|
||||||
marketId={marketId}
|
<LiquidityContainer
|
||||||
filter={{ active: false }}
|
marketId={marketId}
|
||||||
/>
|
filter={{ active: false }}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
} from '../../components/market-banner';
|
} from '../../components/market-banner';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
interface TradeGridProps {
|
interface TradeGridProps {
|
||||||
market: Market | null;
|
market: Market | null;
|
||||||
@ -62,28 +63,38 @@ const MainGrid = memo(
|
|||||||
name={t('Chart')}
|
name={t('Chart')}
|
||||||
menu={<TradingViews.candles.menu />}
|
menu={<TradingViews.candles.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.candles.component marketId={marketId} />
|
<ErrorBoundary feature="chart">
|
||||||
|
<TradingViews.candles.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="depth" name={t('Depth')}>
|
<Tab id="depth" name={t('Depth')}>
|
||||||
<TradingViews.depth.component marketId={marketId} />
|
<ErrorBoundary feature="depth">
|
||||||
|
<TradingViews.depth.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="liquidity" name={t('Liquidity')}>
|
<Tab id="liquidity" name={t('Liquidity')}>
|
||||||
<TradingViews.liquidity.component marketId={marketId} />
|
<ErrorBoundary feature="liquidity">
|
||||||
|
<TradingViews.liquidity.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
{market &&
|
{market &&
|
||||||
market.tradableInstrument.instrument.product.__typename ===
|
market.tradableInstrument.instrument.product.__typename ===
|
||||||
'Perpetual' ? (
|
'Perpetual' ? (
|
||||||
<Tab id="funding-history" name={t('Funding history')}>
|
<Tab id="funding-history" name={t('Funding history')}>
|
||||||
<TradingViews.funding.component marketId={marketId} />
|
<ErrorBoundary feature="funding-history">
|
||||||
|
<TradingViews.funding.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null}
|
) : null}
|
||||||
{market &&
|
{market &&
|
||||||
market.tradableInstrument.instrument.product.__typename ===
|
market.tradableInstrument.instrument.product.__typename ===
|
||||||
'Perpetual' ? (
|
'Perpetual' ? (
|
||||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||||
<TradingViews.fundingPayments.component
|
<ErrorBoundary feature="funding-payments">
|
||||||
marketId={marketId}
|
<TradingViews.fundingPayments.component
|
||||||
/>
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null}
|
) : null}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@ -96,10 +107,14 @@ const MainGrid = memo(
|
|||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs storageKey="console-trade-grid-main-right">
|
<Tabs storageKey="console-trade-grid-main-right">
|
||||||
<Tab id="orderbook" name={t('Orderbook')}>
|
<Tab id="orderbook" name={t('Orderbook')}>
|
||||||
<TradingViews.orderbook.component marketId={marketId} />
|
<ErrorBoundary feature="orderbook">
|
||||||
|
<TradingViews.orderbook.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="trades" name={t('Trades')}>
|
<Tab id="trades" name={t('Trades')}>
|
||||||
<TradingViews.trades.component marketId={marketId} />
|
<ErrorBoundary feature="trades">
|
||||||
|
<TradingViews.trades.component marketId={marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
@ -118,31 +133,43 @@ const MainGrid = memo(
|
|||||||
name={t('Positions')}
|
name={t('Positions')}
|
||||||
menu={<TradingViews.positions.menu />}
|
menu={<TradingViews.positions.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.positions.component />
|
<ErrorBoundary feature="positions">
|
||||||
|
<TradingViews.positions.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="open-orders"
|
id="open-orders"
|
||||||
name={t('Open')}
|
name={t('Open')}
|
||||||
menu={<TradingViews.activeOrders.menu />}
|
menu={<TradingViews.activeOrders.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.activeOrders.component />
|
<ErrorBoundary feature="activeOrders">
|
||||||
|
<TradingViews.activeOrders.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="closed-orders" name={t('Closed')}>
|
<Tab id="closed-orders" name={t('Closed')}>
|
||||||
<TradingViews.closedOrders.component />
|
<ErrorBoundary feature="closedOrders">
|
||||||
|
<TradingViews.closedOrders.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||||
<TradingViews.rejectedOrders.component />
|
<ErrorBoundary feature="rejectedOrders">
|
||||||
|
<TradingViews.rejectedOrders.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="orders"
|
id="orders"
|
||||||
name={t('All')}
|
name={t('All')}
|
||||||
menu={<TradingViews.orders.menu />}
|
menu={<TradingViews.orders.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.orders.component />
|
<ErrorBoundary feature="orders">
|
||||||
|
<TradingViews.orders.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
{FLAGS.STOP_ORDERS ? (
|
{FLAGS.STOP_ORDERS ? (
|
||||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||||
<TradingViews.stopOrders.component />
|
<ErrorBoundary feature="stop-orders">
|
||||||
|
<TradingViews.stopOrders.component />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null}
|
) : null}
|
||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
@ -153,7 +180,11 @@ const MainGrid = memo(
|
|||||||
name={t('Collateral')}
|
name={t('Collateral')}
|
||||||
menu={<TradingViews.collateral.menu />}
|
menu={<TradingViews.collateral.menu />}
|
||||||
>
|
>
|
||||||
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
|
<ErrorBoundary feature="collateral">
|
||||||
|
<TradingViews.collateral.component
|
||||||
|
pinnedAsset={pinnedAsset}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import { type Market } from '@vegaprotocol/markets';
|
||||||
import { OracleBanner } from '@vegaprotocol/markets';
|
import { OracleBanner } from '@vegaprotocol/markets';
|
||||||
import type { TradingView } from './trade-views';
|
|
||||||
import { TradingViews } from './trade-views';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useT } from '../../lib/use-t';
|
||||||
import {
|
import {
|
||||||
MarketSuccessorBanner,
|
MarketSuccessorBanner,
|
||||||
MarketSuccessorProposalBanner,
|
MarketSuccessorProposalBanner,
|
||||||
MarketTerminationBanner,
|
MarketTerminationBanner,
|
||||||
} from '../../components/market-banner';
|
} from '../../components/market-banner';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
import { useT } from '../../lib/use-t';
|
import { type TradingView } from './trade-views';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { TradingViews } from './trade-views';
|
||||||
|
|
||||||
interface TradePanelsProps {
|
interface TradePanelsProps {
|
||||||
market: Market | null;
|
market: Market | null;
|
||||||
@ -34,7 +35,11 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|||||||
|
|
||||||
// Watch out here, we don't know what component is being rendered
|
// Watch out here, we don't know what component is being rendered
|
||||||
// so watch out for clashes in props
|
// so watch out for clashes in props
|
||||||
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
return (
|
||||||
|
<ErrorBoundary feature={view}>
|
||||||
|
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMenu = () => {
|
const renderMenu = () => {
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
useLinks,
|
useLinks,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
export const MarketsPage = () => {
|
export const MarketsPage = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -34,7 +35,9 @@ export const MarketsPage = () => {
|
|||||||
<div className="h-full my-1 border rounded-sm border-default">
|
<div className="h-full my-1 border rounded-sm border-default">
|
||||||
<Tabs storageKey="console-markets">
|
<Tabs storageKey="console-markets">
|
||||||
<Tab id="open-markets" name={t('Open markets')}>
|
<Tab id="open-markets" name={t('Open markets')}>
|
||||||
<OpenMarkets />
|
<ErrorBoundary feature="markets-open">
|
||||||
|
<OpenMarkets />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="proposed-markets"
|
id="proposed-markets"
|
||||||
@ -49,10 +52,14 @@ export const MarketsPage = () => {
|
|||||||
</TradingAnchorButton>
|
</TradingAnchorButton>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Proposed />
|
<ErrorBoundary feature="markets-proposed">
|
||||||
|
<Proposed />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||||
<Closed />
|
<ErrorBoundary feature="markets-closed">
|
||||||
|
<Closed />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,7 @@ import { DepositsMenu } from '../../components/deposits-menu';
|
|||||||
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
|
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
const WithdrawalsIndicator = () => {
|
const WithdrawalsIndicator = () => {
|
||||||
const { ready } = useIncompleteWithdrawals();
|
const { ready } = useIncompleteWithdrawals();
|
||||||
@ -72,19 +73,29 @@ export const Portfolio = () => {
|
|||||||
name={t('Positions')}
|
name={t('Positions')}
|
||||||
menu={<PositionsMenu />}
|
menu={<PositionsMenu />}
|
||||||
>
|
>
|
||||||
<PositionsContainer allKeys />
|
<ErrorBoundary feature="portfolio-positions">
|
||||||
|
<PositionsContainer allKeys />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="orders" name={t('Orders')}>
|
<Tab id="orders" name={t('Orders')}>
|
||||||
<OrdersContainer />
|
<ErrorBoundary feature="portfolio-orders">
|
||||||
|
<OrdersContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<FillsContainer />
|
<ErrorBoundary feature="portfolio-fills">
|
||||||
|
<FillsContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||||
<FundingPaymentsContainer />
|
<ErrorBoundary feature="portfolio-funding-payments">
|
||||||
|
<FundingPaymentsContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||||
<LedgerContainer />
|
<ErrorBoundary feature="portfolio-ledger">
|
||||||
|
<LedgerContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PortfolioGridChild>
|
</PortfolioGridChild>
|
||||||
@ -101,10 +112,14 @@ export const Portfolio = () => {
|
|||||||
name={t('Collateral')}
|
name={t('Collateral')}
|
||||||
menu={<AccountsMenu />}
|
menu={<AccountsMenu />}
|
||||||
>
|
>
|
||||||
<AccountsContainer />
|
<ErrorBoundary feature="portfolio-accounts">
|
||||||
|
<AccountsContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
|
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
|
||||||
<DepositsContainer />
|
<ErrorBoundary feature="portfolio-deposit">
|
||||||
|
<DepositsContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="withdrawals"
|
id="withdrawals"
|
||||||
@ -112,7 +127,9 @@ export const Portfolio = () => {
|
|||||||
indicator={<WithdrawalsIndicator />}
|
indicator={<WithdrawalsIndicator />}
|
||||||
menu={<WithdrawalsMenu />}
|
menu={<WithdrawalsMenu />}
|
||||||
>
|
>
|
||||||
<WithdrawalsContainer />
|
<ErrorBoundary feature="portfolio-deposit">
|
||||||
|
<WithdrawalsContainer />
|
||||||
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PortfolioGridChild>
|
</PortfolioGridChild>
|
||||||
|
@ -18,6 +18,7 @@ import { usePageTitleStore } from '../../stores';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { titlefy } from '@vegaprotocol/utils';
|
import { titlefy } from '@vegaprotocol/utils';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
const Nav = () => {
|
const Nav = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -65,7 +66,7 @@ export const Referrals = () => {
|
|||||||
}, [updateTitle, t]);
|
}, [updateTitle, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ErrorBoundary feature="referrals">
|
||||||
<LandingBanner />
|
<LandingBanner />
|
||||||
|
|
||||||
{showNav && <Nav />}
|
{showNav && <Nav />}
|
||||||
@ -107,6 +108,6 @@ export const Referrals = () => {
|
|||||||
</TradingAnchorButton>
|
</TradingAnchorButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { titlefy } from '@vegaprotocol/utils';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { RewardsContainer } from '../../components/rewards-container';
|
import { RewardsContainer } from '../../components/rewards-container';
|
||||||
import { usePageTitleStore } from '../../stores';
|
import { usePageTitleStore } from '../../stores';
|
||||||
import { titlefy } from '@vegaprotocol/utils';
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export const Rewards = () => {
|
export const Rewards = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -14,9 +15,11 @@ export const Rewards = () => {
|
|||||||
updateTitle(titlefy([title]));
|
updateTitle(titlefy([title]));
|
||||||
}, [updateTitle, title]);
|
}, [updateTitle, title]);
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-4">
|
<ErrorBoundary feature="rewards">
|
||||||
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
<div className="container mx-auto p-4">
|
||||||
<RewardsContainer />
|
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
|
||||||
</div>
|
<RewardsContainer />
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { ErrorBoundary } from './error-boundary';
|
||||||
|
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/logger', () => ({
|
||||||
|
localLoggerFactory: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ErrorBoundary', () => {
|
||||||
|
const mockLogError = jest.fn();
|
||||||
|
const originalConsoleError = console.error;
|
||||||
|
const mockLoggerFactory = localLoggerFactory as jest.Mock;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
console.error = () => {};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
console.error = originalConsoleError;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockLoggerFactory.mockImplementation(() => ({
|
||||||
|
error: mockLogError,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockLogError.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders children', () => {
|
||||||
|
render(
|
||||||
|
<ErrorBoundary feature="feature">
|
||||||
|
<div data-testid="child" />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders fallback ui and logs an error', () => {
|
||||||
|
const error = new Error('bork!');
|
||||||
|
const BorkedComponent = () => {
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ErrorBoundary feature="test-feature">
|
||||||
|
<BorkedComponent />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
|
||||||
|
expect(mockLogError).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockLogError).toHaveBeenCalledWith(
|
||||||
|
error.message,
|
||||||
|
expect.stringContaining('componentStack')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders fallback render prop if error', () => {
|
||||||
|
const error = new Error('bork!');
|
||||||
|
const BorkedComponent = () => {
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ErrorBoundary
|
||||||
|
feature="test-feature"
|
||||||
|
fallback={<div data-testid="custom-ui" />}
|
||||||
|
>
|
||||||
|
<BorkedComponent />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('custom-ui')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
53
apps/trading/components/error-boundary/error-boundary.tsx
Normal file
53
apps/trading/components/error-boundary/error-boundary.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { localLoggerFactory, type LocalLogger } from '@vegaprotocol/logger';
|
||||||
|
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
||||||
|
import { useT } from '../../lib/use-t';
|
||||||
|
|
||||||
|
interface ErrorBoundaryProps {
|
||||||
|
children: ReactNode;
|
||||||
|
feature: string;
|
||||||
|
fallback?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends Component<
|
||||||
|
ErrorBoundaryProps,
|
||||||
|
ErrorBoundaryState
|
||||||
|
> {
|
||||||
|
logger: LocalLogger | null = null;
|
||||||
|
|
||||||
|
constructor(props: ErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.logger = localLoggerFactory({ application: props.feature });
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
hasError: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError() {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||||
|
if (this.logger) {
|
||||||
|
this.logger.error(error.message, JSON.stringify(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return this.props.fallback || <DefaultFallback />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultFallback = () => {
|
||||||
|
const t = useT();
|
||||||
|
return <p className="text-xs">{t('Something went wrong')}</p>;
|
||||||
|
};
|
1
apps/trading/components/error-boundary/index.ts
Normal file
1
apps/trading/components/error-boundary/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ErrorBoundary } from './error-boundary';
|
@ -16,6 +16,7 @@ import { GetStarted } from '../welcome-dialog';
|
|||||||
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
import { ErrorBoundary } from '../error-boundary';
|
||||||
|
|
||||||
export enum ViewType {
|
export enum ViewType {
|
||||||
Order = 'Order',
|
Order = 'Order',
|
||||||
@ -163,12 +164,14 @@ export const SidebarContent = () => {
|
|||||||
if (params.marketId) {
|
if (params.marketId) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper>
|
<ContentWrapper>
|
||||||
<DealTicketContainer
|
<ErrorBoundary feature="deal-ticket">
|
||||||
marketId={params.marketId}
|
<DealTicketContainer
|
||||||
onDeposit={(assetId) =>
|
marketId={params.marketId}
|
||||||
setViews({ type: ViewType.Deposit, assetId }, currentRouteId)
|
onDeposit={(assetId) =>
|
||||||
}
|
setViews({ type: ViewType.Deposit, assetId }, currentRouteId)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
<GetStarted />
|
<GetStarted />
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
@ -181,7 +184,9 @@ export const SidebarContent = () => {
|
|||||||
if (params.marketId) {
|
if (params.marketId) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper>
|
<ContentWrapper>
|
||||||
<MarketInfoAccordionContainer marketId={params.marketId} />
|
<ErrorBoundary feature="market-info">
|
||||||
|
<MarketInfoAccordionContainer marketId={params.marketId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -192,7 +197,9 @@ export const SidebarContent = () => {
|
|||||||
if (view.type === ViewType.Deposit) {
|
if (view.type === ViewType.Deposit) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Deposit')}>
|
<ContentWrapper title={t('Deposit')}>
|
||||||
<DepositContainer assetId={view.assetId} />
|
<ErrorBoundary feature="deposit">
|
||||||
|
<DepositContainer assetId={view.assetId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -200,7 +207,9 @@ export const SidebarContent = () => {
|
|||||||
if (view.type === ViewType.Withdraw) {
|
if (view.type === ViewType.Withdraw) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Withdraw')}>
|
<ContentWrapper title={t('Withdraw')}>
|
||||||
<WithdrawContainer assetId={view.assetId} />
|
<ErrorBoundary feature="withdraw">
|
||||||
|
<WithdrawContainer assetId={view.assetId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -208,7 +217,9 @@ export const SidebarContent = () => {
|
|||||||
if (view.type === ViewType.Transfer) {
|
if (view.type === ViewType.Transfer) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Transfer')}>
|
<ContentWrapper title={t('Transfer')}>
|
||||||
<TransferContainer assetId={view.assetId} />
|
<ErrorBoundary feature="transfer">
|
||||||
|
<TransferContainer assetId={view.assetId} />
|
||||||
|
</ErrorBoundary>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -216,7 +227,9 @@ export const SidebarContent = () => {
|
|||||||
if (view.type === ViewType.Settings) {
|
if (view.type === ViewType.Settings) {
|
||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Settings')}>
|
<ContentWrapper title={t('Settings')}>
|
||||||
<Settings />
|
<ErrorBoundary feature="settings">
|
||||||
|
<Settings />
|
||||||
|
</ErrorBoundary>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ export interface LoggerProps extends LoggerConf {
|
|||||||
|
|
||||||
export const useLogger = ({ dsn, env, ...props }: LoggerProps) => {
|
export const useLogger = ({ dsn, env, ...props }: LoggerProps) => {
|
||||||
const logger = useRef<LocalLogger | null>(null);
|
const logger = useRef<LocalLogger | null>(null);
|
||||||
|
|
||||||
if (!logger.current) {
|
if (!logger.current) {
|
||||||
logger.current = localLoggerFactory(props);
|
logger.current = localLoggerFactory(props);
|
||||||
if (dsn) {
|
if (dsn) {
|
||||||
|
@ -25,6 +25,7 @@ describe('LocalLogger', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logger should be properly instantiate', () => {
|
it('logger should be properly instantiate', () => {
|
||||||
const logger = localLoggerFactory({});
|
const logger = localLoggerFactory({});
|
||||||
expect(logger).toBeInstanceOf(LocalLogger);
|
expect(logger).toBeInstanceOf(LocalLogger);
|
||||||
@ -50,7 +51,7 @@ describe('LocalLogger', () => {
|
|||||||
logger[method]('test', 'test2');
|
logger[method]('test', 'test2');
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
expect(console[consoleMethod]).toHaveBeenCalledWith(
|
expect(console[consoleMethod]).toHaveBeenCalledWith(
|
||||||
`trading:${methodToLevel[i]}: `,
|
`trading:${methodToLevel[i]}:`,
|
||||||
'test',
|
'test',
|
||||||
'test2'
|
'test2'
|
||||||
);
|
);
|
||||||
@ -110,7 +111,7 @@ describe('LocalLogger', () => {
|
|||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
expect(console.debug).toHaveBeenCalledWith(
|
expect(console.debug).toHaveBeenCalledWith(
|
||||||
'trading:debug: ',
|
'trading:debug:',
|
||||||
'test',
|
'test',
|
||||||
'test1'
|
'test1'
|
||||||
);
|
);
|
||||||
|
@ -62,6 +62,7 @@ export class LocalLogger {
|
|||||||
}
|
}
|
||||||
private tags: string[] = [];
|
private tags: string[] = [];
|
||||||
private _application = 'trading';
|
private _application = 'trading';
|
||||||
|
|
||||||
constructor(conf: LoggerConf) {
|
constructor(conf: LoggerConf) {
|
||||||
if (conf.application) {
|
if (conf.application) {
|
||||||
this._application = conf.application;
|
this._application = conf.application;
|
||||||
@ -69,6 +70,7 @@ export class LocalLogger {
|
|||||||
this.tags = [...(conf.tags || [])];
|
this.tags = [...(conf.tags || [])];
|
||||||
this._logLevel = conf.logLevel || this._logLevel;
|
this._logLevel = conf.logLevel || this._logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public debug(...args: ConsoleArg[]) {
|
public debug(...args: ConsoleArg[]) {
|
||||||
this._log('debug', 'debug', args);
|
this._log('debug', 'debug', args);
|
||||||
}
|
}
|
||||||
@ -101,7 +103,7 @@ export class LocalLogger {
|
|||||||
) {
|
) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console[logMethod].apply(console, [
|
console[logMethod].apply(console, [
|
||||||
`${this._application}:${level}: `,
|
`${this._application}:${level}:`,
|
||||||
...args,
|
...args,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user