feat(trading): make Sentry only after opt in (#3448)
This commit is contained in:
parent
d6ecdf80fa
commit
b7a440132d
@ -32,7 +32,7 @@ describe('Navbar', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('Resources dropdown should be correctly rendered', () => {
|
||||
const resourceSelector = 'ul li:last-child';
|
||||
const resourceSelector = 'ul li:contains(Resources)';
|
||||
['Docs', 'Give Feedback'].forEach((text, index) => {
|
||||
cy.get('nav').find(resourceSelector).contains('Resources').click();
|
||||
cy.get('nav')
|
||||
@ -91,7 +91,7 @@ describe('Navbar', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer')
|
||||
.find('[data-testid="theme-switcher"]')
|
||||
.find('[data-testid="Settings"]')
|
||||
.should('be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
|
45
apps/trading-e2e/src/integration/settings.cy.ts
Normal file
45
apps/trading-e2e/src/integration/settings.cy.ts
Normal file
@ -0,0 +1,45 @@
|
||||
describe('Settings page', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
cy.get('[role=dialog]').within(() => {
|
||||
cy.getByTestId('dialog-close').click();
|
||||
});
|
||||
cy.get('[aria-label="cog icon"]').click();
|
||||
});
|
||||
});
|
||||
it('telemetry checkbox should work well', () => {
|
||||
cy.location('hash').should('equal', '#/settings');
|
||||
cy.getByTestId('telemetry-approval').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
cy.get('[for="telemetry-approval"]').click();
|
||||
cy.getByTestId('telemetry-approval').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
cy.reload();
|
||||
cy.getByTestId('telemetry-approval').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
cy.get('[for="telemetry-approval"]').click();
|
||||
cy.getByTestId('telemetry-approval').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
cy.reload();
|
||||
cy.getByTestId('telemetry-approval').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
});
|
||||
});
|
@ -11,4 +11,4 @@ NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
2
apps/trading/client-pages/settings/index.ts
Normal file
2
apps/trading/client-pages/settings/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { Settings as default } from './settings';
|
||||
export { SettingsButton } from './settings-button';
|
16
apps/trading/client-pages/settings/settings-button.tsx
Normal file
16
apps/trading/client-pages/settings/settings-button.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Icon, NavigationLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { COG } from '@blueprintjs/icons/src/generated/iconNames';
|
||||
|
||||
export const SettingsButton = ({ withMobile }: { withMobile?: boolean }) => {
|
||||
return (
|
||||
<NavigationLink data-testid="Settings" to={Links[Routes.SETTINGS]()}>
|
||||
{withMobile ? (
|
||||
t('Settings')
|
||||
) : (
|
||||
<Icon name={COG} className="!align-middle" />
|
||||
)}
|
||||
</NavigationLink>
|
||||
);
|
||||
};
|
45
apps/trading/client-pages/settings/settings.tsx
Normal file
45
apps/trading/client-pages/settings/settings.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TelemetryApproval } from '../../components/welcome-dialog/telemetry-approval';
|
||||
import {
|
||||
Divider,
|
||||
RoundedWrapper,
|
||||
Switch,
|
||||
ThemeSwitcher,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const Settings = () => {
|
||||
const { theme, setTheme } = useThemeSwitcher();
|
||||
const text = t(theme === 'dark' ? 'Light mode' : 'Dark mode');
|
||||
return (
|
||||
<div className="py-16 px-8 flex w-full justify-center">
|
||||
<div className="lg:min-w-[700px] min-w-[300px]">
|
||||
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
|
||||
{t('Settings')}
|
||||
</h1>
|
||||
<div className="mt-8 text-base text-neutral-500 dark:text-neutral-400">
|
||||
{t('Changes are applied automatically.')}
|
||||
</div>
|
||||
<div className="mt-10 w-full">
|
||||
<RoundedWrapper paddingBottom>
|
||||
<div className="flex justify-between py-3">
|
||||
<div className="flex shrink">
|
||||
<ThemeSwitcher />
|
||||
<label htmlFor="theme-switcher" className="self-center text-lg">
|
||||
{text}
|
||||
</label>
|
||||
</div>
|
||||
<Switch
|
||||
name="settings-theme-switch"
|
||||
onCheckedChange={() => setTheme()}
|
||||
checked={theme === 'dark'}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<TelemetryApproval isSettingsPage />
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -10,7 +10,6 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
@ -24,6 +23,7 @@ import {
|
||||
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import { SettingsButton } from '../../client-pages/settings';
|
||||
|
||||
export const Navbar = ({
|
||||
theme = 'system',
|
||||
@ -45,7 +45,7 @@ export const Navbar = ({
|
||||
theme={theme}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<SettingsButton />
|
||||
<VegaWalletConnectButton />
|
||||
</>
|
||||
}
|
||||
@ -108,15 +108,15 @@ export const Navbar = ({
|
||||
)}
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-8 [.drawer-content_&]:mt-4"
|
||||
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-4 [.drawer-content_&]:mt-4"
|
||||
hide={[
|
||||
NavigationBreakpoint.Small,
|
||||
NavigationBreakpoint.Narrow,
|
||||
NavigationBreakpoint.Full,
|
||||
]}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full text-black dark:text-white">
|
||||
<ThemeSwitcher withMobile />
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<SettingsButton withMobile />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
|
@ -21,12 +21,12 @@ describe('Risk notice dialog', () => {
|
||||
});
|
||||
|
||||
it.each`
|
||||
assertion | network
|
||||
${'displays'} | ${Networks.MAINNET}
|
||||
${'does not display'} | ${Networks.CUSTOM}
|
||||
${'does not display'} | ${Networks.DEVNET}
|
||||
${'does not display'} | ${Networks.STAGNET3}
|
||||
${'does not display'} | ${Networks.TESTNET}
|
||||
assertion | network
|
||||
${'displays'} | ${Networks.MAINNET}
|
||||
${'displays'} | ${Networks.CUSTOM}
|
||||
${'displays'} | ${Networks.DEVNET}
|
||||
${'displays'} | ${Networks.STAGNET3}
|
||||
${'displays'} | ${Networks.TESTNET}
|
||||
`(
|
||||
'$assertion the risk notice on $network',
|
||||
async ({ assertion, network }) => {
|
||||
@ -43,13 +43,7 @@ describe('Risk notice dialog', () => {
|
||||
{ wrapper: BrowserRouter }
|
||||
);
|
||||
|
||||
if (assertion === 'displays') {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(screen.queryByText(introText)).toBeInTheDocument();
|
||||
} else {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(screen.queryByText(introText)).not.toBeInTheDocument();
|
||||
}
|
||||
expect(screen.queryByText(introText)).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { LocalStorage } from '@vegaprotocol/utils';
|
||||
import { RISK_ACCEPTED_KEY } from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
@ -32,6 +33,9 @@ export const RiskNoticeDialog = ({ onClose }: Props) => {
|
||||
)}
|
||||
</p>
|
||||
<Button onClick={handleAcceptRisk}>{t('I understand, Continue')}</Button>
|
||||
<div className="text-base mt-8">
|
||||
<TelemetryApproval />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
describe('TelemetryApproval', () => {
|
||||
it('click on checkbox should be properly handled', () => {
|
||||
render(<TelemetryApproval />);
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
act(() => {
|
||||
screen.getByRole('checkbox').click();
|
||||
});
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
import { Checkbox } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
|
||||
export const TelemetryApproval = ({
|
||||
isSettingsPage,
|
||||
}: {
|
||||
isSettingsPage?: boolean;
|
||||
}) => {
|
||||
const [isApproved, setIsApproved] = useTelemetryApproval();
|
||||
return (
|
||||
<div className="flex flex-col px-2 py-3">
|
||||
<div className="mr-4" role="form">
|
||||
<Checkbox
|
||||
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
|
||||
checked={isApproved}
|
||||
name="telemetry-approval"
|
||||
onCheckedChange={() => setIsApproved(!isApproved)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-neutral-600 dark:text-neutral-300 ml-6">
|
||||
<span>
|
||||
{t(
|
||||
'Help identify bugs and improve the service by sharing anonymous usage data.'
|
||||
)}
|
||||
{!isSettingsPage &&
|
||||
' ' + t('You can change this in your settings at any time.')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -4,7 +4,6 @@ import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider, useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { activeMarketsProvider } from '@vegaprotocol/market-list';
|
||||
import { useEnvironment, Networks } from '@vegaprotocol/environment';
|
||||
import * as constants from '../constants';
|
||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
||||
import { WelcomeNoticeDialog } from './welcome-notice-dialog';
|
||||
@ -13,37 +12,38 @@ import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { pathname } = useLocation();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
let dialogContent: React.ReactNode = null;
|
||||
let dialogContent: React.ReactNode;
|
||||
let title = '';
|
||||
let size: 'small' | 'medium' = 'small';
|
||||
let onClose: ((open: boolean) => void) | undefined = undefined;
|
||||
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: activeMarketsProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
|
||||
const { update, shouldDisplayWelcomeDialog } = useGlobalStore((store) => ({
|
||||
update: store.update,
|
||||
shouldDisplayWelcomeDialog: store.shouldDisplayWelcomeDialog,
|
||||
}));
|
||||
const isRiskDialogNeeded =
|
||||
riskAccepted !== 'true' && VEGA_ENV === Networks.MAINNET;
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const shouldDisplayWelcomeDialog = useGlobalStore(
|
||||
(store) => store.shouldDisplayWelcomeDialog
|
||||
);
|
||||
const isRiskDialogNeeded = riskAccepted !== 'true' && !('Cypress' in window);
|
||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
||||
const onClose = useCallback(() => {
|
||||
const onCloseDialog = useCallback(() => {
|
||||
update({ shouldDisplayWelcomeDialog: isRiskDialogNeeded });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
dialogContent = null;
|
||||
}, [update, isRiskDialogNeeded]);
|
||||
|
||||
if (isRiskDialogNeeded) {
|
||||
dialogContent = <RiskNoticeDialog onClose={onClose} />;
|
||||
dialogContent = <RiskNoticeDialog onClose={onCloseDialog} />;
|
||||
title = t('WARNING');
|
||||
size = 'medium';
|
||||
} else if (isWelcomeDialogNeeded && data?.length === 0) {
|
||||
dialogContent = <WelcomeNoticeDialog />;
|
||||
onClose = onCloseDialog;
|
||||
} else if (isWelcomeDialogNeeded && (data?.length || 0) > 0) {
|
||||
dialogContent = <WelcomeLandingDialog onClose={onClose} />;
|
||||
dialogContent = <WelcomeLandingDialog onClose={onCloseDialog} />;
|
||||
onClose = onCloseDialog;
|
||||
} else {
|
||||
dialogContent = null as React.ReactNode;
|
||||
}
|
||||
|
||||
return (
|
||||
|
48
apps/trading/lib/hooks/use-telemetry-approval.spec.ts
Normal file
48
apps/trading/lib/hooks/use-telemetry-approval.spec.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
|
||||
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
|
||||
|
||||
const mockSetValue = jest.fn();
|
||||
const mockRemoveValue = jest.fn();
|
||||
jest.mock('@vegaprotocol/utils');
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useLocalStorage: jest
|
||||
.fn()
|
||||
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
|
||||
}));
|
||||
|
||||
describe('useTelemetryApproval', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('hook should return proper array', () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
expect(res.result.current[0]).toEqual(false);
|
||||
expect(res.result.current[1]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('hook should init stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](true);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryInit).toHaveBeenCalled();
|
||||
expect(mockSetValue).toHaveBeenCalledWith('1');
|
||||
});
|
||||
});
|
||||
|
||||
it('hook should close stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](false);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryClose).toHaveBeenCalled();
|
||||
expect(mockRemoveValue).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
24
apps/trading/lib/hooks/use-telemetry-approval.ts
Normal file
24
apps/trading/lib/hooks/use-telemetry-approval.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
|
||||
import { ENV } from '../config';
|
||||
export const STORAGE_KEY = 'vega_telemetry_approval';
|
||||
|
||||
export const useTelemetryApproval = (): [
|
||||
value: boolean,
|
||||
setValue: (value: boolean) => void
|
||||
] => {
|
||||
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
|
||||
const setApprove = useCallback(
|
||||
(value: boolean) => {
|
||||
if (value) {
|
||||
SentryInit(ENV.dsn, ENV.envName);
|
||||
return setValue('1');
|
||||
}
|
||||
SentryClose();
|
||||
removeValue();
|
||||
},
|
||||
[setValue, removeValue]
|
||||
);
|
||||
return [Boolean(value), setApprove];
|
||||
};
|
@ -36,6 +36,7 @@ import { Navbar } from '../components/navbar';
|
||||
import { ENV } from '../lib/config';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@ -145,7 +146,10 @@ const PartyData = () => {
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
useVegaEagerConnect(Connectors);
|
||||
useEthereumEagerConnect(ENV.dsn);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: ENV.dsn, env: ENV.envName } : {}
|
||||
);
|
||||
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
@ -26,12 +26,17 @@ const LazyPortfolio = dynamic(() => import('../client-pages/portfolio'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const LazySettings = dynamic(() => import('../client-pages/settings'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export enum Routes {
|
||||
HOME = '/',
|
||||
MARKET = '/markets',
|
||||
MARKETS = '/markets/all',
|
||||
PORTFOLIO = '/portfolio',
|
||||
LIQUIDITY = '/liquidity',
|
||||
LIQUIDITY = 'liquidity/:marketId',
|
||||
SETTINGS = 'settings',
|
||||
}
|
||||
|
||||
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
|
||||
@ -45,6 +50,7 @@ export const Links: ConsoleLinks = {
|
||||
marketId
|
||||
? trimEnd(`${Routes.LIQUIDITY}/${marketId}`, '/')
|
||||
: Routes.LIQUIDITY,
|
||||
[Routes.SETTINGS]: () => Routes.SETTINGS,
|
||||
};
|
||||
|
||||
const routerConfig: RouteObject[] = [
|
||||
@ -87,6 +93,10 @@ const routerConfig: RouteObject[] = [
|
||||
path: Routes.PORTFOLIO,
|
||||
element: <LazyPortfolio />,
|
||||
},
|
||||
{
|
||||
path: Routes.SETTINGS,
|
||||
element: <LazySettings />,
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: (
|
||||
|
@ -1,14 +1,9 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
import { ENV } from './lib/config/env';
|
||||
import { LocalStorage, SentryInit } from '@vegaprotocol/utils';
|
||||
import { STORAGE_KEY } from './lib/hooks/use-telemetry-approval';
|
||||
|
||||
const { dsn } = ENV;
|
||||
|
||||
if (dsn) {
|
||||
Sentry.init({
|
||||
dsn,
|
||||
integrations: [new BrowserTracing()],
|
||||
tracesSampleRate: 1,
|
||||
environment: ENV.envName,
|
||||
});
|
||||
const { dsn, envName } = ENV;
|
||||
const isTelemetryApproved = !!LocalStorage.getItem(STORAGE_KEY);
|
||||
if (dsn && isTelemetryApproved) {
|
||||
SentryInit(dsn, envName);
|
||||
}
|
||||
|
@ -1,24 +1,18 @@
|
||||
import { useRef } from 'react';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import type { LocalLogger, LoggerConf } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory, SentryInit } from '@vegaprotocol/utils';
|
||||
|
||||
interface Props extends LoggerConf {
|
||||
export interface LoggerProps extends LoggerConf {
|
||||
dsn?: string;
|
||||
env?: string;
|
||||
}
|
||||
|
||||
export const useLogger = ({ dsn, ...props }: Props) => {
|
||||
export const useLogger = ({ dsn, env, ...props }: LoggerProps) => {
|
||||
const logger = useRef<LocalLogger | null>(null);
|
||||
if (!logger.current) {
|
||||
logger.current = localLoggerFactory(props);
|
||||
if (dsn) {
|
||||
Sentry.init({
|
||||
dsn,
|
||||
integrations: [new BrowserTracing()],
|
||||
tracesSampleRate: 1,
|
||||
defaultIntegrations: false,
|
||||
});
|
||||
SentryInit(dsn, env);
|
||||
}
|
||||
}
|
||||
return logger.current;
|
||||
|
41
libs/ui-toolkit/src/components/divider/divider.stories.tsx
Normal file
41
libs/ui-toolkit/src/components/divider/divider.stories.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import className from 'classnames';
|
||||
import { Divider } from './divider';
|
||||
|
||||
export default {
|
||||
title: 'Divider',
|
||||
component: Divider,
|
||||
} as ComponentMeta<typeof Divider>;
|
||||
|
||||
const Template: ComponentStory<typeof Divider> = (args) => {
|
||||
return (
|
||||
<div
|
||||
className={className('flex', {
|
||||
'flex-col': args?.orientation !== 'vertical',
|
||||
'h-[50px]': args?.orientation === 'vertical',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={className(
|
||||
'h-[50px]',
|
||||
args?.orientation !== 'vertical' ? 'w-full' : 'w-1/2'
|
||||
)}
|
||||
/>
|
||||
<Divider orientation={args?.orientation} />
|
||||
<div
|
||||
className={className(
|
||||
'h-[50px]',
|
||||
args?.orientation !== 'vertical' ? 'w-full' : 'w-2/2'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
||||
|
||||
export const Vertical = Template.bind({});
|
||||
Vertical.args = {
|
||||
orientation: 'vertical',
|
||||
};
|
19
libs/ui-toolkit/src/components/divider/divider.tsx
Normal file
19
libs/ui-toolkit/src/components/divider/divider.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { forwardRef } from 'react';
|
||||
import * as Separator from '@radix-ui/react-separator';
|
||||
|
||||
interface DividerProps {
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
decorative?: boolean;
|
||||
}
|
||||
|
||||
export const Divider = forwardRef<HTMLDivElement, DividerProps>(
|
||||
({ orientation = 'horizontal', decorative }, ref) => {
|
||||
return (
|
||||
<Separator.Root
|
||||
ref={ref}
|
||||
className="bg-neutral-700 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px data-[orientation=vertical]:mx-3 data-[orientation=horizontal]:my-3"
|
||||
{...{ orientation, decorative }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
1
libs/ui-toolkit/src/components/divider/index.ts
Normal file
1
libs/ui-toolkit/src/components/divider/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Divider } from './divider';
|
@ -9,6 +9,7 @@ export * from './callout';
|
||||
export * from './checkbox';
|
||||
export * from './copy-with-tooltip';
|
||||
export * from './dialog';
|
||||
export * from './divider';
|
||||
export * from './drawer';
|
||||
export * from './dropdown-menu';
|
||||
export * from './form-group';
|
||||
@ -37,6 +38,7 @@ export * from './simple-grid';
|
||||
export * from './slider';
|
||||
export * from './sparkline';
|
||||
export * from './splash';
|
||||
export * from './switch';
|
||||
export * from './syntax-highlighter';
|
||||
export * from './tabs';
|
||||
export * from './text-area';
|
||||
|
1
libs/ui-toolkit/src/components/switch/index.ts
Normal file
1
libs/ui-toolkit/src/components/switch/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Switch } from './switch';
|
31
libs/ui-toolkit/src/components/switch/switch.stories.tsx
Normal file
31
libs/ui-toolkit/src/components/switch/switch.stories.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import type { Story, ComponentMeta } from '@storybook/react';
|
||||
import type { SwitchProps } from './switch';
|
||||
import { Switch } from './switch';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default {
|
||||
component: Switch,
|
||||
title: 'Switch',
|
||||
} as ComponentMeta<typeof Switch>;
|
||||
|
||||
const Template: Story<SwitchProps> = (args) => {
|
||||
const [checked, setChecked] = useState(args.checked || false);
|
||||
return (
|
||||
<Switch
|
||||
{...args}
|
||||
checked={checked}
|
||||
onCheckedChange={(checked) => setChecked(checked)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
name: 'switch',
|
||||
};
|
||||
|
||||
export const WithTextLabel = Template.bind({});
|
||||
WithTextLabel.args = {
|
||||
name: 'switch',
|
||||
labelText: 'Light mode',
|
||||
};
|
38
libs/ui-toolkit/src/components/switch/switch.tsx
Normal file
38
libs/ui-toolkit/src/components/switch/switch.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import * as RootSwitch from '@radix-ui/react-switch';
|
||||
|
||||
export interface SwitchProps {
|
||||
name?: string;
|
||||
onCheckedChange?: (checked: boolean) => void;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
labelText?: ReactNode | string;
|
||||
}
|
||||
|
||||
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
|
||||
(
|
||||
{ name = 'switch', labelText, onCheckedChange, checked = false, disabled },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<div className="flex items-center justify-start">
|
||||
<RootSwitch.Root
|
||||
className="w-[41px] h-[18px] rounded bg-vega-light-200 dark:bg-neutral-700 rounded-full relative data-[state=checked]:bg-vega-light-200 dark:data-[state=checked]:bg-neutral-700 outline-none cursor-default"
|
||||
id={`switch-${name}`}
|
||||
onCheckedChange={onCheckedChange}
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
>
|
||||
<RootSwitch.Thumb className="block w-[18px] h-[18px] bg-black dark:bg-white rounded-full transition-transform duration-100 translate-x-0.3 will-change-transform data-[state=checked]:translate-x-[23px]" />
|
||||
</RootSwitch.Root>
|
||||
{labelText && (
|
||||
<label htmlFor={`switch-${name}`} className="ml-2">
|
||||
{labelText}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
@ -17,6 +17,7 @@ export const ThemeSwitcher = ({
|
||||
onClick={() => setTheme()}
|
||||
className={className}
|
||||
data-testid="theme-switcher"
|
||||
id="theme-switcher"
|
||||
>
|
||||
{theme === 'dark' && <SunIcon />}
|
||||
{theme === 'light' && <MoonIcon />}
|
||||
|
@ -17,3 +17,4 @@ export * from './lib/remove-0x';
|
||||
export * from './lib/remove-pagination-wrapper';
|
||||
export * from './lib/time';
|
||||
export * from './lib/validate';
|
||||
export * from './lib/sentry-utils';
|
||||
|
26
libs/utils/src/lib/sentry-utils.spec.ts
Normal file
26
libs/utils/src/lib/sentry-utils.spec.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { SentryInit, SentryClose } from './sentry-utils';
|
||||
|
||||
jest.mock('@sentry/nextjs');
|
||||
|
||||
describe('Sentry utlis', () => {
|
||||
describe('SentryInit', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize', () => {
|
||||
SentryInit('sentry.dsn');
|
||||
expect(Sentry.init).toHaveBeenCalled();
|
||||
});
|
||||
it('should do nothing', () => {
|
||||
SentryInit('');
|
||||
expect(Sentry.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close', () => {
|
||||
SentryClose();
|
||||
expect(Sentry.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
15
libs/utils/src/lib/sentry-utils.ts
Normal file
15
libs/utils/src/lib/sentry-utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
|
||||
export const SentryInit = (dsn: string, env?: string) => {
|
||||
if (dsn) {
|
||||
Sentry.init({
|
||||
dsn,
|
||||
integrations: [new BrowserTracing()],
|
||||
tracesSampleRate: 1,
|
||||
environment: env || '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const SentryClose = () => Sentry.close();
|
@ -1,3 +1,4 @@
|
||||
import type { LoggerProps } from '@vegaprotocol/react-helpers';
|
||||
import { useLocalStorage, useLogger } from '@vegaprotocol/react-helpers';
|
||||
import type { Web3ReactHooks } from '@web3-react/core';
|
||||
import { MetaMask } from '@web3-react/metamask';
|
||||
@ -8,12 +9,12 @@ import { useWeb3ConnectStore } from './web3-connect-store';
|
||||
|
||||
export const ETHEREUM_EAGER_CONNECT = 'ethereum-eager-connect';
|
||||
|
||||
export const useEagerConnect = (sentryDsn?: string) => {
|
||||
export const useEagerConnect = (loggerConf: LoggerProps) => {
|
||||
const connectors = useWeb3ConnectStore((store) => store.connectors);
|
||||
const [eagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
|
||||
const attemptedRef = useRef(false);
|
||||
|
||||
const logger = useLogger({ dsn: sentryDsn });
|
||||
const logger = useLogger(loggerConf);
|
||||
|
||||
useEffect(() => {
|
||||
if (attemptedRef.current || 'Cypress' in window) return;
|
||||
|
@ -31,7 +31,9 @@
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.1",
|
||||
"@radix-ui/react-select": "^1.2.0",
|
||||
"@radix-ui/react-separator": "^1.0.2",
|
||||
"@radix-ui/react-slider": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.2",
|
||||
"@radix-ui/react-tooltip": "^1.0.3",
|
||||
"@sentry/nextjs": "^6.19.3",
|
||||
@ -202,6 +204,8 @@
|
||||
"*.{ts,tsx,js,jsx}": "yarn eslint --fix"
|
||||
},
|
||||
"resolutions": {
|
||||
"graphql": "15.8.0"
|
||||
"graphql": "15.8.0",
|
||||
"//": "workaround storybook issue: https://github.com/storybookjs/storybook/issues/21642",
|
||||
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0"
|
||||
}
|
||||
}
|
||||
|
42
yarn.lock
42
yarn.lock
@ -4609,6 +4609,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.1"
|
||||
|
||||
"@radix-ui/react-primitive@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz#54e22f49ca59ba88d8143090276d50b93f8a7053"
|
||||
integrity sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.1"
|
||||
|
||||
"@radix-ui/react-radio-group@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.1.tgz#564549b3e0a5905367dfe9adfe7b0e245cbdb640"
|
||||
@ -4670,6 +4678,14 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-separator@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.2.tgz#52417c8bfae1e4ef87356b2f7bffbc3dbbeddf23"
|
||||
integrity sha512-lZoAG/rS2jzb/OSvyBrpN3dmikw20ewmWx1GkM1VldbDyD0DACCbH9LIXSrqyS/2mE1VYKOHmyq5W90Dx4ryqA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.2"
|
||||
|
||||
"@radix-ui/react-slider@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.1.0.tgz#b3fdaca27619150e9e6067ad9f979a4535f68d5e"
|
||||
@ -4696,6 +4712,20 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
|
||||
"@radix-ui/react-switch@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.2.tgz#e3d1b9fe18b6b1173aadc8b8e6efdc96a28a70f8"
|
||||
integrity sha512-BcG/LKehxt36NXG0wPnoCitIfSMtU9Xo7BmythYA1PAMLtsMvW7kALfBzmduQoHTWcKr0AVcFyh0gChBUp9TiQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
"@radix-ui/react-use-previous" "1.0.0"
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
|
||||
"@radix-ui/react-tabs@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.2.tgz#8f5ec73ca41b151a413bdd6e00553408ff34ce07"
|
||||
@ -6266,17 +6296,17 @@
|
||||
unfetch "^4.2.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0":
|
||||
version "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0.tgz#3103532ff494fb7dc3cf835f10740ecf6a26c0f9"
|
||||
integrity sha512-eVg3BxlOm2P+chijHBTByr90IZVUtgRW56qEOLX7xlww2NBuKrcavBlcmn+HH7GIUktquWkMPtvy6e0W0NgA5w==
|
||||
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0", "@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.cd77847.0":
|
||||
version "1.0.6--canary.9.cd77847.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.6--canary.9.cd77847.0.tgz#35beed1bd0813569fc8852b372c92069fe74a448"
|
||||
integrity sha512-I4oBYmnUCX5IsrZhg+ST72dubSIV4wdwY+SfqJiJ3NHvDpdb240ZjdHAmjIy/yJh5rh42Fl4jbG8Tr4SzwV53Q==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
endent "^2.0.1"
|
||||
find-cache-dir "^3.3.1"
|
||||
flat-cache "^3.0.4"
|
||||
micromatch "^4.0.2"
|
||||
react-docgen-typescript "^2.1.1"
|
||||
react-docgen-typescript "^2.2.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@storybook/react@6.5.10":
|
||||
@ -20701,7 +20731,7 @@ react-copy-to-clipboard@^5.0.4:
|
||||
copy-to-clipboard "^3.3.1"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-docgen-typescript@^2.1.1:
|
||||
react-docgen-typescript@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c"
|
||||
integrity sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==
|
||||
|
Loading…
Reference in New Issue
Block a user