From b7a440132d834519c2ea63eb9e2b599cb0571154 Mon Sep 17 00:00:00 2001 From: Maciek Date: Wed, 26 Apr 2023 17:17:23 +0200 Subject: [PATCH] feat(trading): make Sentry only after opt in (#3448) --- apps/trading-e2e/src/integration/navbar.cy.ts | 4 +- .../src/integration/settings.cy.ts | 45 +++++++++++++++++ apps/trading/.env.testnet | 2 +- apps/trading/client-pages/settings/index.ts | 2 + .../client-pages/settings/settings-button.tsx | 16 +++++++ .../client-pages/settings/settings.tsx | 45 +++++++++++++++++ apps/trading/components/navbar/navbar.tsx | 10 ++-- .../risk-notice-dialog.spec.tsx | 20 +++----- .../welcome-dialog/risk-notice-dialog.tsx | 4 ++ .../telemetry-approval.spec.tsx | 19 ++++++++ .../welcome-dialog/telemetry-approval.tsx | 32 +++++++++++++ .../welcome-dialog/welcome-dialog.tsx | 28 +++++------ .../lib/hooks/use-telemetry-approval.spec.ts | 48 +++++++++++++++++++ .../lib/hooks/use-telemetry-approval.ts | 24 ++++++++++ apps/trading/pages/_app.page.tsx | 6 ++- apps/trading/pages/client-router.tsx | 12 ++++- apps/trading/sentry.client.config.js | 17 +++---- libs/react-helpers/src/hooks/use-logger.ts | 16 ++----- .../components/divider/divider.stories.tsx | 41 ++++++++++++++++ .../src/components/divider/divider.tsx | 19 ++++++++ .../src/components/divider/index.ts | 1 + libs/ui-toolkit/src/components/index.ts | 2 + .../ui-toolkit/src/components/switch/index.ts | 1 + .../src/components/switch/switch.stories.tsx | 31 ++++++++++++ .../src/components/switch/switch.tsx | 38 +++++++++++++++ .../theme-switcher/theme-switcher.tsx | 1 + libs/utils/src/index.ts | 1 + libs/utils/src/lib/sentry-utils.spec.ts | 26 ++++++++++ libs/utils/src/lib/sentry-utils.ts | 15 ++++++ libs/web3/src/lib/use-eager-connect.ts | 5 +- package.json | 6 ++- yarn.lock | 42 +++++++++++++--- 32 files changed, 511 insertions(+), 68 deletions(-) create mode 100644 apps/trading-e2e/src/integration/settings.cy.ts create mode 100644 apps/trading/client-pages/settings/index.ts create mode 100644 apps/trading/client-pages/settings/settings-button.tsx create mode 100644 apps/trading/client-pages/settings/settings.tsx create mode 100644 apps/trading/components/welcome-dialog/telemetry-approval.spec.tsx create mode 100644 apps/trading/components/welcome-dialog/telemetry-approval.tsx create mode 100644 apps/trading/lib/hooks/use-telemetry-approval.spec.ts create mode 100644 apps/trading/lib/hooks/use-telemetry-approval.ts create mode 100644 libs/ui-toolkit/src/components/divider/divider.stories.tsx create mode 100644 libs/ui-toolkit/src/components/divider/divider.tsx create mode 100644 libs/ui-toolkit/src/components/divider/index.ts create mode 100644 libs/ui-toolkit/src/components/switch/index.ts create mode 100644 libs/ui-toolkit/src/components/switch/switch.stories.tsx create mode 100644 libs/ui-toolkit/src/components/switch/switch.tsx create mode 100644 libs/utils/src/lib/sentry-utils.spec.ts create mode 100644 libs/utils/src/lib/sentry-utils.ts diff --git a/apps/trading-e2e/src/integration/navbar.cy.ts b/apps/trading-e2e/src/integration/navbar.cy.ts index 6b04eecac..205c77f3a 100644 --- a/apps/trading-e2e/src/integration/navbar.cy.ts +++ b/apps/trading-e2e/src/integration/navbar.cy.ts @@ -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'); diff --git a/apps/trading-e2e/src/integration/settings.cy.ts b/apps/trading-e2e/src/integration/settings.cy.ts new file mode 100644 index 000000000..8074fee0c --- /dev/null +++ b/apps/trading-e2e/src/integration/settings.cy.ts @@ -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' + ); + }); +}); diff --git a/apps/trading/.env.testnet b/apps/trading/.env.testnet index 64e584a9c..c4f8bc481 100644 --- a/apps/trading/.env.testnet +++ b/apps/trading/.env.testnet @@ -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 \ No newline at end of file +NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports diff --git a/apps/trading/client-pages/settings/index.ts b/apps/trading/client-pages/settings/index.ts new file mode 100644 index 000000000..9b97e60b3 --- /dev/null +++ b/apps/trading/client-pages/settings/index.ts @@ -0,0 +1,2 @@ +export { Settings as default } from './settings'; +export { SettingsButton } from './settings-button'; diff --git a/apps/trading/client-pages/settings/settings-button.tsx b/apps/trading/client-pages/settings/settings-button.tsx new file mode 100644 index 000000000..4f17c2601 --- /dev/null +++ b/apps/trading/client-pages/settings/settings-button.tsx @@ -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 ( + + {withMobile ? ( + t('Settings') + ) : ( + + )} + + ); +}; diff --git a/apps/trading/client-pages/settings/settings.tsx b/apps/trading/client-pages/settings/settings.tsx new file mode 100644 index 000000000..cb14ffde2 --- /dev/null +++ b/apps/trading/client-pages/settings/settings.tsx @@ -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 ( +
+
+

+ {t('Settings')} +

+
+ {t('Changes are applied automatically.')} +
+
+ +
+
+ + +
+ setTheme()} + checked={theme === 'dark'} + /> +
+ + +
+
+
+
+ ); +}; diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index f4a36e2af..ccbd32b91 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -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={ <> - + } @@ -108,15 +108,15 @@ export const Navbar = ({ )} - - + + diff --git a/apps/trading/components/welcome-dialog/risk-notice-dialog.spec.tsx b/apps/trading/components/welcome-dialog/risk-notice-dialog.spec.tsx index bf29c411a..9b476130b 100644 --- a/apps/trading/components/welcome-dialog/risk-notice-dialog.spec.tsx +++ b/apps/trading/components/welcome-dialog/risk-notice-dialog.spec.tsx @@ -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(); } ); diff --git a/apps/trading/components/welcome-dialog/risk-notice-dialog.tsx b/apps/trading/components/welcome-dialog/risk-notice-dialog.tsx index 650d59da7..6c5531b2b 100644 --- a/apps/trading/components/welcome-dialog/risk-notice-dialog.tsx +++ b/apps/trading/components/welcome-dialog/risk-notice-dialog.tsx @@ -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) => { )}

