From 8404c6076c351205f69c1eee10bdf83e87557da5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 10 Feb 2022 19:33:51 -0800 Subject: [PATCH] feat: confirm price impact (#3288) * refactor: action button naming * feat: high price impact acknowledgement --- src/lib/components/ActionButton.tsx | 40 ++++++++---------- src/lib/components/Error/ErrorBoundary.tsx | 2 +- src/lib/components/Error/ErrorDialog.tsx | 6 +-- .../components/Swap/Status/StatusDialog.tsx | 2 +- src/lib/components/Swap/Summary/index.tsx | 41 ++++++++++++------- src/lib/components/Swap/SwapButton.tsx | 6 +-- src/lib/icons/index.tsx | 2 + 7 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/lib/components/ActionButton.tsx b/src/lib/components/ActionButton.tsx index 9805049dd4..ca282b6f7c 100644 --- a/src/lib/components/ActionButton.tsx +++ b/src/lib/components/ActionButton.tsx @@ -15,7 +15,7 @@ const StyledButton = styled(Button)` } ` -const UpdateRow = styled(Row)`` +const ActionRow = styled(Row)`` const grow = keyframes` from { @@ -28,12 +28,12 @@ const grow = keyframes` } ` -const updateCss = css` +const actionCss = css` border: 1px solid ${({ theme }) => theme.outline}; padding: calc(0.25em - 1px); padding-left: calc(0.75em - 1px); - ${UpdateRow} { + ${ActionRow} { animation: ${grow} 0.25s ease-in; white-space: nowrap; } @@ -45,45 +45,37 @@ const updateCss = css` } ` -export const Overlay = styled(Row)<{ update?: boolean }>` +export const Overlay = styled(Row)<{ action?: boolean }>` border-radius: ${({ theme }) => theme.borderRadius}em; flex-direction: row-reverse; min-height: 3.5em; transition: padding 0.25s ease-out; - ${({ update }) => update && updateCss} + ${({ action }) => action && actionCss} ` export interface ActionButtonProps { color?: Color disabled?: boolean - update?: { message: ReactNode; action: ReactNode; icon?: Icon } + action?: { message: ReactNode; icon?: Icon; onClick: () => void; children: ReactNode } onClick: () => void - onUpdate?: () => void children: ReactNode } -export default function ActionButton({ - color = 'accent', - disabled, - update, - onClick, - onUpdate, - children, -}: ActionButtonProps) { +export default function ActionButton({ color = 'accent', disabled, action, onClick, children }: ActionButtonProps) { const textColor = useMemo(() => (color === 'accent' && !disabled ? 'onAccent' : 'currentColor'), [color, disabled]) return ( - - - - {update ? update.action : children} + + + + {action ? action.children : children} - {update && ( - - - {update?.message} - + {action && ( + + + {action?.message} + )} ) diff --git a/src/lib/components/Error/ErrorBoundary.tsx b/src/lib/components/Error/ErrorBoundary.tsx index 9071616e60..01fee12f0b 100644 --- a/src/lib/components/Error/ErrorBoundary.tsx +++ b/src/lib/components/Error/ErrorBoundary.tsx @@ -36,7 +36,7 @@ export default class ErrorBoundary extends React.ComponentSomething went wrong.} action={Reload the page} - onAction={() => window.location.reload()} + onClick={() => window.location.reload()} /> ) diff --git a/src/lib/components/Error/ErrorDialog.tsx b/src/lib/components/Error/ErrorDialog.tsx index 920582b950..86634dc5db 100644 --- a/src/lib/components/Error/ErrorDialog.tsx +++ b/src/lib/components/Error/ErrorDialog.tsx @@ -87,10 +87,10 @@ interface ErrorDialogProps { header?: ReactNode error: Error action: ReactNode - onAction: () => void + onClick: () => void } -export default function ErrorDialog({ header, error, action, onAction }: ErrorDialogProps) { +export default function ErrorDialog({ header, error, action, onClick }: ErrorDialogProps) { const [open, setOpen] = useState(false) const [details, setDetails] = useState(null) const scrollbar = useScrollbar(details) @@ -123,7 +123,7 @@ export default function ErrorDialog({ header, error, action, onAction }: ErrorDi - {action} + {action} ) diff --git a/src/lib/components/Swap/Status/StatusDialog.tsx b/src/lib/components/Swap/Status/StatusDialog.tsx index 3478a0519e..4ddb6c86cb 100644 --- a/src/lib/components/Swap/Status/StatusDialog.tsx +++ b/src/lib/components/Swap/Status/StatusDialog.tsx @@ -93,7 +93,7 @@ export default function TransactionStatusDialog({ tx, onClose }: TransactionStat header={errorMessage} error={new Error('TODO(zzmp)')} action={Dismiss} - onAction={onClose} + onClick={onClose} /> ) : ( diff --git a/src/lib/components/Swap/Summary/index.tsx b/src/lib/components/Swap/Summary/index.tsx index d47fda3e82..8cac769ad7 100644 --- a/src/lib/components/Swap/Summary/index.tsx +++ b/src/lib/components/Swap/Summary/index.tsx @@ -6,7 +6,7 @@ import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_MEDIUM } from 'constant import { useAtomValue } from 'jotai/utils' import { IconButton } from 'lib/components/Button' import useScrollbar from 'lib/hooks/useScrollbar' -import { AlertTriangle, Expando, Info } from 'lib/icons' +import { AlertTriangle, BarChart, Expando, Info } from 'lib/icons' import { MIN_HIGH_SLIPPAGE } from 'lib/state/settings' import { Field, independentFieldAtom } from 'lib/state/swap' import styled, { ThemedText } from 'lib/theme' @@ -79,8 +79,6 @@ const Body = styled(Column)<{ open: boolean }>` } ` -const priceUpdate = { message: Price updated, action: Accept } - interface SummaryDialogProps { trade: Trade allowedSlippage: Percent @@ -92,8 +90,12 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial const inputCurrency = inputAmount.currency const outputCurrency = outputAmount.currency const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade]) - const independentField = useAtomValue(independentFieldAtom) + const { i18n } = useLingui() + + const [open, setOpen] = useState(false) + const [details, setDetails] = useState(null) + const scrollbar = useScrollbar(details) const warning = useMemo(() => { if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_HIGH)) return 'error' @@ -102,18 +104,31 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial return }, [allowedSlippage, priceImpact]) + const [ackPriceImpact, setAckPriceImpact] = useState(false) + const [confirmedTrade, setConfirmedTrade] = useState(trade) const doesTradeDiffer = useMemo( () => Boolean(trade && confirmedTrade && tradeMeaningfullyDiffers(trade, confirmedTrade)), [confirmedTrade, trade] ) - const [open, setOpen] = useState(false) - const [details, setDetails] = useState(null) - - const scrollbar = useScrollbar(details) - - const { i18n } = useLingui() + const action = useMemo(() => { + if (doesTradeDiffer) { + return { + message: Price updated, + icon: BarChart, + onClick: () => setConfirmedTrade(trade), + children: Accept, + } + } else if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_HIGH) && !ackPriceImpact) { + return { + message: High price impact, + onClick: () => setAckPriceImpact(true), + children: Acknowledge, + } + } + return + }, [ackPriceImpact, doesTradeDiffer, priceImpact, trade]) if (!(inputAmount && outputAmount && inputCurrency && outputCurrency)) { return null @@ -163,11 +178,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial )} - setConfirmedTrade(trade)} - update={doesTradeDiffer ? priceUpdate : undefined} - > + Confirm swap diff --git a/src/lib/components/Swap/SwapButton.tsx b/src/lib/components/Swap/SwapButton.tsx index a10446ac6d..f04b43915d 100644 --- a/src/lib/components/Swap/SwapButton.tsx +++ b/src/lib/components/Swap/SwapButton.tsx @@ -95,8 +95,9 @@ export default function SwapButton({ disabled }: SwapButtonProps) { ), - action: Approve, icon: Spinner, + onClick: addApprovalTransaction, + children: Approve, }, } } else if (approval === ApprovalState.NOT_APPROVED) { @@ -111,7 +112,7 @@ export default function SwapButton({ disabled }: SwapButtonProps) { } return { disabled: true } - }, [approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance]) + }, [addApprovalTransaction, approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance]) const deadline = useTransactionDeadline() const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, deadline) @@ -156,7 +157,6 @@ export default function SwapButton({ disabled }: SwapButtonProps) { setActiveTrade(trade.trade)} - onUpdate={addApprovalTransaction} {...actionProps} > Review swap diff --git a/src/lib/icons/index.tsx b/src/lib/icons/index.tsx index e50fb770ca..2ce085aa96 100644 --- a/src/lib/icons/index.tsx +++ b/src/lib/icons/index.tsx @@ -12,6 +12,7 @@ import { ArrowDown as ArrowDownIcon, ArrowRight as ArrowRightIcon, ArrowUp as ArrowUpIcon, + BarChart2 as BarChart2Icon, CheckCircle as CheckCircleIcon, ChevronDown as ChevronDownIcon, Clock as ClockIcon, @@ -75,6 +76,7 @@ export const ArrowDown = icon(ArrowDownIcon) export const ArrowRight = icon(ArrowRightIcon) export const ArrowUp = icon(ArrowUpIcon) export const CheckCircle = icon(CheckCircleIcon) +export const BarChart = icon(BarChart2Icon) export const ChevronDown = icon(ChevronDownIcon) export const Clock = icon(ClockIcon) export const HelpCircle = icon(HelpCircleIcon)