Skip to content

Commit

Permalink
feat: implemented trade success ui for multi-hop trades
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture committed Nov 20, 2023
1 parent ac62005 commit 897b7ee
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 106 deletions.
20 changes: 11 additions & 9 deletions src/components/MultiHopTrade/MultiHopTrade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ export const MultiHopTrade = memo(
}, [defaultBuyAsset, defaultSellAsset, dispatch, routeBuyAsset])

return (
<Card {...cardProps}>
<CardBody px={0} py={0}>
<FormProvider {...methods}>
<MemoryRouter initialEntries={MultiHopEntries} initialIndex={0}>
<MultiHopRoutes />
</MemoryRouter>
</FormProvider>
</CardBody>
</Card>
<>
<Card {...cardProps}>
<CardBody px={0} py={0}>
<FormProvider {...methods}>
<MemoryRouter initialEntries={MultiHopEntries} initialIndex={0}>
<MultiHopRoutes />
</MemoryRouter>
</FormProvider>
</CardBody>
</Card>
</>
)
},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,29 @@
import {
Card,
CardBody,
CardHeader,
Heading,
Stack,
useDisclosure,
usePrevious,
} from '@chakra-ui/react'
import { Card, CardBody, CardHeader, Heading, useDisclosure, usePrevious } from '@chakra-ui/react'
import { memo, useCallback, useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { WithBackButton } from 'components/MultiHopTrade/components/WithBackButton'
import { TradeRoutePaths } from 'components/MultiHopTrade/types'
import { SlideTransition } from 'components/SlideTransition'
import { Text } from 'components/Text'
import { swappers as swappersSlice } from 'state/slices/swappersSlice/swappersSlice'
import {
selectActiveSwapperName,
selectFirstHop,
selectIsActiveQuoteMultiHop,
selectLastHop,
selectTradeExecutionState,
} from 'state/slices/tradeQuoteSlice/selectors'
import { selectTradeExecutionState } from 'state/slices/tradeQuoteSlice/selectors'
import { tradeQuoteSlice } from 'state/slices/tradeQuoteSlice/tradeQuoteSlice'
import { MultiHopExecutionState } from 'state/slices/tradeQuoteSlice/types'
import { useAppDispatch, useAppSelector } from 'state/store'

import { TradeSuccess } from '../TradeSuccess/TradeSuccess'
import { Footer } from './components/Footer'
import { Hop } from './components/Hop'
import { Hops } from './components/Hops'
import { useIsApprovalInitiallyNeeded } from './hooks/useIsApprovalInitiallyNeeded'

const cardBorderRadius = { base: 'xl' }

export const MultiHopTradeConfirm = memo(() => {
const dispatch = useAppDispatch()
const swapperName = useAppSelector(selectActiveSwapperName)
const firstHop = useAppSelector(selectFirstHop)
const lastHop = useAppSelector(selectLastHop)
const isMultiHopTrade = useAppSelector(selectIsActiveQuoteMultiHop)
const tradeExecutionState = useAppSelector(selectTradeExecutionState)
const previousTradeExecutionState = usePrevious(tradeExecutionState)
const history = useHistory()

const { isOpen: isFirstHopOpen, onToggle: onToggleFirstHop } = useDisclosure({
defaultIsOpen: true,
})

const { isOpen: isSecondHopOpen, onToggle: onToggleSecondHop } = useDisclosure({
defaultIsOpen: true,
})

const { isApprovalInitiallyNeeded, isLoading } = useIsApprovalInitiallyNeeded()

// set initial approval requirements
Expand All @@ -63,6 +38,14 @@ export const MultiHopTradeConfirm = memo(() => {
history.push(TradeRoutePaths.Input)
}, [dispatch, history])

const { isOpen: isFirstHopOpen, onToggle: onToggleFirstHop } = useDisclosure({
defaultIsOpen: true,
})

const { isOpen: isSecondHopOpen, onToggle: onToggleSecondHop } = useDisclosure({
defaultIsOpen: true,
})

// toggle hop open states as we transition to the next hop
useEffect(() => {
if (
Expand All @@ -81,50 +64,40 @@ export const MultiHopTradeConfirm = memo(() => {
tradeExecutionState,
])

// TODO: redirect to completed page when trade is complete
// useEffect(() => {
// if (
// previousTradeExecutionState !== tradeExecutionState &&
// tradeExecutionState === MultiHopExecutionState.TradeComplete
// ) {
// history.push(TradeRoutePaths.Complete)
// }
// }, [history, previousTradeExecutionState, tradeExecutionState])

if (!firstHop || !swapperName) return null

return (
<SlideTransition>
<Card flex={1} borderRadius={cardBorderRadius} width='full' padding={6}>
<CardHeader px={0} pt={0}>
<WithBackButton handleBack={handleBack}>
<Heading textAlign='center'>
<Text translation='trade.confirmDetails' />
<Text
translation={
tradeExecutionState === MultiHopExecutionState.Previewing
? 'trade.confirmDetails'
: 'trade.trade'
}
/>
</Heading>
</WithBackButton>
</CardHeader>
<CardBody pb={0} px={0}>
<Stack spacing={6}>
<Hop
tradeQuoteStep={firstHop}
swapperName={swapperName}
hopIndex={0}
isOpen={isFirstHopOpen}
onToggleIsOpen={onToggleFirstHop}
/>
{isMultiHopTrade && lastHop && (
<Hop
tradeQuoteStep={lastHop}
swapperName={swapperName}
hopIndex={1}
isOpen={isSecondHopOpen}
onToggleIsOpen={onToggleSecondHop}
{tradeExecutionState === MultiHopExecutionState.TradeComplete ? (
<TradeSuccess handleBack={handleBack}>
<Hops isFirstHopOpen isSecondHopOpen />
</TradeSuccess>
) : (
<>
<CardBody pb={0} px={0}>
<Hops
isFirstHopOpen={isFirstHopOpen}
isSecondHopOpen={isSecondHopOpen}
onToggleFirstHop={onToggleFirstHop}
onToggleSecondHop={onToggleSecondHop}
/>
)}
</Stack>
</CardBody>
</CardBody>
<Footer />
</>
)}
</Card>
<Footer />
</SlideTransition>
)
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Box, Button, Card, Icon, Switch, Tooltip, VStack } from '@chakra-ui/react'
import { TxStatus } from '@shapeshiftoss/unchained-client'
import { useCallback, useMemo } from 'react'
import { FaInfoCircle } from 'react-icons/fa'
import { useTranslate } from 'react-polyglot'
Expand Down Expand Up @@ -69,8 +70,10 @@ export const ApprovalStep = ({

// the txStatus needs to be undefined before the tx is executed to handle "ready" but not "executing" status
const txStatus =
HOP_EXECUTION_STATE_ORDERED.indexOf(hopExecutionState) >=
HOP_EXECUTION_STATE_ORDERED.indexOf(HopExecutionState.AwaitingApprovalExecution)
hopExecutionState === HopExecutionState.Complete
? TxStatus.Confirmed
: HOP_EXECUTION_STATE_ORDERED.indexOf(hopExecutionState) >=
HOP_EXECUTION_STATE_ORDERED.indexOf(HopExecutionState.AwaitingApprovalExecution)
? _approvalTxStatus
: undefined

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'
import {
Box,
Card,
Expand All @@ -8,7 +7,6 @@ import {
Divider,
Flex,
HStack,
IconButton,
Stepper,
useColorModeValue,
} from '@chakra-ui/react'
Expand All @@ -17,7 +15,6 @@ import { getDefaultSlippageDecimalPercentageForSwapper } from 'constants/constan
import prettyMilliseconds from 'pretty-ms'
import { useMemo, useState } from 'react'
import { FaAdjust, FaGasPump, FaProcedures } from 'react-icons/fa'
import { useTranslate } from 'react-polyglot'
import { Amount } from 'components/Amount/Amount'
import { RawText } from 'components/Text'
import type { SwapperName, TradeQuoteStep } from 'lib/swapper/types'
Expand All @@ -40,6 +37,7 @@ import { DonationStep } from './DonationStep'
import { HopTransactionStep } from './HopTransactionStep'
import { JuicyGreenCheck } from './JuicyGreenCheck'
import { TimeRemaining } from './TimeRemaining'
import { TwirlyToggle } from './TwirlyToggle'

const cardBorderRadius = { base: 'xl' }

Expand All @@ -54,13 +52,10 @@ export const Hop = ({
tradeQuoteStep: TradeQuoteStep
hopIndex: number
isOpen: boolean
onToggleIsOpen: () => void
onToggleIsOpen?: () => void
}) => {
const translate = useTranslate()
const backgroundColor = useColorModeValue('gray.100', 'gray.750')
const borderColor = useColorModeValue('gray.50', 'gray.650')
const chevronUpIcon = useMemo(() => <ChevronUpIcon boxSize='16px' />, [])
const chevronDownIcon = useMemo(() => <ChevronDownIcon boxSize='16px' />, [])
const networkFeeFiatPrecision = useAppSelector(state =>
selectHopTotalNetworkFeeFiatPrecision(state, hopIndex),
)
Expand Down Expand Up @@ -91,32 +86,13 @@ export const Hop = ({
)
)
case TxStatus.Confirmed:
return (
<Box width='auto'>
<IconButton
aria-label={translate('trade.expand')}
variant='link'
p={4}
borderTopRadius='none'
colorScheme='blue'
onClick={onToggleIsOpen}
width='full'
icon={isOpen ? chevronUpIcon : chevronDownIcon}
/>
</Box>
)
return onToggleIsOpen ? (
<TwirlyToggle isOpen={isOpen} onToggle={onToggleIsOpen} p={4} />
) : null
default:
return null
}
}, [
chevronDownIcon,
chevronUpIcon,
tradeQuoteStep.estimatedExecutionTimeMs,
isOpen,
onToggleIsOpen,
translate,
txStatus,
])
}, [tradeQuoteStep.estimatedExecutionTimeMs, isOpen, onToggleIsOpen, txStatus])

const initialApprovalRequirements = useAppSelector(selectInitialApprovalRequirements)
const isApprovalInitiallyNeeded = initialApprovalRequirements?.[hopIndex]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Button, Link, VStack } from '@chakra-ui/react'
import type { KnownChainIds } from '@shapeshiftoss/types'
import type { TxStatus } from '@shapeshiftoss/unchained-client'
import { TxStatus } from '@shapeshiftoss/unchained-client'
import { useCallback, useEffect, useMemo } from 'react'
import { Row } from 'components/Row/Row'
import { RawText, Text } from 'components/Text'
Expand Down Expand Up @@ -87,8 +87,10 @@ export const HopTransactionStep = ({

// the txStatus needs to be undefined before the tx is executed to handle "ready" but not "executing" status
const txStatus =
HOP_EXECUTION_STATE_ORDERED.indexOf(hopExecutionState) >=
HOP_EXECUTION_STATE_ORDERED.indexOf(HopExecutionState.AwaitingTradeExecution)
hopExecutionState === HopExecutionState.Complete
? TxStatus.Confirmed
: HOP_EXECUTION_STATE_ORDERED.indexOf(hopExecutionState) >=
HOP_EXECUTION_STATE_ORDERED.indexOf(HopExecutionState.AwaitingTradeExecution)
? tradeStatus
: undefined

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Stack } from '@chakra-ui/react'
import { memo } from 'react'
import {
selectActiveSwapperName,
selectFirstHop,
selectIsActiveQuoteMultiHop,
selectLastHop,
} from 'state/slices/tradeQuoteSlice/selectors'
import { useAppSelector } from 'state/store'

import { Hop } from './Hop'

export type HopsProps = {
isFirstHopOpen: boolean
isSecondHopOpen: boolean
onToggleFirstHop?: () => void
onToggleSecondHop?: () => void
}

export const Hops = memo((props: HopsProps) => {
const { isFirstHopOpen, isSecondHopOpen, onToggleFirstHop, onToggleSecondHop } = props
const swapperName = useAppSelector(selectActiveSwapperName)
const firstHop = useAppSelector(selectFirstHop)
const lastHop = useAppSelector(selectLastHop)
const isMultiHopTrade = useAppSelector(selectIsActiveQuoteMultiHop)

if (!firstHop || !swapperName) return null

return (
<Stack spacing={6}>
<Hop
tradeQuoteStep={firstHop}
swapperName={swapperName}
hopIndex={0}
isOpen={isFirstHopOpen}
onToggleIsOpen={onToggleFirstHop}
/>
{isMultiHopTrade && lastHop && (
<Hop
tradeQuoteStep={lastHop}
swapperName={swapperName}
hopIndex={1}
isOpen={isSecondHopOpen}
onToggleIsOpen={onToggleSecondHop}
/>
)}
</Stack>
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ChevronUpIcon } from '@chakra-ui/icons'
import type { BoxProps } from '@chakra-ui/react'
import { Box, Circle, IconButton, useColorModeValue } from '@chakra-ui/react'
import { useMemo } from 'react'
import { useTranslate } from 'react-polyglot'

export type TwirlyToggleProps = { isOpen: boolean; onToggle: () => void } & BoxProps

export const TwirlyToggle = ({ isOpen, onToggle, ...boxProps }: TwirlyToggleProps) => {
const translate = useTranslate()
const backgroundColor = useColorModeValue('gray.100', 'gray.750')

const icon = useMemo(
() => (
<Circle size={8} bgColor={backgroundColor} borderWidth={0}>
<ChevronUpIcon
transform={isOpen ? 'rotate(180deg)' : 'rotate(0deg)'}
transition='transform 0.2s ease-in-out'
boxSize='16px'
/>
</Circle>
),
[backgroundColor, isOpen],
)

return (
<Box width='auto' {...boxProps}>
<IconButton
aria-label={translate('trade.expand')}
variant='link'
borderTopRadius='none'
colorScheme='blue'
onClick={onToggle}
width='full'
icon={icon}
/>
</Box>
)
}
Loading

0 comments on commit 897b7ee

Please sign in to comment.