chore: refactoring bag (#6039)
* chore: refactoring bag component to sub components (#6027) * moving totalEthPrice to hook * moving everything from bag to bag footer * moving transaction state to sep hook * explicit type * itemsInBag * fixing transaction tracking * chore: refactor useFetchAssets to make it more readable (#6043) * chore: refactoring useFetchAssets to make it more readable * remvoing eslint stuff * extracting what can be * comments * removing feature flag * changing return type of useUsd hook * zustand shallow
This commit is contained in:
parent
783f42abcc
commit
a362f8797a
@ -1,5 +1,4 @@
|
|||||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
|
||||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||||
@ -218,12 +217,6 @@ export default function FeatureFlagModal() {
|
|||||||
featureFlag={FeatureFlag.swapWidget}
|
featureFlag={FeatureFlag.swapWidget}
|
||||||
label="Swap Widget"
|
label="Swap Widget"
|
||||||
/>
|
/>
|
||||||
<FeatureFlagOption
|
|
||||||
variant={GqlRoutingVariant}
|
|
||||||
value={useGqlRoutingFlag()}
|
|
||||||
featureFlag={FeatureFlag.gqlRouting}
|
|
||||||
label="GraphQL NFT Routing"
|
|
||||||
/>
|
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
variant={NftGraphqlVariant}
|
variant={NftGraphqlVariant}
|
||||||
value={useNftGraphqlFlag()}
|
value={useNftGraphqlFlag()}
|
||||||
|
@ -6,7 +6,6 @@ export enum FeatureFlag {
|
|||||||
permit2 = 'permit2',
|
permit2 = 'permit2',
|
||||||
payWithAnyToken = 'payWithAnyToken',
|
payWithAnyToken = 'payWithAnyToken',
|
||||||
swapWidget = 'swap_widget_replacement_enabled',
|
swapWidget = 'swap_widget_replacement_enabled',
|
||||||
gqlRouting = 'gqlRouting',
|
|
||||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||||
nftGraphql = 'nft_graphql_migration',
|
nftGraphql = 'nft_graphql_migration',
|
||||||
taxService = 'tax_service_banner',
|
taxService = 'tax_service_banner',
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
|
||||||
|
|
||||||
export function useGqlRoutingFlag(): BaseVariant {
|
|
||||||
return useBaseFlag(FeatureFlag.gqlRouting, BaseVariant.Enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { BaseVariant as GqlRoutingVariant }
|
|
@ -1,38 +1,16 @@
|
|||||||
import { BigNumber } from '@ethersproject/bignumber'
|
|
||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||||
import { NFTEventName } from '@uniswap/analytics-events'
|
import { NFTEventName } from '@uniswap/analytics-events'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
|
||||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
|
||||||
import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
|
|
||||||
import { useIsNftDetailsPage, useIsNftPage, useIsNftProfilePage } from 'hooks/useIsNftPage'
|
import { useIsNftDetailsPage, useIsNftPage, useIsNftProfilePage } from 'hooks/useIsNftPage'
|
||||||
import { BagFooter } from 'nft/components/bag/BagFooter'
|
import { BagFooter } from 'nft/components/bag/BagFooter'
|
||||||
import { Box } from 'nft/components/Box'
|
import { Box } from 'nft/components/Box'
|
||||||
import { Portal } from 'nft/components/common/Portal'
|
import { Portal } from 'nft/components/common/Portal'
|
||||||
import { Column } from 'nft/components/Flex'
|
import { Column } from 'nft/components/Flex'
|
||||||
import { Overlay } from 'nft/components/modals/Overlay'
|
import { Overlay } from 'nft/components/modals/Overlay'
|
||||||
import {
|
import { useBag, useIsMobile, useProfilePageState, useSellAsset } from 'nft/hooks'
|
||||||
useBag,
|
import { BagStatus, ProfilePageStateType } from 'nft/types'
|
||||||
useIsMobile,
|
import { formatAssetEventProperties, recalculateBagUsingPooledAssets } from 'nft/utils'
|
||||||
useProfilePageState,
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
useSellAsset,
|
|
||||||
useSendTransaction,
|
|
||||||
useTransactionResponse,
|
|
||||||
} from 'nft/hooks'
|
|
||||||
import { useTokenInput } from 'nft/hooks/useTokenInput'
|
|
||||||
import { fetchRoute } from 'nft/queries'
|
|
||||||
import { BagItemStatus, BagStatus, ProfilePageStateType, RouteResponse, TxStateType } from 'nft/types'
|
|
||||||
import {
|
|
||||||
buildNftTradeInputFromBagItems,
|
|
||||||
buildSellObject,
|
|
||||||
formatAssetEventProperties,
|
|
||||||
recalculateBagUsingPooledAssets,
|
|
||||||
sortUpdatedAssets,
|
|
||||||
} from 'nft/utils'
|
|
||||||
import { buildRouteResponse } from 'nft/utils/nftRoute'
|
|
||||||
import { combineBuyItemsWithTxRoute } from 'nft/utils/txRoute/combineItemsWithTxRoute'
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import { useQueryClient } from 'react-query'
|
|
||||||
import styled from 'styled-components/macro'
|
import styled from 'styled-components/macro'
|
||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
import { shallow } from 'zustand/shallow'
|
import { shallow } from 'zustand/shallow'
|
||||||
@ -120,8 +98,6 @@ const ScrollingIndicator = ({ top, show }: SeparatorProps) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const Bag = () => {
|
const Bag = () => {
|
||||||
const { account, provider } = useWeb3React()
|
|
||||||
|
|
||||||
const { resetSellAssets, sellAssets } = useSellAsset(
|
const { resetSellAssets, sellAssets } = useSellAsset(
|
||||||
({ reset, sellAssets }) => ({
|
({ reset, sellAssets }) => ({
|
||||||
resetSellAssets: reset,
|
resetSellAssets: reset,
|
||||||
@ -132,36 +108,16 @@ const Bag = () => {
|
|||||||
|
|
||||||
const { setProfilePageState } = useProfilePageState(({ setProfilePageState }) => ({ setProfilePageState }))
|
const { setProfilePageState } = useProfilePageState(({ setProfilePageState }) => ({ setProfilePageState }))
|
||||||
|
|
||||||
const {
|
const { bagStatus, bagIsLocked, reset, bagExpanded, toggleBag, setBagExpanded } = useBag(
|
||||||
bagStatus,
|
(state) => ({ ...state, bagIsLocked: state.isLocked, uncheckedItemsInBag: state.itemsInBag }),
|
||||||
setBagStatus,
|
shallow
|
||||||
didOpenUnavailableAssets,
|
)
|
||||||
setDidOpenUnavailableAssets,
|
|
||||||
bagIsLocked,
|
|
||||||
setLocked,
|
|
||||||
reset,
|
|
||||||
setItemsInBag,
|
|
||||||
bagExpanded,
|
|
||||||
toggleBag,
|
|
||||||
setTotalEthPrice,
|
|
||||||
setBagExpanded,
|
|
||||||
} = useBag((state) => ({ ...state, bagIsLocked: state.isLocked, uncheckedItemsInBag: state.itemsInBag }), shallow)
|
|
||||||
const { uncheckedItemsInBag } = useBag(({ itemsInBag }) => ({ uncheckedItemsInBag: itemsInBag }))
|
const { uncheckedItemsInBag } = useBag(({ itemsInBag }) => ({ uncheckedItemsInBag: itemsInBag }))
|
||||||
|
|
||||||
const isProfilePage = useIsNftProfilePage()
|
const isProfilePage = useIsNftProfilePage()
|
||||||
const isDetailsPage = useIsNftDetailsPage()
|
const isDetailsPage = useIsNftDetailsPage()
|
||||||
const isNFTPage = useIsNftPage()
|
const isNFTPage = useIsNftPage()
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const usingGqlRouting = useGqlRoutingFlag() === GqlRoutingVariant.Enabled
|
|
||||||
|
|
||||||
const sendTransaction = useSendTransaction((state) => state.sendTransaction)
|
|
||||||
const transactionState = useSendTransaction((state) => state.state)
|
|
||||||
const setTransactionState = useSendTransaction((state) => state.setState)
|
|
||||||
const transactionStateRef = useRef(transactionState)
|
|
||||||
const [setTransactionResponse] = useTransactionResponse((state) => [state.setTransactionResponse])
|
|
||||||
const tokenTradeInput = useTokenInput((state) => state.tokenTradeInput)
|
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
const itemsInBag = useMemo(() => recalculateBagUsingPooledAssets(uncheckedItemsInBag), [uncheckedItemsInBag])
|
const itemsInBag = useMemo(() => recalculateBagUsingPooledAssets(uncheckedItemsInBag), [uncheckedItemsInBag])
|
||||||
|
|
||||||
@ -175,210 +131,14 @@ const Bag = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { totalEthPrice } = useMemo(() => {
|
|
||||||
const totalEthPrice = itemsInBag.reduce(
|
|
||||||
(total, item) =>
|
|
||||||
item.status !== BagItemStatus.UNAVAILABLE
|
|
||||||
? total.add(
|
|
||||||
BigNumber.from(
|
|
||||||
item.asset.updatedPriceInfo ? item.asset.updatedPriceInfo.ETHPrice : item.asset.priceInfo.ETHPrice
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: total,
|
|
||||||
BigNumber.from(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
return { totalEthPrice }
|
|
||||||
}, [itemsInBag])
|
|
||||||
|
|
||||||
const purchaseAssets = async (routingData: RouteResponse, purchasingWithErc20: boolean) => {
|
|
||||||
if (!provider || !routingData) return
|
|
||||||
const purchaseResponse = await sendTransaction(
|
|
||||||
provider?.getSigner(),
|
|
||||||
itemsInBag.filter((item) => item.status !== BagItemStatus.UNAVAILABLE).map((item) => item.asset),
|
|
||||||
routingData,
|
|
||||||
purchasingWithErc20
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
purchaseResponse &&
|
|
||||||
(transactionStateRef.current === TxStateType.Success || transactionStateRef.current === TxStateType.Failed)
|
|
||||||
) {
|
|
||||||
setLocked(false)
|
|
||||||
setModalIsOpen(false)
|
|
||||||
setTransactionResponse(purchaseResponse)
|
|
||||||
setBagExpanded({ bagExpanded: false })
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCloseBag = useCallback(() => {
|
const handleCloseBag = useCallback(() => {
|
||||||
setBagExpanded({ bagExpanded: false, manualClose: true })
|
setBagExpanded({ bagExpanded: false, manualClose: true })
|
||||||
}, [setBagExpanded])
|
}, [setBagExpanded])
|
||||||
|
|
||||||
const [fetchGqlRoute] = useNftRouteLazyQuery()
|
|
||||||
|
|
||||||
const fetchAssets = async () => {
|
|
||||||
const itemsToBuy = itemsInBag.filter((item) => item.status !== BagItemStatus.UNAVAILABLE).map((item) => item.asset)
|
|
||||||
const ethSellObject = buildSellObject(
|
|
||||||
itemsToBuy
|
|
||||||
.reduce((ethTotal, asset) => ethTotal.add(BigNumber.from(asset.priceInfo.ETHPrice)), BigNumber.from(0))
|
|
||||||
.toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
didOpenUnavailableAssets && setDidOpenUnavailableAssets(false)
|
|
||||||
!bagIsLocked && setLocked(true)
|
|
||||||
setBagStatus(BagStatus.FETCHING_ROUTE)
|
|
||||||
try {
|
|
||||||
if (usingGqlRouting) {
|
|
||||||
fetchGqlRoute({
|
|
||||||
variables: {
|
|
||||||
senderAddress: usingGqlRouting && account ? account : '',
|
|
||||||
nftTrades: usingGqlRouting ? buildNftTradeInputFromBagItems(itemsInBag) : [],
|
|
||||||
tokenTrades: tokenTradeInput ? tokenTradeInput : undefined,
|
|
||||||
},
|
|
||||||
onCompleted: (data) => {
|
|
||||||
if (!data.nftRoute || !data.nftRoute.route) {
|
|
||||||
setBagStatus(BagStatus.ADDING_TO_BAG)
|
|
||||||
setLocked(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const purchasingWithErc20 = !!tokenTradeInput
|
|
||||||
const { route, routeResponse } = buildRouteResponse(data.nftRoute, purchasingWithErc20)
|
|
||||||
|
|
||||||
const { hasPriceAdjustment, updatedAssets } = combineBuyItemsWithTxRoute(itemsToBuy, route)
|
|
||||||
const shouldRefetchCalldata = hasPriceAdjustment && purchasingWithErc20
|
|
||||||
|
|
||||||
const fetchedPriceChangedAssets = updatedAssets
|
|
||||||
.filter((asset) => asset.updatedPriceInfo)
|
|
||||||
.sort(sortUpdatedAssets)
|
|
||||||
const fetchedUnavailableAssets = updatedAssets.filter((asset) => asset.isUnavailable)
|
|
||||||
const fetchedUnchangedAssets = updatedAssets.filter(
|
|
||||||
(asset) => !asset.updatedPriceInfo && !asset.isUnavailable
|
|
||||||
)
|
|
||||||
const hasReviewedAssets = fetchedUnchangedAssets.length > 0
|
|
||||||
const hasAssetsInReview = fetchedPriceChangedAssets.length > 0
|
|
||||||
const hasUnavailableAssets = fetchedUnavailableAssets.length > 0
|
|
||||||
const hasAssets = hasReviewedAssets || hasAssetsInReview || hasUnavailableAssets
|
|
||||||
const shouldReview = hasAssetsInReview || hasUnavailableAssets
|
|
||||||
|
|
||||||
setItemsInBag([
|
|
||||||
...fetchedUnavailableAssets.map((unavailableAsset) => ({
|
|
||||||
asset: unavailableAsset,
|
|
||||||
status: BagItemStatus.UNAVAILABLE,
|
|
||||||
})),
|
|
||||||
...fetchedPriceChangedAssets.map((changedAsset) => ({
|
|
||||||
asset: changedAsset,
|
|
||||||
status: BagItemStatus.REVIEWING_PRICE_CHANGE,
|
|
||||||
})),
|
|
||||||
...fetchedUnchangedAssets.map((unchangedAsset) => ({
|
|
||||||
asset: unchangedAsset,
|
|
||||||
status: BagItemStatus.REVIEWED,
|
|
||||||
})),
|
|
||||||
])
|
|
||||||
|
|
||||||
let shouldLock = false
|
|
||||||
|
|
||||||
if (hasAssets) {
|
|
||||||
if (!shouldReview) {
|
|
||||||
if (shouldRefetchCalldata) {
|
|
||||||
setBagStatus(BagStatus.CONFIRM_QUOTE)
|
|
||||||
} else {
|
|
||||||
purchaseAssets(routeResponse, purchasingWithErc20)
|
|
||||||
setBagStatus(BagStatus.CONFIRMING_IN_WALLET)
|
|
||||||
shouldLock = true
|
|
||||||
}
|
|
||||||
} else if (!hasAssetsInReview) setBagStatus(BagStatus.CONFIRM_REVIEW)
|
|
||||||
else {
|
|
||||||
setBagStatus(BagStatus.IN_REVIEW)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setBagStatus(BagStatus.ADDING_TO_BAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocked(shouldLock)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const routeData = await queryClient.fetchQuery(['assetsRoute', ethSellObject, itemsToBuy, account], () =>
|
|
||||||
fetchRoute({
|
|
||||||
toSell: [ethSellObject],
|
|
||||||
toBuy: itemsToBuy,
|
|
||||||
senderAddress: account ?? '',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const { updatedAssets } = combineBuyItemsWithTxRoute(itemsToBuy, routeData.route)
|
|
||||||
|
|
||||||
const fetchedPriceChangedAssets = updatedAssets
|
|
||||||
.filter((asset) => asset.updatedPriceInfo)
|
|
||||||
.sort(sortUpdatedAssets)
|
|
||||||
const fetchedUnavailableAssets = updatedAssets.filter((asset) => asset.isUnavailable)
|
|
||||||
const fetchedUnchangedAssets = updatedAssets.filter((asset) => !asset.updatedPriceInfo && !asset.isUnavailable)
|
|
||||||
const hasReviewedAssets = fetchedUnchangedAssets.length > 0
|
|
||||||
const hasAssetsInReview = fetchedPriceChangedAssets.length > 0
|
|
||||||
const hasUnavailableAssets = fetchedUnavailableAssets.length > 0
|
|
||||||
const hasAssets = hasReviewedAssets || hasAssetsInReview || hasUnavailableAssets
|
|
||||||
const shouldReview = hasAssetsInReview || hasUnavailableAssets
|
|
||||||
|
|
||||||
setItemsInBag([
|
|
||||||
...fetchedUnavailableAssets.map((unavailableAsset) => ({
|
|
||||||
asset: unavailableAsset,
|
|
||||||
status: BagItemStatus.UNAVAILABLE,
|
|
||||||
})),
|
|
||||||
...fetchedPriceChangedAssets.map((changedAsset) => ({
|
|
||||||
asset: changedAsset,
|
|
||||||
status: BagItemStatus.REVIEWING_PRICE_CHANGE,
|
|
||||||
})),
|
|
||||||
...fetchedUnchangedAssets.map((unchangedAsset) => ({
|
|
||||||
asset: unchangedAsset,
|
|
||||||
status: BagItemStatus.REVIEWED,
|
|
||||||
})),
|
|
||||||
])
|
|
||||||
setLocked(false)
|
|
||||||
|
|
||||||
if (hasAssets) {
|
|
||||||
if (!shouldReview) {
|
|
||||||
purchaseAssets(routeData, false)
|
|
||||||
setBagStatus(BagStatus.CONFIRMING_IN_WALLET)
|
|
||||||
} else if (!hasAssetsInReview) setBagStatus(BagStatus.CONFIRM_REVIEW)
|
|
||||||
else {
|
|
||||||
setBagStatus(BagStatus.IN_REVIEW)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setBagStatus(BagStatus.ADDING_TO_BAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setBagStatus(BagStatus.ADDING_TO_BAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useSendTransaction.subscribe((state) => (transactionStateRef.current = state.state))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bagIsLocked && !isModalOpen) setModalIsOpen(true)
|
if (bagIsLocked && !isModalOpen) setModalIsOpen(true)
|
||||||
}, [bagIsLocked, isModalOpen])
|
}, [bagIsLocked, isModalOpen])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (transactionStateRef.current === TxStateType.Confirming) setBagStatus(BagStatus.PROCESSING_TRANSACTION)
|
|
||||||
if (transactionStateRef.current === TxStateType.Denied || transactionStateRef.current === TxStateType.Invalid) {
|
|
||||||
if (transactionStateRef.current === TxStateType.Invalid) setBagStatus(BagStatus.WARNING)
|
|
||||||
else setBagStatus(BagStatus.CONFIRM_REVIEW)
|
|
||||||
setTransactionState(TxStateType.New)
|
|
||||||
|
|
||||||
setLocked(false)
|
|
||||||
setModalIsOpen(false)
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [transactionStateRef.current])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTotalEthPrice(totalEthPrice)
|
|
||||||
}, [totalEthPrice, setTotalEthPrice])
|
|
||||||
|
|
||||||
const hasAssetsToShow = itemsInBag.length > 0
|
const hasAssetsToShow = itemsInBag.length > 0
|
||||||
|
|
||||||
const scrollHandler = (event: React.UIEvent<HTMLDivElement>) => {
|
const scrollHandler = (event: React.UIEvent<HTMLDivElement>) => {
|
||||||
@ -422,7 +182,7 @@ const Bag = () => {
|
|||||||
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
|
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
|
||||||
</Column>
|
</Column>
|
||||||
{hasAssetsToShow && !isProfilePage && (
|
{hasAssetsToShow && !isProfilePage && (
|
||||||
<BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} />
|
<BagFooter setModalIsOpen={setModalIsOpen} eventProperties={eventProperties} />
|
||||||
)}
|
)}
|
||||||
{isSellingAssets && isProfilePage && (
|
{isSellingAssets && isProfilePage && (
|
||||||
<ContinueButton
|
<ContinueButton
|
||||||
|
@ -20,10 +20,13 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
|||||||
import { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
|
import { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
|
||||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||||
import { useBag } from 'nft/hooks/useBag'
|
import { useBag } from 'nft/hooks/useBag'
|
||||||
|
import { useBagTotalEthPrice } from 'nft/hooks/useBagTotalEthPrice'
|
||||||
import useDerivedPayWithAnyTokenSwapInfo from 'nft/hooks/useDerivedPayWithAnyTokenSwapInfo'
|
import useDerivedPayWithAnyTokenSwapInfo from 'nft/hooks/useDerivedPayWithAnyTokenSwapInfo'
|
||||||
|
import { useFetchAssets } from 'nft/hooks/useFetchAssets'
|
||||||
import usePayWithAnyTokenSwap from 'nft/hooks/usePayWithAnyTokenSwap'
|
import usePayWithAnyTokenSwap from 'nft/hooks/usePayWithAnyTokenSwap'
|
||||||
import usePermit2Approval from 'nft/hooks/usePermit2Approval'
|
import usePermit2Approval from 'nft/hooks/usePermit2Approval'
|
||||||
import { PriceImpact, usePriceImpact } from 'nft/hooks/usePriceImpact'
|
import { PriceImpact, usePriceImpact } from 'nft/hooks/usePriceImpact'
|
||||||
|
import { useSubscribeTransactionState } from 'nft/hooks/useSubscribeTransactionState'
|
||||||
import { useTokenInput } from 'nft/hooks/useTokenInput'
|
import { useTokenInput } from 'nft/hooks/useTokenInput'
|
||||||
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
|
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
|
||||||
import { BagStatus } from 'nft/types'
|
import { BagStatus } from 'nft/types'
|
||||||
@ -272,8 +275,7 @@ const FiatValue = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BagFooterProps {
|
interface BagFooterProps {
|
||||||
totalEthPrice: BigNumber
|
setModalIsOpen: (open: boolean) => void
|
||||||
fetchAssets: () => void
|
|
||||||
eventProperties: Record<string, unknown>
|
eventProperties: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,11 +286,12 @@ const PENDING_BAG_STATUSES = [
|
|||||||
BagStatus.PROCESSING_TRANSACTION,
|
BagStatus.PROCESSING_TRANSACTION,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFooterProps) => {
|
export const BagFooter = ({ setModalIsOpen, eventProperties }: BagFooterProps) => {
|
||||||
const toggleWalletModal = useToggleWalletModal()
|
const toggleWalletModal = useToggleWalletModal()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { account, chainId, connector } = useWeb3React()
|
const { account, chainId, connector } = useWeb3React()
|
||||||
const connected = Boolean(account && chainId)
|
const connected = Boolean(account && chainId)
|
||||||
|
const totalEthPrice = useBagTotalEthPrice()
|
||||||
const shouldUsePayWithAnyToken = usePayWithAnyTokenEnabled()
|
const shouldUsePayWithAnyToken = usePayWithAnyTokenEnabled()
|
||||||
const inputCurrency = useTokenInput((state) => state.inputCurrency)
|
const inputCurrency = useTokenInput((state) => state.inputCurrency)
|
||||||
const setInputCurrency = useTokenInput((state) => state.setInputCurrency)
|
const setInputCurrency = useTokenInput((state) => state.setInputCurrency)
|
||||||
@ -297,7 +300,6 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
|
|||||||
account ?? undefined,
|
account ?? undefined,
|
||||||
!!inputCurrency && inputCurrency.isToken ? inputCurrency : undefined
|
!!inputCurrency && inputCurrency.isToken ? inputCurrency : undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLocked: bagIsLocked,
|
isLocked: bagIsLocked,
|
||||||
bagStatus,
|
bagStatus,
|
||||||
@ -312,13 +314,14 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
|
|||||||
}),
|
}),
|
||||||
shallow
|
shallow
|
||||||
)
|
)
|
||||||
|
|
||||||
const [tokenSelectorOpen, setTokenSelectorOpen] = useState(false)
|
const [tokenSelectorOpen, setTokenSelectorOpen] = useState(false)
|
||||||
|
|
||||||
const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
|
const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
|
||||||
const activeCurrency = inputCurrency ?? defaultCurrency
|
const activeCurrency = inputCurrency ?? defaultCurrency
|
||||||
const usingPayWithAnyToken = !!inputCurrency && shouldUsePayWithAnyToken && chainId === SupportedChainId.MAINNET
|
const usingPayWithAnyToken = !!inputCurrency && shouldUsePayWithAnyToken && chainId === SupportedChainId.MAINNET
|
||||||
|
|
||||||
|
useSubscribeTransactionState(setModalIsOpen)
|
||||||
|
const fetchAssets = useFetchAssets()
|
||||||
|
|
||||||
const parsedOutputAmount = useMemo(() => {
|
const parsedOutputAmount = useMemo(() => {
|
||||||
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
|
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
|
||||||
}, [defaultCurrency, totalEthPrice])
|
}, [defaultCurrency, totalEthPrice])
|
||||||
@ -374,7 +377,7 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
|
|||||||
handleClick,
|
handleClick,
|
||||||
buttonColor,
|
buttonColor,
|
||||||
} = useMemo(() => {
|
} = useMemo(() => {
|
||||||
let handleClick = fetchAssets
|
let handleClick: (() => void) | (() => Promise<void>) = fetchAssets
|
||||||
let buttonText = <Trans>Something went wrong</Trans>
|
let buttonText = <Trans>Something went wrong</Trans>
|
||||||
let disabled = true
|
let disabled = true
|
||||||
let warningText = undefined
|
let warningText = undefined
|
||||||
|
@ -2,14 +2,15 @@ import { Box } from 'nft/components/Box'
|
|||||||
import { Column, Row } from 'nft/components/Flex'
|
import { Column, Row } from 'nft/components/Flex'
|
||||||
import { body, bodySmall } from 'nft/css/common.css'
|
import { body, bodySmall } from 'nft/css/common.css'
|
||||||
import { useBag } from 'nft/hooks'
|
import { useBag } from 'nft/hooks'
|
||||||
|
import { useBagTotalEthPrice, useBagTotalUsdPrice } from 'nft/hooks/useBagTotalEthPrice'
|
||||||
import { ethNumberStandardFormatter, formatWeiToDecimal, roundAndPluralize } from 'nft/utils'
|
import { ethNumberStandardFormatter, formatWeiToDecimal, roundAndPluralize } from 'nft/utils'
|
||||||
|
|
||||||
import * as styles from './MobileHoverBag.css'
|
import * as styles from './MobileHoverBag.css'
|
||||||
export const MobileHoverBag = () => {
|
export const MobileHoverBag = () => {
|
||||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||||
const toggleBag = useBag((state) => state.toggleBag)
|
const toggleBag = useBag((state) => state.toggleBag)
|
||||||
const totalEthPrice = useBag((state) => state.totalEthPrice)
|
const totalEthPrice = useBagTotalEthPrice()
|
||||||
const totalUsdPrice = useBag((state) => state.totalUsdPrice)
|
const totalUsdPrice = useBagTotalUsdPrice()
|
||||||
|
|
||||||
const shouldShowBag = itemsInBag.length > 0
|
const shouldShowBag = itemsInBag.length > 0
|
||||||
|
|
||||||
@ -47,11 +48,10 @@ export const MobileHoverBag = () => {
|
|||||||
{roundAndPluralize(itemsInBag.length, 'NFT')}
|
{roundAndPluralize(itemsInBag.length, 'NFT')}
|
||||||
</Box>
|
</Box>
|
||||||
<Row gap="8">
|
<Row gap="8">
|
||||||
<Box className={body}>{`${formatWeiToDecimal(totalEthPrice.toString())}`}</Box>
|
<Box className={body}>{`${formatWeiToDecimal(totalEthPrice.toString())}`} ETH</Box>
|
||||||
<Box color="textSecondary" className={bodySmall}>{`${ethNumberStandardFormatter(
|
<Box color="textSecondary" className={bodySmall}>
|
||||||
totalUsdPrice,
|
{ethNumberStandardFormatter(totalUsdPrice, true)}
|
||||||
true
|
</Box>
|
||||||
)}`}</Box>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { BigNumber } from '@ethersproject/bignumber'
|
|
||||||
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
|
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
import { BagItem, BagItemStatus, BagStatus, UpdatedGenieAsset } from 'nft/types'
|
import { BagItem, BagItemStatus, BagStatus, UpdatedGenieAsset } from 'nft/types'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@ -12,10 +11,6 @@ interface BagState {
|
|||||||
setBagStatus: (state: BagStatus) => void
|
setBagStatus: (state: BagStatus) => void
|
||||||
itemsInBag: BagItem[]
|
itemsInBag: BagItem[]
|
||||||
setItemsInBag: (items: BagItem[]) => void
|
setItemsInBag: (items: BagItem[]) => void
|
||||||
totalEthPrice: BigNumber
|
|
||||||
setTotalEthPrice: (totalEthPrice: BigNumber) => void
|
|
||||||
totalUsdPrice: number | undefined
|
|
||||||
setTotalUsdPrice: (totalUsdPrice: number | undefined) => void
|
|
||||||
addAssetsToBag: (asset: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
addAssetsToBag: (asset: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
||||||
removeAssetsFromBag: (assets: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
removeAssetsFromBag: (assets: UpdatedGenieAsset[], fromSweep?: boolean) => void
|
||||||
markAssetAsReviewed: (asset: UpdatedGenieAsset, toKeep: boolean) => void
|
markAssetAsReviewed: (asset: UpdatedGenieAsset, toKeep: boolean) => void
|
||||||
@ -72,16 +67,6 @@ export const useBag = create<BagState>()(
|
|||||||
set(() => ({
|
set(() => ({
|
||||||
itemsInBag: items,
|
itemsInBag: items,
|
||||||
})),
|
})),
|
||||||
totalEthPrice: BigNumber.from(0),
|
|
||||||
setTotalEthPrice: (totalEthPrice) =>
|
|
||||||
set(() => ({
|
|
||||||
totalEthPrice,
|
|
||||||
})),
|
|
||||||
totalUsdPrice: undefined,
|
|
||||||
setTotalUsdPrice: (totalUsdPrice) =>
|
|
||||||
set(() => ({
|
|
||||||
totalUsdPrice,
|
|
||||||
})),
|
|
||||||
addAssetsToBag: (assets, fromSweep = false) =>
|
addAssetsToBag: (assets, fromSweep = false) =>
|
||||||
set(({ itemsInBag }) => {
|
set(({ itemsInBag }) => {
|
||||||
if (get().isLocked) return { itemsInBag: get().itemsInBag }
|
if (get().isLocked) return { itemsInBag: get().itemsInBag }
|
||||||
|
44
src/nft/hooks/useBagTotalEthPrice.ts
Normal file
44
src/nft/hooks/useBagTotalEthPrice.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
|
import { formatEther } from '@ethersproject/units'
|
||||||
|
import { useCurrency } from 'hooks/Tokens'
|
||||||
|
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||||
|
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||||
|
import { BagItemStatus } from 'nft/types'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import { useBag } from './useBag'
|
||||||
|
|
||||||
|
export function useBagTotalEthPrice(): BigNumber {
|
||||||
|
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const totalEthPrice = itemsInBag.reduce(
|
||||||
|
(total, item) =>
|
||||||
|
item.status !== BagItemStatus.UNAVAILABLE
|
||||||
|
? total.add(
|
||||||
|
BigNumber.from(
|
||||||
|
item.asset.updatedPriceInfo ? item.asset.updatedPriceInfo.ETHPrice : item.asset.priceInfo.ETHPrice
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: total,
|
||||||
|
BigNumber.from(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
return totalEthPrice
|
||||||
|
}, [itemsInBag])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBagTotalUsdPrice(): string | undefined {
|
||||||
|
const totalEthPrice = useBagTotalEthPrice()
|
||||||
|
const defaultCurrency = useCurrency('ETH')
|
||||||
|
|
||||||
|
const parsedOutputAmount = useMemo(() => {
|
||||||
|
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
|
||||||
|
}, [defaultCurrency, totalEthPrice])
|
||||||
|
|
||||||
|
const usdcValue = useStablecoinValue(parsedOutputAmount)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return usdcValue?.toExact()
|
||||||
|
}, [usdcValue])
|
||||||
|
}
|
102
src/nft/hooks/useFetchAssets.ts
Normal file
102
src/nft/hooks/useFetchAssets.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||||
|
import { BagStatus } from 'nft/types'
|
||||||
|
import { buildNftTradeInputFromBagItems, recalculateBagUsingPooledAssets } from 'nft/utils'
|
||||||
|
import { getNextBagState, getPurchasableAssets } from 'nft/utils/bag'
|
||||||
|
import { buildRouteResponse } from 'nft/utils/nftRoute'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import { shallow } from 'zustand/shallow'
|
||||||
|
|
||||||
|
import { useBag } from './useBag'
|
||||||
|
import { usePurchaseAssets } from './usePurchaseAssets'
|
||||||
|
import { useTokenInput } from './useTokenInput'
|
||||||
|
|
||||||
|
export function useFetchAssets(): () => Promise<void> {
|
||||||
|
const { account } = useWeb3React()
|
||||||
|
|
||||||
|
const {
|
||||||
|
itemsInBag: uncheckedItemsInBag,
|
||||||
|
setBagStatus,
|
||||||
|
didOpenUnavailableAssets,
|
||||||
|
setDidOpenUnavailableAssets,
|
||||||
|
isLocked: bagIsLocked,
|
||||||
|
setLocked: setBagLocked,
|
||||||
|
setItemsInBag,
|
||||||
|
} = useBag(
|
||||||
|
({
|
||||||
|
itemsInBag,
|
||||||
|
setBagStatus,
|
||||||
|
didOpenUnavailableAssets,
|
||||||
|
setDidOpenUnavailableAssets,
|
||||||
|
isLocked,
|
||||||
|
setLocked,
|
||||||
|
setItemsInBag,
|
||||||
|
}) => ({
|
||||||
|
itemsInBag,
|
||||||
|
setBagStatus,
|
||||||
|
didOpenUnavailableAssets,
|
||||||
|
setDidOpenUnavailableAssets,
|
||||||
|
isLocked,
|
||||||
|
setLocked,
|
||||||
|
setItemsInBag,
|
||||||
|
}),
|
||||||
|
shallow
|
||||||
|
)
|
||||||
|
const tokenTradeInput = useTokenInput((state) => state.tokenTradeInput)
|
||||||
|
const itemsInBag = useMemo(() => recalculateBagUsingPooledAssets(uncheckedItemsInBag), [uncheckedItemsInBag])
|
||||||
|
|
||||||
|
const [fetchGqlRoute] = useNftRouteLazyQuery()
|
||||||
|
const purchaseAssets = usePurchaseAssets()
|
||||||
|
|
||||||
|
const resetStateBeforeFetch = useCallback(() => {
|
||||||
|
didOpenUnavailableAssets && setDidOpenUnavailableAssets(false)
|
||||||
|
!bagIsLocked && setBagLocked(true)
|
||||||
|
setBagStatus(BagStatus.FETCHING_ROUTE)
|
||||||
|
}, [bagIsLocked, didOpenUnavailableAssets, setBagLocked, setBagStatus, setDidOpenUnavailableAssets])
|
||||||
|
|
||||||
|
return useCallback(async () => {
|
||||||
|
resetStateBeforeFetch()
|
||||||
|
|
||||||
|
fetchGqlRoute({
|
||||||
|
variables: {
|
||||||
|
senderAddress: account ? account : '',
|
||||||
|
nftTrades: buildNftTradeInputFromBagItems(itemsInBag),
|
||||||
|
tokenTrades: tokenTradeInput ? tokenTradeInput : undefined,
|
||||||
|
},
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (!data.nftRoute || !data.nftRoute.route) {
|
||||||
|
setBagStatus(BagStatus.ADDING_TO_BAG)
|
||||||
|
setBagLocked(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const wishAssetsToBuy = getPurchasableAssets(itemsInBag)
|
||||||
|
const purchasingWithErc20 = !!tokenTradeInput
|
||||||
|
const { route, routeResponse } = buildRouteResponse(data.nftRoute, purchasingWithErc20)
|
||||||
|
|
||||||
|
const { newBagItems, nextBagStatus } = getNextBagState(wishAssetsToBuy, route, purchasingWithErc20)
|
||||||
|
|
||||||
|
setItemsInBag(newBagItems)
|
||||||
|
setBagStatus(nextBagStatus)
|
||||||
|
|
||||||
|
if (nextBagStatus === BagStatus.CONFIRMING_IN_WALLET) {
|
||||||
|
purchaseAssets(routeResponse, wishAssetsToBuy, purchasingWithErc20)
|
||||||
|
setBagLocked(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setBagLocked(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [
|
||||||
|
account,
|
||||||
|
fetchGqlRoute,
|
||||||
|
itemsInBag,
|
||||||
|
purchaseAssets,
|
||||||
|
resetStateBeforeFetch,
|
||||||
|
setBagLocked,
|
||||||
|
setBagStatus,
|
||||||
|
setItemsInBag,
|
||||||
|
tokenTradeInput,
|
||||||
|
])
|
||||||
|
}
|
52
src/nft/hooks/usePurchaseAssets.ts
Normal file
52
src/nft/hooks/usePurchaseAssets.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { RouteResponse, UpdatedGenieAsset } from 'nft/types'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import shallow from 'zustand/shallow'
|
||||||
|
|
||||||
|
import { useBag } from './useBag'
|
||||||
|
import { useSendTransaction } from './useSendTransaction'
|
||||||
|
import { useTransactionResponse } from './useTransactionResponse'
|
||||||
|
|
||||||
|
export function usePurchaseAssets(): (
|
||||||
|
routingData: RouteResponse,
|
||||||
|
assetsToBuy: UpdatedGenieAsset[],
|
||||||
|
purchasingWithErc20?: boolean
|
||||||
|
) => Promise<void> {
|
||||||
|
const { provider } = useWeb3React()
|
||||||
|
const sendTransaction = useSendTransaction((state) => state.sendTransaction)
|
||||||
|
const setTransactionResponse = useTransactionResponse((state) => state.setTransactionResponse)
|
||||||
|
|
||||||
|
const {
|
||||||
|
setLocked: setBagLocked,
|
||||||
|
setBagExpanded,
|
||||||
|
reset: resetBag,
|
||||||
|
} = useBag(
|
||||||
|
({ setLocked, setBagExpanded, reset }) => ({
|
||||||
|
setLocked,
|
||||||
|
setBagExpanded,
|
||||||
|
reset,
|
||||||
|
}),
|
||||||
|
shallow
|
||||||
|
)
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async (routingData: RouteResponse, assetsToBuy: UpdatedGenieAsset[], purchasingWithErc20 = false) => {
|
||||||
|
if (!provider) return
|
||||||
|
|
||||||
|
const purchaseResponse = await sendTransaction(
|
||||||
|
provider.getSigner(),
|
||||||
|
assetsToBuy,
|
||||||
|
routingData,
|
||||||
|
purchasingWithErc20
|
||||||
|
)
|
||||||
|
|
||||||
|
if (purchaseResponse) {
|
||||||
|
setBagLocked(false)
|
||||||
|
setTransactionResponse(purchaseResponse)
|
||||||
|
setBagExpanded({ bagExpanded: false })
|
||||||
|
resetBag()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[provider, resetBag, sendTransaction, setBagExpanded, setBagLocked, setTransactionResponse]
|
||||||
|
)
|
||||||
|
}
|
@ -12,7 +12,7 @@ import ERC721 from '../../abis/erc721.json'
|
|||||||
import ERC1155 from '../../abis/erc1155.json'
|
import ERC1155 from '../../abis/erc1155.json'
|
||||||
import CryptoPunksMarket from '../abis/CryptoPunksMarket.json'
|
import CryptoPunksMarket from '../abis/CryptoPunksMarket.json'
|
||||||
import { GenieAsset, RouteResponse, RoutingItem, TxResponse, TxStateType, UpdatedGenieAsset } from '../types'
|
import { GenieAsset, RouteResponse, RoutingItem, TxResponse, TxStateType, UpdatedGenieAsset } from '../types'
|
||||||
import { combineBuyItemsWithTxRoute } from '../utils/txRoute/combineItemsWithTxRoute'
|
import { compareAssetsWithTransactionRoute } from '../utils/txRoute/combineItemsWithTxRoute'
|
||||||
|
|
||||||
interface TxState {
|
interface TxState {
|
||||||
state: TxStateType
|
state: TxStateType
|
||||||
@ -147,7 +147,7 @@ const findNFTsPurchased = (
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return combineBuyItemsWithTxRoute(transferredItems, txRoute).updatedAssets
|
return compareAssetsWithTransactionRoute(transferredItems, txRoute).updatedAssets
|
||||||
}
|
}
|
||||||
|
|
||||||
const findNFTsNotPurchased = (toBuy: GenieAsset[], nftsPurchased: UpdatedGenieAsset[]) => {
|
const findNFTsNotPurchased = (toBuy: GenieAsset[], nftsPurchased: UpdatedGenieAsset[]) => {
|
||||||
|
38
src/nft/hooks/useSubscribeTransactionState.ts
Normal file
38
src/nft/hooks/useSubscribeTransactionState.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { BagStatus, TxStateType } from 'nft/types'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { shallow } from 'zustand/shallow'
|
||||||
|
|
||||||
|
import { useBag } from './useBag'
|
||||||
|
import { useSendTransaction } from './useSendTransaction'
|
||||||
|
|
||||||
|
export function useSubscribeTransactionState(setModalIsOpen: (isOpen: boolean) => void) {
|
||||||
|
const transactionState = useSendTransaction((state) => state.state)
|
||||||
|
const setTransactionState = useSendTransaction((state) => state.setState)
|
||||||
|
const transactionStateRef = useRef(transactionState)
|
||||||
|
const { setBagStatus, setLocked: setBagLocked } = useBag(
|
||||||
|
({ setBagExpanded, setBagStatus, setLocked }) => ({
|
||||||
|
setBagExpanded,
|
||||||
|
setBagStatus,
|
||||||
|
setLocked,
|
||||||
|
}),
|
||||||
|
shallow
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
useSendTransaction.subscribe((state) => (transactionStateRef.current = state.state))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (transactionStateRef.current === TxStateType.Confirming) setBagStatus(BagStatus.PROCESSING_TRANSACTION)
|
||||||
|
if (transactionStateRef.current === TxStateType.Denied || transactionStateRef.current === TxStateType.Invalid) {
|
||||||
|
if (transactionStateRef.current === TxStateType.Invalid) {
|
||||||
|
setBagStatus(BagStatus.WARNING)
|
||||||
|
} else setBagStatus(BagStatus.CONFIRM_REVIEW)
|
||||||
|
setTransactionState(TxStateType.New)
|
||||||
|
|
||||||
|
setBagLocked(false)
|
||||||
|
setModalIsOpen(false)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [setBagLocked, setBagStatus, setModalIsOpen, setTransactionState, transactionStateRef.current])
|
||||||
|
}
|
75
src/nft/utils/bag.ts
Normal file
75
src/nft/utils/bag.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { BagItem, BagItemStatus, BagStatus, RoutingItem, UpdatedGenieAsset } from 'nft/types'
|
||||||
|
|
||||||
|
import { compareAssetsWithTransactionRoute } from './txRoute/combineItemsWithTxRoute'
|
||||||
|
import { filterUpdatedAssetsByState } from './updatedAssets'
|
||||||
|
|
||||||
|
export function getPurchasableAssets(itemsInBag: BagItem[]): UpdatedGenieAsset[] {
|
||||||
|
return itemsInBag.filter((item) => item.status !== BagItemStatus.UNAVAILABLE).map((item) => item.asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBagFromUpdatedAssets(
|
||||||
|
unavailable: UpdatedGenieAsset[],
|
||||||
|
priceChanged: UpdatedGenieAsset[],
|
||||||
|
unchanged: UpdatedGenieAsset[]
|
||||||
|
): BagItem[] {
|
||||||
|
return [
|
||||||
|
...unavailable.map((unavailableAsset) => ({
|
||||||
|
asset: unavailableAsset,
|
||||||
|
status: BagItemStatus.UNAVAILABLE,
|
||||||
|
})),
|
||||||
|
...priceChanged.map((changedAsset) => ({
|
||||||
|
asset: changedAsset,
|
||||||
|
status: BagItemStatus.REVIEWING_PRICE_CHANGE,
|
||||||
|
})),
|
||||||
|
...unchanged.map((unchangedAsset) => ({
|
||||||
|
asset: unchangedAsset,
|
||||||
|
status: BagItemStatus.REVIEWED,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateNextBagState(
|
||||||
|
hasAssets: boolean,
|
||||||
|
shouldReview: boolean,
|
||||||
|
hasAssetsInReview: boolean,
|
||||||
|
shouldRefetchCalldata: boolean
|
||||||
|
): BagStatus {
|
||||||
|
if (!hasAssets) {
|
||||||
|
return BagStatus.ADDING_TO_BAG
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldReview) {
|
||||||
|
if (hasAssetsInReview) {
|
||||||
|
return BagStatus.IN_REVIEW
|
||||||
|
}
|
||||||
|
|
||||||
|
return BagStatus.CONFIRM_REVIEW
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRefetchCalldata) {
|
||||||
|
return BagStatus.CONFIRM_QUOTE
|
||||||
|
}
|
||||||
|
|
||||||
|
return BagStatus.CONFIRMING_IN_WALLET
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextBagState(
|
||||||
|
wishAssetsToBuy: UpdatedGenieAsset[],
|
||||||
|
route: RoutingItem[],
|
||||||
|
purchasingWithErc20: boolean
|
||||||
|
): { newBagItems: BagItem[]; nextBagStatus: BagStatus } {
|
||||||
|
const { hasPriceAdjustment, updatedAssets } = compareAssetsWithTransactionRoute(wishAssetsToBuy, route)
|
||||||
|
const shouldRefetchCalldata = hasPriceAdjustment && purchasingWithErc20
|
||||||
|
|
||||||
|
const { unchanged, priceChanged, unavailable } = filterUpdatedAssetsByState(updatedAssets)
|
||||||
|
|
||||||
|
const hasAssets = updatedAssets.length > 0
|
||||||
|
const hasAssetsInReview = priceChanged.length > 0
|
||||||
|
const hasUnavailableAssets = unavailable.length > 0
|
||||||
|
const shouldReview = hasAssetsInReview || hasUnavailableAssets
|
||||||
|
|
||||||
|
const newBagItems = createBagFromUpdatedAssets(unavailable, priceChanged, unchanged)
|
||||||
|
const nextBagStatus = evaluateNextBagState(hasAssets, shouldReview, hasAssetsInReview, shouldRefetchCalldata)
|
||||||
|
|
||||||
|
return { newBagItems, nextBagStatus }
|
||||||
|
}
|
@ -74,7 +74,7 @@ const itemInRouteAndSamePool = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const combineBuyItemsWithTxRoute = (
|
export const compareAssetsWithTransactionRoute = (
|
||||||
items: UpdatedGenieAsset[],
|
items: UpdatedGenieAsset[],
|
||||||
txRoute?: RoutingItem[]
|
txRoute?: RoutingItem[]
|
||||||
): { hasPriceAdjustment: boolean; updatedAssets: UpdatedGenieAsset[] } => {
|
): { hasPriceAdjustment: boolean; updatedAssets: UpdatedGenieAsset[] } => {
|
||||||
|
@ -20,3 +20,15 @@ export const getTotalNftValue = (nfts: UpdatedGenieAsset[]): BigNumber => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterUpdatedAssetsByState(assets: UpdatedGenieAsset[]): {
|
||||||
|
unchanged: UpdatedGenieAsset[]
|
||||||
|
priceChanged: UpdatedGenieAsset[]
|
||||||
|
unavailable: UpdatedGenieAsset[]
|
||||||
|
} {
|
||||||
|
const unchanged = assets.filter((asset) => !asset.updatedPriceInfo && !asset.isUnavailable)
|
||||||
|
const priceChanged = assets.filter((asset) => asset.updatedPriceInfo).sort(sortUpdatedAssets)
|
||||||
|
const unavailable = assets.filter((asset) => asset.isUnavailable)
|
||||||
|
|
||||||
|
return { unchanged, priceChanged, unavailable }
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user