Commit 8e3b2cb4 authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feat(widgets): add error reporting component, INTEGRATION ERROR type, and...

feat(widgets): add error reporting component, INTEGRATION ERROR type, and Missing provider error (#3110)

* add error reporting component, INTEGRATION ERROR type, and Missing provider error

* rename reporter to generator

* pr feedback

* refactor provider check

* add chainId, convenienceFee, and width errors

* pr feedback and convenienceFeeRecipient address enforcement

* fix imports for utils
parent d54783a3
...@@ -34,7 +34,7 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E ...@@ -34,7 +34,7 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
<Dialog color="dialog"> <Dialog color="dialog">
<ErrorDialog <ErrorDialog
error={this.state.error} error={this.state.error}
header={<Trans>Reload the page to try again</Trans>} header={<Trans>Something went wrong.</Trans>}
action={<Trans>Reload the page</Trans>} action={<Trans>Reload the page</Trans>}
onAction={() => window.location.reload()} onAction={() => window.location.reload()}
/> />
......
...@@ -117,7 +117,10 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi ...@@ -117,7 +117,10 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi
<Rule /> <Rule />
<ErrorColumn> <ErrorColumn>
<Column gap={0.5} ref={setDetails} css={scrollbar}> <Column gap={0.5} ref={setDetails} css={scrollbar}>
<ThemedText.Code>{error.message}</ThemedText.Code> <ThemedText.Code>
{error.name}
{error.message ? `: ${error.message}` : ''}
</ThemedText.Code>
</Column> </Column>
</ErrorColumn> </ErrorColumn>
<ActionButton onClick={onAction}>{action}</ActionButton> <ActionButton onClick={onAction}>{action}</ActionButton>
......
import { ALL_SUPPORTED_CHAIN_IDS } from 'constants/chains'
import { ChainIdError, IntegrationError } from 'lib/errors'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { SwapWidgetProps } from 'lib/index'
import { useEffect } from 'react'
import { isAddress } from '../../../utils'
export default function ErrorGenerator(swapWidgetProps: SwapWidgetProps) {
const { jsonRpcEndpoint, provider } = swapWidgetProps
useEffect(() => {
if (!provider && !jsonRpcEndpoint) {
throw new IntegrationError('This widget requires a provider or jsonRpcEndpoint.')
}
}, [provider, jsonRpcEndpoint])
const { chainId } = useActiveWeb3React()
useEffect(() => {
if (chainId && !ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
throw new ChainIdError('Switch to a network supported by the Uniswap Protocol.')
}
}, [chainId])
// size constraints
const { width } = swapWidgetProps
useEffect(() => {
if (width && width < 300) {
throw new IntegrationError('Set widget width to at least 300px.')
}
}, [width])
// convenience fee constraints
const { convenienceFee, convenienceFeeRecipient } = swapWidgetProps
useEffect(() => {
if (convenienceFee) {
if (convenienceFee > 100 || convenienceFee < 0) {
throw new IntegrationError('Set widget convenienceFee to at least 400px.')
}
if (!convenienceFeeRecipient) {
throw new IntegrationError('convenienceFeeRecipient is required when convenienceFee is set.')
}
const MustBeValidAddressError = new IntegrationError('convenienceFeeRecipient must be a valid address.')
if (typeof convenienceFeeRecipient === 'string') {
if (!isAddress(convenienceFeeRecipient)) {
throw MustBeValidAddressError
}
} else if (typeof convenienceFeeRecipient === 'object') {
Object.values(convenienceFeeRecipient).forEach((recipient) => {
if (!isAddress(recipient)) {
throw MustBeValidAddressError
}
})
}
}
}, [convenienceFee, convenienceFeeRecipient])
return null
}
...@@ -39,6 +39,8 @@ function useSwapDefaults(defaults: Partial<SwapDefaults> = {}): SwapDefaults { ...@@ -39,6 +39,8 @@ function useSwapDefaults(defaults: Partial<SwapDefaults> = {}): SwapDefaults {
} }
export interface SwapProps { export interface SwapProps {
convenienceFee?: number
convenienceFeeRecipient?: string // TODO: improve typing to require recipient when fee is set
defaults?: Partial<SwapDefaults> defaults?: Partial<SwapDefaults>
} }
......
...@@ -11,6 +11,7 @@ import { Provider as EthProvider } from 'widgets-web3-react/types' ...@@ -11,6 +11,7 @@ import { Provider as EthProvider } from 'widgets-web3-react/types'
import { Provider as DialogProvider } from './Dialog' import { Provider as DialogProvider } from './Dialog'
import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary' import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
import ErrorGenerator from './Error/ErrorGenerator'
import Web3Provider from './Web3Provider' import Web3Provider from './Web3Provider'
const slideDown = keyframes` const slideDown = keyframes`
...@@ -90,19 +91,19 @@ export type WidgetProps<T extends JSXElementConstructor<any> | undefined = undef ...@@ -90,19 +91,19 @@ export type WidgetProps<T extends JSXElementConstructor<any> | undefined = undef
: // eslint-disable-next-line @typescript-eslint/ban-types : // eslint-disable-next-line @typescript-eslint/ban-types
{}) {})
export default function Widget({ export default function Widget(props: PropsWithChildren<WidgetProps>) {
children, const {
theme, children,
locale = DEFAULT_LOCALE, theme,
provider, locale = DEFAULT_LOCALE,
jsonRpcEndpoint, provider,
width = 360, jsonRpcEndpoint,
dialog, width = 360,
className, dialog,
onError, className,
}: PropsWithChildren<WidgetProps>) { onError,
} = props
const wrapper = useRef<HTMLDivElement>(null) const wrapper = useRef<HTMLDivElement>(null)
return ( return (
<StrictMode> <StrictMode>
<I18nProvider locale={locale}> <I18nProvider locale={locale}>
...@@ -114,6 +115,7 @@ export default function Widget({ ...@@ -114,6 +115,7 @@ export default function Widget({
<AtomProvider> <AtomProvider>
<Web3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}> <Web3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}>
<Updaters /> <Updaters />
<ErrorGenerator {...props} />
{children} {children}
</Web3Provider> </Web3Provider>
</AtomProvider> </AtomProvider>
......
export class IntegrationError extends Error {
constructor(message: string) {
super(message)
this.name = 'Integration Error'
}
}
export class ChainIdError extends Error {
constructor(message: string) {
super(message)
this.name = 'Unsupported network'
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment