Skip to content

Commit

Permalink
fix: wrap popups in ErrorBoundary
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Oct 20, 2023
1 parent 287c3b0 commit 0adcd47
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 36 deletions.
8 changes: 4 additions & 4 deletions src/components/dashboard/FeaturedApps/FeaturedApps.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactElement } from 'react'
import { useContext } from 'react'
import { useAppDispatch } from '@/store'
import { Box, Grid, Typography, Link } from '@mui/material'
import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk'
import { Card, WidgetBody, WidgetContainer } from '../styled'
Expand All @@ -9,7 +9,7 @@ import { AppRoutes } from '@/config/routes'
import { SafeAppsTag } from '@/config/constants'
import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard'
import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext'
import { openWalletConnect } from '@/store/popupSlice'

const isWalletConnectSafeApp = (app: SafeAppData): boolean => {
const WALLET_CONNECT = /wallet-connect/
Expand Down Expand Up @@ -39,12 +39,12 @@ const FeaturedAppCard = ({ app }: { app: SafeAppData }) => (
export const FeaturedApps = ({ stackedLayout }: { stackedLayout: boolean }): ReactElement | null => {
const router = useRouter()
const [featuredApps, _, remoteSafeAppsLoading] = useRemoteSafeApps(SafeAppsTag.DASHBOARD_FEATURED)
const { setOpen } = useContext(WalletConnectContext)
const dispatch = useAppDispatch()

if (!featuredApps?.length && !remoteSafeAppsLoading) return null

const onWcWidgetClick = () => {
setOpen(true)
dispatch(openWalletConnect())
}

return (
Expand Down
32 changes: 32 additions & 0 deletions src/components/walletconnect/HeaderWidget/ErrorFalllback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useRef } from 'react'
import type { ReactElement } from 'react'

import Popup from '../Popup'
import Icon from './Icon'
import { WalletConnectErrorMessage } from '../SessionManager/ErrorMessage'

export const ErrorFalllback = ({
onOpen,
onClose,
open,
error,
}: {
onOpen: () => void
onClose: () => void
open: boolean
error: Error
}): ReactElement => {
const iconRef = useRef<HTMLDivElement>(null)

return (
<>
<div ref={iconRef}>
<Icon onClick={onOpen} sessionCount={0} error />
</div>

<Popup anchorEl={iconRef.current} open={open} onClose={onClose}>
<WalletConnectErrorMessage error={error} />
</Popup>
</>
)
}
10 changes: 2 additions & 8 deletions src/components/walletconnect/HeaderWidget/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { Badge, ButtonBase, SvgIcon } from '@mui/material'
import { useContext } from 'react'

import WalletConnectIcon from '@/public/images/common/walletconnect.svg'
import { useDarkMode } from '@/hooks/useDarkMode'
import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext'

type IconProps = {
onClick: () => void
sessionCount: number
sessionInfo?: {
name: string
iconUrl: string
}
error: boolean
}

const Icon = ({ sessionCount, sessionInfo, ...props }: IconProps): React.ReactElement => {
const { error } = useContext(WalletConnectContext)
const Icon = ({ sessionCount, error = false, ...props }: IconProps): React.ReactElement => {
const isDarkMode = useDarkMode()

return (
Expand Down
41 changes: 30 additions & 11 deletions src/components/walletconnect/HeaderWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { ErrorBoundary } from '@sentry/react'
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import type { CoreTypes, SessionTypes } from '@walletconnect/types'
import type { ReactElement } from 'react'

import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext'
import { WalletConnectContext, WalletConnectProvider } from '@/services/walletconnect/WalletConnectContext'
import useWalletConnectSessions from '@/services/walletconnect/useWalletConnectSessions'
import { useWalletConnectClipboardUri } from '@/services/walletconnect/useWalletConnectClipboardUri'
import { useWalletConnectSearchParamUri } from '@/services/walletconnect/useWalletConnectSearchParamUri'
import Icon from './Icon'
import SessionManager from '../SessionManager'
import Popup from '../Popup'
import { SuccessBanner } from '../SuccessBanner'
import { useAppDispatch, useAppSelector } from '@/store'
import { closeWalletConnect, openWalletConnect, selectWalletConnectPopup } from '@/store/popupSlice'
import { ErrorFalllback } from './ErrorFalllback'

const usePrepopulatedUri = (): [string, () => void] => {
const [searchParamWcUri, setSearchParamWcUri] = useWalletConnectSearchParamUri()
Expand All @@ -25,20 +29,22 @@ const usePrepopulatedUri = (): [string, () => void] => {
return [uri, clearUri]
}

const WalletConnectHeaderWidget = (): ReactElement => {
const { walletConnect, setError, open, setOpen } = useContext(WalletConnectContext)
const HeaderWidget = (): ReactElement => {
const { walletConnect, error, setError } = useContext(WalletConnectContext)
const { open } = useAppSelector(selectWalletConnectPopup)
const dispatch = useAppDispatch()
const iconRef = useRef<HTMLDivElement>(null)
const sessions = useWalletConnectSessions()
const [uri, clearUri] = usePrepopulatedUri()
const [metadata, setMetadata] = useState<CoreTypes.Metadata>()

const onOpenSessionManager = useCallback(() => setOpen(true), [setOpen])
const onOpenSessionManager = useCallback(() => dispatch(openWalletConnect()), [dispatch])

const onCloseSessionManager = useCallback(() => {
setOpen(false)
dispatch(closeWalletConnect())
clearUri()
setError(null)
}, [setOpen, clearUri, setError])
}, [dispatch, clearUri, setError])

const onCloseSuccesBanner = useCallback(() => setMetadata(undefined), [])

Expand Down Expand Up @@ -74,13 +80,17 @@ const WalletConnectHeaderWidget = (): ReactElement => {

// Open the popup when a prepopulated uri is found
useEffect(() => {
if (uri) setOpen(true)
}, [uri, setOpen])
if (uri) dispatch(openWalletConnect())
}, [uri, dispatch])

return (
<>
<ErrorBoundary
fallback={({ error }) => (
<ErrorFalllback onOpen={onOpenSessionManager} onClose={onCloseSessionManager} open={open} error={error} />
)}
>
<div ref={iconRef}>
<Icon onClick={onOpenSessionManager} sessionCount={sessions.length} />
<Icon onClick={onOpenSessionManager} sessionCount={sessions.length} error={!!error} />
</div>

<Popup anchorEl={iconRef.current} open={open} onClose={onCloseSessionManager}>
Expand All @@ -90,7 +100,16 @@ const WalletConnectHeaderWidget = (): ReactElement => {
<Popup anchorEl={iconRef.current} open={!!metadata} onClose={onCloseSuccesBanner}>
{metadata && <SuccessBanner metadata={metadata} />}
</Popup>
</>
</ErrorBoundary>
)
}

const WalletConnectHeaderWidget = (): ReactElement => {
// Provider wraps widget so ErrorBoundary is isolated to this component
return (
<WalletConnectProvider>
<HeaderWidget />
</WalletConnectProvider>
)
}

Expand Down
5 changes: 1 addition & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import useSafeMessageNotifications from '@/hooks/messages/useSafeMessageNotifica
import useSafeMessagePendingStatuses from '@/hooks/messages/useSafeMessagePendingStatuses'
import useChangedValue from '@/hooks/useChangedValue'
import { TxModalProvider } from '@/components/tx-flow'
import { WalletConnectProvider } from '@/services/walletconnect/WalletConnectContext'
import useABTesting from '@/services/tracking/useAbTesting'
import { AbTest } from '@/services/tracking/abTesting'
import { useNotificationTracking } from '@/components/settings/PushNotifications/hooks/useNotificationTracking'
Expand Down Expand Up @@ -79,9 +78,7 @@ export const AppProviders = ({ children }: { children: ReactNode | ReactNode[] }
{(safeTheme: Theme) => (
<ThemeProvider theme={safeTheme}>
<Sentry.ErrorBoundary showDialog fallback={ErrorBoundary}>
<TxModalProvider>
<WalletConnectProvider>{children}</WalletConnectProvider>
</TxModalProvider>
<TxModalProvider>{children}</TxModalProvider>
</Sentry.ErrorBoundary>
</ThemeProvider>
)}
Expand Down
9 changes: 1 addition & 8 deletions src/services/walletconnect/WalletConnectContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ export const WalletConnectContext = createContext<{
walletConnect: WalletConnectWallet | null
error: Error | null
setError: Dispatch<SetStateAction<Error | null>>
open: boolean
setOpen: (open: boolean) => void
}>({
walletConnect: null,
error: null,
setError: () => {},
open: false,
setOpen: (_open: boolean) => {},
})

export const WalletConnectProvider = ({ children }: { children: ReactNode }) => {
Expand All @@ -30,7 +26,6 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) =>
safeAddress,
} = useSafeInfo()
const [walletConnect, setWalletConnect] = useState<WalletConnectWallet | null>(null)
const [open, setOpen] = useState(false)
const [error, setError] = useState<Error | null>(null)
const safeWalletProvider = useSafeWalletProvider()

Expand Down Expand Up @@ -86,8 +81,6 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) =>
}, [walletConnect, chainId, safeWalletProvider])

return (
<WalletConnectContext.Provider value={{ walletConnect, error, setError, open, setOpen }}>
{children}
</WalletConnectContext.Provider>
<WalletConnectContext.Provider value={{ walletConnect, error, setError }}>{children}</WalletConnectContext.Provider>
)
}
44 changes: 44 additions & 0 deletions src/store/__tests__/popupSlice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { popupSlice, PopupType } from '@/store/popupSlice'
import { CookieType } from '../cookiesSlice'

describe('popupSlice', () => {
it('should open the cookie banner', () => {
const initialState = {
[PopupType.COOKIES]: { open: false },
[PopupType.WALLET_CONNECT]: { open: false },
}
const { reducer, actions } = popupSlice
const newState = reducer(initialState, actions.openCookieBanner({ warningKey: CookieType.ANALYTICS }))
expect(newState[PopupType.COOKIES]).toEqual({ open: true, warningKey: CookieType.ANALYTICS })
})

it('should close the cookie banner', () => {
const initialState = {
[PopupType.COOKIES]: { open: true },
[PopupType.WALLET_CONNECT]: { open: false },
}
const { reducer, actions } = popupSlice
const newState = reducer(initialState, actions.closeCookieBanner())
expect(newState[PopupType.COOKIES]).toEqual({ open: false })
})

it('should open the wallet connect popup', () => {
const initialState = {
[PopupType.COOKIES]: { open: false },
[PopupType.WALLET_CONNECT]: { open: false },
}
const { reducer, actions } = popupSlice
const newState = reducer(initialState, actions.openWalletConnect())
expect(newState[PopupType.WALLET_CONNECT]).toEqual({ open: true })
})

it('should close the wallet connect popup', () => {
const initialState = {
[PopupType.COOKIES]: { open: false },
[PopupType.WALLET_CONNECT]: { open: true },
}
const { reducer, actions } = popupSlice
const newState = reducer(initialState, actions.closeWalletConnect())
expect(newState[PopupType.WALLET_CONNECT]).toEqual({ open: false })
})
})
16 changes: 15 additions & 1 deletion src/store/popupSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ import type { RootState } from '.'

export enum PopupType {
COOKIES = 'cookies',
WALLET_CONNECT = 'walletConnect',
}

type PopupState = {
[PopupType.COOKIES]: {
open: boolean
warningKey?: CookieType
}
[PopupType.WALLET_CONNECT]: {
open: boolean
}
}

const initialState: PopupState = {
[PopupType.COOKIES]: {
open: false,
},
[PopupType.WALLET_CONNECT]: {
open: false,
},
}

export const popupSlice = createSlice({
Expand All @@ -33,9 +40,16 @@ export const popupSlice = createSlice({
closeCookieBanner: (state) => {
state[PopupType.COOKIES] = { open: false }
},
openWalletConnect: (state) => {
state[PopupType.WALLET_CONNECT] = { open: true }
},
closeWalletConnect: (state) => {
state[PopupType.WALLET_CONNECT] = { open: false }
},
},
})

export const { openCookieBanner, closeCookieBanner } = popupSlice.actions
export const { openCookieBanner, closeCookieBanner, openWalletConnect, closeWalletConnect } = popupSlice.actions

export const selectCookieBanner = (state: RootState) => state[popupSlice.name][PopupType.COOKIES]
export const selectWalletConnectPopup = (state: RootState) => state[popupSlice.name][PopupType.WALLET_CONNECT]

0 comments on commit 0adcd47

Please sign in to comment.