Skip to content

Commit

Permalink
Merge branch 'develop' into solana-chain-adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
0xApotheosis authored Oct 15, 2024
2 parents ffa6013 + c542ca2 commit 656c5fb
Show file tree
Hide file tree
Showing 17 changed files with 680 additions and 348 deletions.
1 change: 1 addition & 0 deletions .env.develop
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# feature flags
REACT_APP_FEATURE_LIMIT_ORDERS=true

# mixpanel
REACT_APP_MIXPANEL_TOKEN=1c1369f6ea23a6404bac41b42817cc4b
Expand Down
3 changes: 3 additions & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,9 @@
"slippage": "Slippage: %{slippageFormatted}"
}
},
"limitOrder": {
"heading": "Limit Order"
},
"modals": {
"assetSearch": {
"myAssets": "My Assets",
Expand Down
10 changes: 5 additions & 5 deletions src/components/Acknowledgement/Acknowledgement.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ComponentWithAs, IconProps, ResponsiveValue, ThemeTypings } from '@chakra-ui/react'
import type { BoxProps, ComponentWithAs, IconProps, ThemeTypings } from '@chakra-ui/react'
import { Box, Button, Checkbox, Link, useColorModeValue } from '@chakra-ui/react'
import type * as CSS from 'csstype'
import type { AnimationDefinition, MotionStyle } from 'framer-motion'
import { AnimatePresence, motion } from 'framer-motion'
import type { InterpolationOptions } from 'node-polyglot'
Expand Down Expand Up @@ -92,7 +91,7 @@ type AcknowledgementProps = {
buttonTranslation?: string | [string, InterpolationOptions]
icon?: ComponentWithAs<'svg', IconProps>
disableButton?: boolean
position?: ResponsiveValue<CSS.Property.Position>
boxProps?: BoxProps
}

type StreamingAcknowledgementProps = Omit<AcknowledgementProps, 'message'> & {
Expand All @@ -115,7 +114,7 @@ export const Acknowledgement = ({
buttonTranslation,
disableButton,
icon: CustomIcon,
position = 'relative',
boxProps,
}: AcknowledgementProps) => {
const translate = useTranslate()
const [isShowing, setIsShowing] = useState(false)
Expand Down Expand Up @@ -152,10 +151,11 @@ export const Acknowledgement = ({

return (
<Box
position={position}
position={boxProps?.position ?? 'relative'}
borderRadius={boxBorderRadius}
overflow={isShowing ? 'hidden' : 'visible'}
width={'100%'}
{...boxProps}
>
<AnimatePresence mode='wait' initial={false}>
{shouldShowAcknowledgement && (
Expand Down
44 changes: 39 additions & 5 deletions src/components/MultiHopTrade/MultiHopTrade.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import type { AssetId } from '@shapeshiftoss/caip'
import { assertUnreachable } from '@shapeshiftoss/utils'
import { AnimatePresence } from 'framer-motion'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { MemoryRouter, Route, Switch, useLocation, useParams } from 'react-router-dom'
import { MemoryRouter, Route, Switch, useHistory, useLocation, useParams } from 'react-router-dom'
import { selectAssetById } from 'state/slices/assetsSlice/selectors'
import { tradeInput } from 'state/slices/tradeInputSlice/tradeInputSlice'
import { tradeQuoteSlice } from 'state/slices/tradeQuoteSlice/tradeQuoteSlice'
import { useAppDispatch, useAppSelector } from 'state/store'

import { LimitOrder } from './components/LimitOrder/LimitOrder'
import { MultiHopTradeConfirm } from './components/MultiHopTradeConfirm/MultiHopTradeConfirm'
import { QuoteListRoute } from './components/QuoteList/QuoteListRoute'
import { Claim } from './components/TradeInput/components/Claim/Claim'
import { TradeInput } from './components/TradeInput/TradeInput'
import { VerifyAddresses } from './components/VerifyAddresses/VerifyAddresses'
import { useGetTradeQuotes } from './hooks/useGetTradeQuotes/useGetTradeQuotes'
import { TradeRoutePaths } from './types'
import { TradeInputTab, TradeRoutePaths } from './types'

const TradeRouteEntries = [
TradeRoutePaths.Input,
TradeRoutePaths.Confirm,
TradeRoutePaths.VerifyAddresses,
TradeRoutePaths.QuoteList,
TradeRoutePaths.Claim,
TradeRoutePaths.LimitOrder,
]

export type TradeCardProps = {
Expand Down Expand Up @@ -80,6 +83,7 @@ type TradeRoutesProps = {
}

const TradeRoutes = memo(({ isCompact }: TradeRoutesProps) => {
const history = useHistory()
const location = useLocation()
const dispatch = useAppDispatch()

Expand All @@ -102,12 +106,35 @@ const TradeRoutes = memo(({ isCompact }: TradeRoutesProps) => {
)
}, [location.pathname])

const handleChangeTab = useCallback(
(newTab: TradeInputTab) => {
switch (newTab) {
case TradeInputTab.Trade:
history.push(TradeRoutePaths.Input)
break
case TradeInputTab.LimitOrder:
history.push(TradeRoutePaths.LimitOrder)
break
case TradeInputTab.Claim:
history.push(TradeRoutePaths.Claim)
break
default:
assertUnreachable(newTab)
}
},
[history],
)

return (
<>
<AnimatePresence mode='wait' initial={false}>
<Switch location={location}>
<Route key={TradeRoutePaths.Input} path={TradeRoutePaths.Input}>
<TradeInput isCompact={isCompact} tradeInputRef={tradeInputRef} />
<TradeInput
isCompact={isCompact}
tradeInputRef={tradeInputRef}
onChangeTab={handleChangeTab}
/>
</Route>
<Route key={TradeRoutePaths.Confirm} path={TradeRoutePaths.Confirm}>
<MultiHopTradeConfirm />
Expand All @@ -122,7 +149,14 @@ const TradeRoutes = memo(({ isCompact }: TradeRoutesProps) => {
/>
</Route>
<Route key={TradeRoutePaths.Claim} path={TradeRoutePaths.Claim}>
<Claim isCompact={isCompact} />
<Claim onChangeTab={handleChangeTab} />
</Route>
<Route key={TradeRoutePaths.LimitOrder} path={TradeRoutePaths.LimitOrder}>
<LimitOrder
isCompact={isCompact}
tradeInputRef={tradeInputRef}
onChangeTab={handleChangeTab}
/>
</Route>
</Switch>
</AnimatePresence>
Expand Down
194 changes: 194 additions & 0 deletions src/components/MultiHopTrade/components/LimitOrder/LimitOrder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type { Asset } from '@shapeshiftoss/types'
import type { FormEvent } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useFormContext } from 'react-hook-form'
import { ethereum, fox } from 'test/mocks/assets'
import { WarningAcknowledgement } from 'components/Acknowledgement/Acknowledgement'
import { useReceiveAddress } from 'components/MultiHopTrade/hooks/useReceiveAddress'
import { TradeInputTab } from 'components/MultiHopTrade/types'
import { WalletActions } from 'context/WalletProvider/actions'
import { useErrorHandler } from 'hooks/useErrorToast/useErrorToast'
import { useWallet } from 'hooks/useWallet/useWallet'
import type { ParameterModel } from 'lib/fees/parameters/types'
import { selectIsSnapshotApiQueriesPending, selectVotingPower } from 'state/apis/snapshot/selectors'
import {
selectHasUserEnteredAmount,
selectIsAnyAccountMetadataLoadedForChainId,
} from 'state/slices/selectors'
import {
selectActiveQuote,
selectBuyAmountAfterFeesCryptoPrecision,
selectBuyAmountAfterFeesUserCurrency,
selectIsTradeQuoteRequestAborted,
selectShouldShowTradeQuoteOrAwaitInput,
} from 'state/slices/tradeQuoteSlice/selectors'
import { useAppSelector } from 'state/store'

import { useAccountIds } from '../../hooks/useAccountIds'
import { SharedTradeInput } from '../SharedTradeInput/SharedTradeInput'

const votingPowerParams: { feeModel: ParameterModel } = { feeModel: 'SWAPPER' }
const acknowledgementBoxProps = {
display: 'flex',
justifyContent: 'center',
}

type LimitOrderProps = {
tradeInputRef: React.MutableRefObject<HTMLDivElement | null>
isCompact?: boolean
onChangeTab: (newTab: TradeInputTab) => void
}

// TODO: Implement me
const CollapsibleLimitOrderList = () => <></>

export const LimitOrder = ({ isCompact, tradeInputRef, onChangeTab }: LimitOrderProps) => {
const {
dispatch: walletDispatch,
state: { isConnected, isDemoWallet, wallet },
} = useWallet()

const { handleSubmit } = useFormContext()
const { showErrorToast } = useErrorHandler()
const { manualReceiveAddress, walletReceiveAddress } = useReceiveAddress({
fetchUnchainedAddress: Boolean(wallet && isLedger(wallet)),
})
const { sellAssetAccountId, buyAssetAccountId, setSellAssetAccountId, setBuyAssetAccountId } =
useAccountIds()

const [isConfirmationLoading, setIsConfirmationLoading] = useState(false)
const [shouldShowWarningAcknowledgement, setShouldShowWarningAcknowledgement] = useState(false)

const buyAmountAfterFeesCryptoPrecision = useAppSelector(selectBuyAmountAfterFeesCryptoPrecision)
const buyAmountAfterFeesUserCurrency = useAppSelector(selectBuyAmountAfterFeesUserCurrency)
const shouldShowTradeQuoteOrAwaitInput = useAppSelector(selectShouldShowTradeQuoteOrAwaitInput)
const isSnapshotApiQueriesPending = useAppSelector(selectIsSnapshotApiQueriesPending)
const isTradeQuoteRequestAborted = useAppSelector(selectIsTradeQuoteRequestAborted)
const hasUserEnteredAmount = useAppSelector(selectHasUserEnteredAmount)
const votingPower = useAppSelector(state => selectVotingPower(state, votingPowerParams))
const sellAsset = ethereum // TODO: Implement me
const buyAsset = fox // TODO: Implement me
const activeQuote = useAppSelector(selectActiveQuote)
const isAnyAccountMetadataLoadedForChainIdFilter = useMemo(
() => ({ chainId: sellAsset.chainId }),
[sellAsset.chainId],
)
const isAnyAccountMetadataLoadedForChainId = useAppSelector(state =>
selectIsAnyAccountMetadataLoadedForChainId(state, isAnyAccountMetadataLoadedForChainIdFilter),
)

const isVotingPowerLoading = useMemo(
() => isSnapshotApiQueriesPending && votingPower === undefined,
[isSnapshotApiQueriesPending, votingPower],
)

const isLoading = useMemo(
() =>
// No account meta loaded for that chain
!isAnyAccountMetadataLoadedForChainId ||
(!shouldShowTradeQuoteOrAwaitInput && !isTradeQuoteRequestAborted) ||
isConfirmationLoading ||
// Only consider snapshot API queries as pending if we don't have voting power yet
// if we do, it means we have persisted or cached (both stale) data, which is enough to let the user continue
// as we are optimistic and don't want to be waiting for a potentially very long time for the snapshot API to respond
isVotingPowerLoading,
[
isAnyAccountMetadataLoadedForChainId,
shouldShowTradeQuoteOrAwaitInput,
isTradeQuoteRequestAborted,
isConfirmationLoading,
isVotingPowerLoading,
],
)

const warningAcknowledgementMessage = useMemo(() => {
// TODO: Implement me
return ''
}, [])

const headerRightContent = useMemo(() => {
// TODO: Implement me
return <></>
}, [])

const setBuyAsset = useCallback((_asset: Asset) => {
// TODO: Implement me
}, [])
const setSellAsset = useCallback((_asset: Asset) => {
// TODO: Implement me
}, [])
const handleSwitchAssets = useCallback(() => {
// TODO: Implement me
}, [])

const handleConnect = useCallback(() => {
walletDispatch({ type: WalletActions.SET_WALLET_MODAL, payload: true })
}, [walletDispatch])

const onSubmit = useCallback(() => {
// No preview happening if wallet isn't connected i.e is using the demo wallet
if (!isConnected || isDemoWallet) {
return handleConnect()
}

setIsConfirmationLoading(true)
try {
// TODO: Implement me
} catch (e) {
showErrorToast(e)
}

setIsConfirmationLoading(false)
}, [handleConnect, isConnected, isDemoWallet, showErrorToast])

const handleFormSubmit = useMemo(() => handleSubmit(onSubmit), [handleSubmit, onSubmit])

const handleWarningAcknowledgementSubmit = useCallback(() => {
handleFormSubmit()
}, [handleFormSubmit])

const handleTradeQuoteConfirm = useCallback(
(e: FormEvent<unknown>) => {
e.preventDefault()
handleFormSubmit()
},
[handleFormSubmit],
)

return (
<WarningAcknowledgement
message={warningAcknowledgementMessage}
onAcknowledge={handleWarningAcknowledgementSubmit}
shouldShowAcknowledgement={shouldShowWarningAcknowledgement}
setShouldShowAcknowledgement={setShouldShowWarningAcknowledgement}
boxProps={acknowledgementBoxProps}
>
<SharedTradeInput
activeQuote={activeQuote}
buyAmountAfterFeesCryptoPrecision={buyAmountAfterFeesCryptoPrecision}
buyAmountAfterFeesUserCurrency={buyAmountAfterFeesUserCurrency}
buyAsset={buyAsset}
hasUserEnteredAmount={hasUserEnteredAmount}
headerRightContent={headerRightContent}
buyAssetAccountId={buyAssetAccountId}
sellAssetAccountId={sellAssetAccountId}
isCompact={isCompact}
isLoading={isLoading}
manualReceiveAddress={manualReceiveAddress}
sellAsset={sellAsset}
sideComponent={CollapsibleLimitOrderList}
tradeInputRef={tradeInputRef}
tradeInputTab={TradeInputTab.LimitOrder}
walletReceiveAddress={walletReceiveAddress}
handleSwitchAssets={handleSwitchAssets}
onSubmit={handleTradeQuoteConfirm}
setBuyAsset={setBuyAsset}
setBuyAssetAccountId={setBuyAssetAccountId}
setSellAsset={setSellAsset}
setSellAssetAccountId={setSellAssetAccountId}
onChangeTab={onChangeTab}
/>
</WarningAcknowledgement>
)
}
Loading

0 comments on commit 656c5fb

Please sign in to comment.