+
+ +
); }; diff --git a/apps/trading/components/welcome-dialog/telemetry-approval.spec.tsx b/apps/trading/components/welcome-dialog/telemetry-approval.spec.tsx new file mode 100644 index 000000000..dc5ff93a9 --- /dev/null +++ b/apps/trading/components/welcome-dialog/telemetry-approval.spec.tsx @@ -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(); + expect(screen.getByRole('checkbox')).toHaveAttribute( + 'data-state', + 'unchecked' + ); + act(() => { + screen.getByRole('checkbox').click(); + }); + expect(screen.getByRole('checkbox')).toHaveAttribute( + 'data-state', + 'checked' + ); + }); +}); diff --git a/apps/trading/components/welcome-dialog/telemetry-approval.tsx b/apps/trading/components/welcome-dialog/telemetry-approval.tsx new file mode 100644 index 000000000..bdc648d4b --- /dev/null +++ b/apps/trading/components/welcome-dialog/telemetry-approval.tsx @@ -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 ( +
+
+ {t('Share usage data')}} + checked={isApproved} + name="telemetry-approval" + onCheckedChange={() => setIsApproved(!isApproved)} + /> +
+
+ + {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.')} + +
+
+ ); +}; diff --git a/apps/trading/components/welcome-dialog/welcome-dialog.tsx b/apps/trading/components/welcome-dialog/welcome-dialog.tsx index c3faf3dc6..722f02c1a 100644 --- a/apps/trading/components/welcome-dialog/welcome-dialog.tsx +++ b/apps/trading/components/welcome-dialog/welcome-dialog.tsx @@ -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 = ; + dialogContent = ; title = t('WARNING'); size = 'medium'; } else if (isWelcomeDialogNeeded && data?.length === 0) { dialogContent = ; + onClose = onCloseDialog; } else if (isWelcomeDialogNeeded && (data?.length || 0) > 0) { - dialogContent = ; + dialogContent = ; + onClose = onCloseDialog; + } else { + dialogContent = null as React.ReactNode; } return ( diff --git a/apps/trading/lib/hooks/use-telemetry-approval.spec.ts b/apps/trading/lib/hooks/use-telemetry-approval.spec.ts new file mode 100644 index 000000000..6a4c9f00d --- /dev/null +++ b/apps/trading/lib/hooks/use-telemetry-approval.spec.ts @@ -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(); + }); + }); +}); diff --git a/apps/trading/lib/hooks/use-telemetry-approval.ts b/apps/trading/lib/hooks/use-telemetry-approval.ts new file mode 100644 index 000000000..21e6a2469 --- /dev/null +++ b/apps/trading/lib/hooks/use-telemetry-approval.ts @@ -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]; +}; diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index cfd815d31..3996631d3 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -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(); diff --git a/apps/trading/pages/client-router.tsx b/apps/trading/pages/client-router.tsx index e5a4659b6..4180df127 100644 --- a/apps/trading/pages/client-router.tsx +++ b/apps/trading/pages/client-router.tsx @@ -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: , }, + { + path: Routes.SETTINGS, + element: , + }, { path: '*', element: ( diff --git a/apps/trading/sentry.client.config.js b/apps/trading/sentry.client.config.js index 6724c2dd4..7b5a79bb5 100644 --- a/apps/trading/sentry.client.config.js +++ b/apps/trading/sentry.client.config.js @@ -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); } diff --git a/libs/react-helpers/src/hooks/use-logger.ts b/libs/react-helpers/src/hooks/use-logger.ts index cf62cf218..759d3f3b8 100644 --- a/libs/react-helpers/src/hooks/use-logger.ts +++ b/libs/react-helpers/src/hooks/use-logger.ts @@ -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(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; diff --git a/libs/ui-toolkit/src/components/divider/divider.stories.tsx b/libs/ui-toolkit/src/components/divider/divider.stories.tsx new file mode 100644 index 000000000..069272530 --- /dev/null +++ b/libs/ui-toolkit/src/components/divider/divider.stories.tsx @@ -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; + +const Template: ComponentStory = (args) => { + return ( +
+
+ +
+
+ ); +}; + +export const Default = Template.bind({}); +Default.args = {}; + +export const Vertical = Template.bind({}); +Vertical.args = { + orientation: 'vertical', +}; diff --git a/libs/ui-toolkit/src/components/divider/divider.tsx b/libs/ui-toolkit/src/components/divider/divider.tsx new file mode 100644 index 000000000..136cc3d46 --- /dev/null +++ b/libs/ui-toolkit/src/components/divider/divider.tsx @@ -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( + ({ orientation = 'horizontal', decorative }, ref) => { + return ( + + ); + } +); diff --git a/libs/ui-toolkit/src/components/divider/index.ts b/libs/ui-toolkit/src/components/divider/index.ts new file mode 100644 index 000000000..0dd50b568 --- /dev/null +++ b/libs/ui-toolkit/src/components/divider/index.ts @@ -0,0 +1 @@ +export { Divider } from './divider'; diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts index c2ef46544..2e2fb84fc 100644 --- a/libs/ui-toolkit/src/components/index.ts +++ b/libs/ui-toolkit/src/components/index.ts @@ -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'; diff --git a/libs/ui-toolkit/src/components/switch/index.ts b/libs/ui-toolkit/src/components/switch/index.ts new file mode 100644 index 000000000..675ddba51 --- /dev/null +++ b/libs/ui-toolkit/src/components/switch/index.ts @@ -0,0 +1 @@ +export { Switch } from './switch'; diff --git a/libs/ui-toolkit/src/components/switch/switch.stories.tsx b/libs/ui-toolkit/src/components/switch/switch.stories.tsx new file mode 100644 index 000000000..f3c0f8289 --- /dev/null +++ b/libs/ui-toolkit/src/components/switch/switch.stories.tsx @@ -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; + +const Template: Story = (args) => { + const [checked, setChecked] = useState(args.checked || false); + return ( + setChecked(checked)} + /> + ); +}; + +export const Default = Template.bind({}); +Default.args = { + name: 'switch', +}; + +export const WithTextLabel = Template.bind({}); +WithTextLabel.args = { + name: 'switch', + labelText: 'Light mode', +}; diff --git a/libs/ui-toolkit/src/components/switch/switch.tsx b/libs/ui-toolkit/src/components/switch/switch.tsx new file mode 100644 index 000000000..f20a0daf6 --- /dev/null +++ b/libs/ui-toolkit/src/components/switch/switch.tsx @@ -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( + ( + { name = 'switch', labelText, onCheckedChange, checked = false, disabled }, + ref + ) => { + return ( +
+ + + + {labelText && ( + + )} +
+ ); + } +); diff --git a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx index 6ca96ecc6..1b065f75e 100644 --- a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx +++ b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx @@ -17,6 +17,7 @@ export const ThemeSwitcher = ({ onClick={() => setTheme()} className={className} data-testid="theme-switcher" + id="theme-switcher" > {theme === 'dark' && } {theme === 'light' && } diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index f099ed873..15f600113 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -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'; diff --git a/libs/utils/src/lib/sentry-utils.spec.ts b/libs/utils/src/lib/sentry-utils.spec.ts new file mode 100644 index 000000000..731405620 --- /dev/null +++ b/libs/utils/src/lib/sentry-utils.spec.ts @@ -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(); + }); + }); +}); diff --git a/libs/utils/src/lib/sentry-utils.ts b/libs/utils/src/lib/sentry-utils.ts new file mode 100644 index 000000000..a308bebb0 --- /dev/null +++ b/libs/utils/src/lib/sentry-utils.ts @@ -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(); diff --git a/libs/web3/src/lib/use-eager-connect.ts b/libs/web3/src/lib/use-eager-connect.ts index dd1c741ca..647d19e2e 100644 --- a/libs/web3/src/lib/use-eager-connect.ts +++ b/libs/web3/src/lib/use-eager-connect.ts @@ -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; diff --git a/package.json b/package.json index 4dcb3db01..35ba287e7 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/yarn.lock b/yarn.lock index 3f70c1cf0..0bfb9d0d5 100644 --- a/yarn.lock +++ b/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==