Commit 2806f651 authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

feat: use modal for txn confirmation on l2 (#2055)

* use modal for txn confirmation on l2

* update with error state

* remove 1 translation

* dedicated L2 component

* update styling on to be less jumpy

* add success animation to modal

* revert regular submitted content

* remove useless hook in popups

* remove import
Co-authored-by: default avatarIan Lapham <ian_lapham@alumni.brown.edu>
parent 422c703e
import useTheme from 'hooks/useTheme'
import styled, { keyframes } from 'styled-components/macro'
const Wrapper = styled.div`
height: 90px;
width: 90px;
`
const dash = keyframes`
0% {
stroke-dashoffset: 1000;
}
100% {
stroke-dashoffset: 0;
}
`
const dashCheck = keyframes`
0% {
stroke-dashoffset: -100;
}
100% {
stroke-dashoffset: 900;
}
`
const Circle = styled.circle`
stroke-dasharray: 1000;
stroke-dashoffset: 0;
-webkit-animation: ${dash} 0.9s ease-in-out;
animation: ${dash} 0.9s ease-in-out;
`
const PolyLine = styled.polyline`
stroke-dasharray: 1000;
stroke-dashoffset: 0;
stroke-dashoffset: -100;
-webkit-animation: ${dashCheck} 0.9s 0.35s ease-in-out forwards;
animation: ${dashCheck} 0.9s 0.35s ease-in-out forwards;
`
export default function AnimatedConfirmation() {
const theme = useTheme()
return (
<Wrapper className="w4rAnimated_checkmark">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2">
<Circle
className="path circle"
fill="none"
stroke={theme.green1}
strokeWidth="6"
strokeMiterlimit="10"
cx="65.1"
cy="65.1"
r="62.1"
/>
<PolyLine
className="path check"
fill="none"
stroke={theme.green1}
strokeWidth="6"
strokeLinecap="round"
strokeMiterlimit="10"
points="100.2,40.2 51.5,88.8 29.8,67.5 "
/>
</svg>
</Wrapper>
)
}
import { Currency } from '@uniswap/sdk-core'
import { ReactNode, useContext, useEffect } from 'react'
import { ReactNode, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'
import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink'
import Modal from '../Modal'
......@@ -7,7 +7,7 @@ import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon, CustomLightSpinner } from '../../theme/components'
import { RowBetween, RowFixed } from '../Row'
import { AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { ButtonPrimary, ButtonLight } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg'
......@@ -15,7 +15,10 @@ import MetaMaskLogo from '../../assets/images/metamask.png'
import { useActiveWeb3React } from '../../hooks/web3'
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
import { Trans } from '@lingui/macro'
import { L2_CHAIN_IDS } from 'constants/chains'
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains'
import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks'
import Badge from 'components/Badge'
import AnimatedConfirmation from './AnimatedConfirmation'
const Wrapper = styled.div`
width: 100%;
......@@ -31,7 +34,7 @@ const BottomSection = styled(Section)`
`
const ConfirmedIcon = styled(ColumnCenter)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '20px 0' : '60px 0;')};
padding: ${({ inline }) => (inline ? '20px 0' : '32px 0;')};
`
const StyledLogo = styled.img`
......@@ -65,12 +68,10 @@ function ConfirmationPendingContent({
<Text fontWeight={500} fontSize={20} textAlign="center">
<Trans>Waiting For Confirmation</Trans>
</Text>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={600} fontSize={14} color="" textAlign="center">
{pendingText}
</Text>
</AutoColumn>
<Text fontSize={12} color="#565A69" textAlign="center" marginBottom={12}>
<Text fontWeight={400} fontSize={16} textAlign="center">
{pendingText}
</Text>
<Text fontWeight={500} fontSize={14} color="#565A69" textAlign="center" marginBottom="12px">
<Trans>Confirm this transaction in your wallet</Trans>
</Text>
</AutoColumn>
......@@ -78,7 +79,6 @@ function ConfirmationPendingContent({
</Wrapper>
)
}
function TransactionSubmittedContent({
onDismiss,
chainId,
......@@ -207,6 +207,106 @@ export function TransactionErrorContent({ message, onDismiss }: { message: React
)
}
function L2Content({
onDismiss,
chainId,
hash,
pendingText,
inline,
}: {
onDismiss: () => void
hash: string | undefined
chainId: number
currencyToAdd?: Currency | undefined
pendingText: ReactNode
inline?: boolean // not in modal
}) {
const theme = useContext(ThemeContext)
const transaction = useTransaction(hash)
const confirmed = useIsTransactionConfirmed(hash)
const transactionSuccess = transaction?.receipt?.status === 1
// convert unix time difference to seconds
const secondsToConfirm = transaction?.confirmedTime
? (transaction.confirmedTime - transaction.addedTime) / 1000
: undefined
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
return (
<Wrapper>
<Section inline={inline}>
{!inline && (
<RowBetween mb="16px">
<Badge>
<RowFixed>
<StyledLogo src={info.logoUrl} style={{ margin: '0 8px 0 0' }} />
{info.label}
</RowFixed>
</Badge>
<CloseIcon onClick={onDismiss} />
</RowBetween>
)}
<ConfirmedIcon inline={inline}>
{confirmed ? (
transactionSuccess ? (
// <CheckCircle strokeWidth={1} size={inline ? '40px' : '90px'} color={theme.green1} />
<AnimatedConfirmation />
) : (
<AlertCircle strokeWidth={1} size={inline ? '40px' : '90px'} color={theme.red1} />
)
) : (
<CustomLightSpinner src={Circle} alt="loader" size={inline ? '40px' : '90px'} />
)}
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20} textAlign="center">
{!hash ? (
<Trans>Confirm transaction in wallet</Trans>
) : !confirmed ? (
<Trans>Transaction Submitted</Trans>
) : transactionSuccess ? (
<Trans>Success</Trans>
) : (
<Trans>Error</Trans>
)}
</Text>
<Text fontWeight={400} fontSize={16} textAlign="center">
{transaction?.summary ?? pendingText ?? ''}
</Text>
{chainId && hash ? (
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
<Trans>View on Explorer</Trans>
</Text>
</ExternalLink>
) : (
<div style={{ height: '17px' }}></div>
)}
<Text color={theme.text3} style={{ margin: '20px 0 0 0' }} fontSize={'14px'}>
{!secondsToConfirm ? (
<div style={{ height: '24px' }}></div>
) : (
<div>
<Trans>Transaction completed in </Trans>
<span style={{ fontWeight: 500, marginLeft: '4px', color: theme.text1 }}>
{secondsToConfirm} seconds 🎉
</span>
</div>
)}
</Text>
<ButtonPrimary onClick={onDismiss} style={{ margin: '4px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
{inline ? <Trans>Return</Trans> : <Trans>Close</Trans>}
</Text>
</ButtonPrimary>
</AutoColumn>
</Section>
</Wrapper>
)
}
interface ConfirmationModalProps {
isOpen: boolean
onDismiss: () => void
......@@ -228,21 +328,16 @@ export default function TransactionConfirmationModal({
}: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React()
// if on L2 and txn is submitted, close automatically (if open)
useEffect(() => {
if (isOpen && chainId && L2_CHAIN_IDS.includes(chainId) && hash) {
onDismiss()
}
}, [chainId, hash, isOpen, onDismiss])
const isL2 = Boolean(chainId && L2_CHAIN_IDS.includes(chainId))
if (!chainId) return null
// confirmation screen
// if on L2 and submitted dont render content, as should auto dismiss
// need this to skip submitted view during state update ^^
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
{L2_CHAIN_IDS.includes(chainId) && hash ? null : attemptingTxn ? (
{isL2 && (hash || attemptingTxn) ? (
<L2Content chainId={chainId} hash={hash} onDismiss={onDismiss} pendingText={pendingText} />
) : attemptingTxn ? (
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />
) : hash ? (
<TransactionSubmittedContent
......
......@@ -52,7 +52,7 @@ interface L1ChainInfo {
readonly infoLink: string
readonly label: string
}
interface L2ChainInfo extends L1ChainInfo {
export interface L2ChainInfo extends L1ChainInfo {
readonly bridge: string
readonly logoUrl: string
}
......
......@@ -45,6 +45,16 @@ export function useAllTransactions(): { [txHash: string]: TransactionDetails } {
return chainId ? state[chainId] ?? {} : {}
}
export function useTransaction(transactionHash?: string): TransactionDetails | undefined {
const allTransactions = useAllTransactions()
if (!transactionHash) {
return undefined
}
return allTransactions[transactionHash]
}
export function useIsTransactionPending(transactionHash?: string): boolean {
const transactions = useAllTransactions()
......@@ -53,6 +63,14 @@ export function useIsTransactionPending(transactionHash?: string): boolean {
return !transactions[transactionHash].receipt
}
export function useIsTransactionConfirmed(transactionHash?: string): boolean {
const transactions = useAllTransactions()
if (!transactionHash || !transactions[transactionHash]) return false
return Boolean(transactions[transactionHash].receipt)
}
/**
* Returns whether a transaction happened in the last day (86400 seconds * 1000 milliseconds / second)
* @param tx to check for recency
......
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