Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new error dialog design #511

Merged
merged 7 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ColumnProps {
justify?: string
gap?: number
padded?: true
padding?: string
flex?: true
grow?: true
css?: ReturnType<typeof css>
Expand All @@ -22,7 +23,7 @@ const Column = styled.div<ColumnProps>`
grid-auto-flow: row;
grid-template-columns: 1fr;
justify-content: ${({ justify }) => justify ?? 'space-between'};
padding: ${({ padded }) => padded && '0.75em'};
padding: ${({ padded, padding }) => padding ?? (padded && '0.75em')};

${({ css }) => css}
`
Expand Down
40 changes: 20 additions & 20 deletions src/components/Error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DEFAULT_ERROR_ACTION, DEFAULT_ERROR_HEADER, WidgetError } from 'errors'
import { t } from '@lingui/macro'
import { DEFAULT_ERROR_HEADER, WidgetError } from 'errors'
import { Component, ErrorInfo, PropsWithChildren, useCallback, useState } from 'react'

import Dialog from '../Dialog'
import ErrorDialog from './ErrorDialog'
import ErrorView from './ErrorView'

export type OnError = (error: Error, info?: ErrorInfo) => void

Expand Down Expand Up @@ -53,30 +53,30 @@ export default class ErrorBoundary extends Component<PropsWithChildren<ErrorBoun
this.props.onError?.(error, errorInfo)
}

renderErrorDialog(error: Error) {
renderErrorView(error: Error) {
const header = error instanceof WidgetError ? error.header : DEFAULT_ERROR_HEADER
const action = error instanceof WidgetError ? error.action : DEFAULT_ERROR_ACTION
return (
<Dialog color="dialog" forceContain>
<ErrorDialog
message={header}
error={error}
action={action}
onClick={
error instanceof WidgetError && error.dismissable
? () => {
this.setState({ error: undefined })
}
: () => window.location.reload()
}
/>
</Dialog>
<ErrorView
message={header}
error={error}
action={t`Get support`}
onDismiss={
error instanceof WidgetError && error.dismissable
? () => {
this.setState({ error: undefined })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason behind why we're using a mix of class and functional react components in widget repo?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes - the ErrorBoundary needs to be a class component. this should be the only one afaik.

Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application.

https://reactjs.org/docs/error-boundaries.html#introducing-error-boundaries

}
: () => window.location.reload()
}
onClick={() => {
window.open('https://support.uniswap.org/', '_blank', 'noopener,noreferrer')
}}
/>
)
}

render() {
if (this.state.error) {
return this.renderErrorDialog(this.state.error)
return this.renderErrorView(this.state.error)
}
return this.props.children
}
Expand Down
105 changes: 0 additions & 105 deletions src/components/Error/ErrorDialog.tsx

This file was deleted.

91 changes: 91 additions & 0 deletions src/components/Error/ErrorView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Trans } from '@lingui/macro'
import ActionButton from 'components/ActionButton'
import Column from 'components/Column'
import Expando from 'components/Expando'
import Row from 'components/Row'
import { iconHoverCss } from 'css/hover'
import { AlertTriangle, Icon, LargeIcon, X } from 'icons'
import { ReactNode, useState } from 'react'
import styled from 'styled-components/macro'
import { Color, ThemedText } from 'theme'

const HeaderIcon = styled(LargeIcon)`
flex-grow: 1;
margin: 2em 0;
`

interface StatusHeaderProps {
icon: Icon
iconColor?: Color
iconSize?: number
children: ReactNode
}

export function StatusHeader({ icon: Icon, iconColor, iconSize = 2.5, children }: StatusHeaderProps) {
return (
<>
<Column flex style={{ flexGrow: 1 }}>
<HeaderIcon icon={Icon} color={iconColor} size={iconSize} />
<Column gap={0.75} flex style={{ textAlign: 'center' }}>
{children}
</Column>
</Column>
</>
)
}

const ExpandoContent = styled(ThemedText.Code)`
margin: 0.5em;
`

const StyledXButton = styled(X)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like i've seen this component declared a couple times across the widget repo now, could we extract it into a single file and re-use via exporting?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH we actually have one already ! wow good catch

${iconHoverCss}
`

const ErrorDialogWrapper = styled(Column)`
background-color: ${({ theme }) => theme.container};
`

interface ErrorDialogProps {
header?: ReactNode
message: ReactNode
error?: Error
action: ReactNode
onClick: () => void
onDismiss: () => void
}

export default function ErrorDialog({ header, message, error, action, onClick, onDismiss }: ErrorDialogProps) {
const [open, setOpen] = useState(false)

return (
<ErrorDialogWrapper flex padding="1em 0.5em 0.25em" gap={0.5} align="stretch">
<Row flex flow="row-reverse">
<LargeIcon icon={StyledXButton} onClick={onDismiss} />
</Row>
<StatusHeader icon={AlertTriangle} iconColor="warning" iconSize={2.5}>
<Column gap={0.75}>
<ThemedText.H4>{header || <Trans>Something went wrong</Trans>}</ThemedText.H4>
<ThemedText.Body1 color="secondary">{message}</ThemedText.Body1>
</Column>
</StatusHeader>
{error ? (
<Expando
title={open ? <Trans>Show less</Trans> : <Trans>Show more</Trans>}
open={open}
onExpand={() => setOpen((open) => !open)}
maxHeight={11.5 /* em */}
>
<Column flex grow padded>
<ExpandoContent userSelect>{error.toString()}</ExpandoContent>
</Column>
</Expando>
) : (
<Column style={{ height: '7.5em' }} />
)}
<ActionButton color="accentSoft" onClick={onClick} narrow>
{action}
</ActionButton>
</ErrorDialogWrapper>
)
}
6 changes: 4 additions & 2 deletions src/components/Swap/Status/StatusDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Trans } from '@lingui/macro'
import ErrorDialog, { StatusHeader } from 'components/Error/ErrorDialog'
import ErrorView from 'components/Error/ErrorView'
import { StatusHeader } from 'components/Error/ErrorView'
import EtherscanLink from 'components/EtherscanLink'
import Row from 'components/Row'
import SwapSummary from 'components/Swap/Summary'
Expand Down Expand Up @@ -56,7 +57,7 @@ function TransactionStatus({ tx, onClose }: TransactionStatusProps) {

export default function TransactionStatusDialog({ tx, onClose }: TransactionStatusProps) {
return tx.receipt?.status === 0 ? (
<ErrorDialog
<ErrorView
header={<Trans>Your swap failed.</Trans>}
message={
<Trans>
Expand All @@ -67,6 +68,7 @@ export default function TransactionStatusDialog({ tx, onClose }: TransactionStat
}
action={<Trans>Dismiss</Trans>}
onClick={onClose}
onDismiss={onClose}
/>
) : (
<TransactionStatus tx={tx} onClose={onClose} />
Expand Down