From eb180fccf78929f5a018682f9b2f80c1c9126ce0 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 13 Feb 2025 10:21:50 -0600 Subject: [PATCH 1/3] feat: new quote card --- .../quotes/multichain-bridge-quote-card.tsx | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx new file mode 100644 index 000000000000..00d1263c5259 --- /dev/null +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx @@ -0,0 +1,311 @@ +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { + Text, + PopoverPosition, + IconName, + ButtonLink, + Icon, + IconSize, + AvatarNetwork, + AvatarNetworkSize, +} from '../../../components/component-library'; +import { + getBridgeQuotes, + getFromChain, + getToChain, + getValidationErrors, +} from '../../../ducks/bridge/selectors'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + formatCurrencyAmount, + formatTokenAmount, + formatEtaInMinutes, +} from '../utils/quote'; +import { + getCurrentCurrency, + getNativeCurrency, +} from '../../../ducks/metamask/metamask'; +import { useCrossChainSwapsEventTracker } from '../../../hooks/bridge/useCrossChainSwapsEventTracker'; +import { useRequestProperties } from '../../../hooks/bridge/events/useRequestProperties'; +import { useRequestMetadataProperties } from '../../../hooks/bridge/events/useRequestMetadataProperties'; +import { useQuoteProperties } from '../../../hooks/bridge/events/useQuoteProperties'; +import { MetaMetricsEventName } from '../../../../shared/constants/metametrics'; +import { + AlignItems, + BackgroundColor, + BlockSize, + IconColor, + JustifyContent, + TextColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { Row, Column, Tooltip } from '../layout'; +import { + BRIDGE_MM_FEE_RATE, + NETWORK_TO_SHORT_NETWORK_NAME_MAP, +} from '../../../../shared/constants/bridge'; +import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network'; +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; +import { TERMS_OF_USE_LINK } from '../../../../shared/constants/terms'; +import { getIntlLocale } from '../../../ducks/locale/locale'; +import { BridgeQuotesModal } from './bridge-quotes-modal'; + +interface MultichainBridgeQuoteCardProps { + destinationAddress?: string; +} + +export const MultichainBridgeQuoteCard = ({ + destinationAddress, +}: MultichainBridgeQuoteCardProps) => { + const t = useI18nContext(); + const { activeQuote } = useSelector(getBridgeQuotes); + const currency = useSelector(getCurrentCurrency); + const ticker = useSelector(getNativeCurrency); + const { isEstimatedReturnLow } = useSelector(getValidationErrors); + + const trackCrossChainSwapsEvent = useCrossChainSwapsEventTracker(); + const { quoteRequestProperties } = useRequestProperties(); + const requestMetadataProperties = useRequestMetadataProperties(); + const quoteListProperties = useQuoteProperties(); + + const fromChain = useSelector(getFromChain); + const toChain = useSelector(getToChain); + const locale = useSelector(getIntlLocale); + + const [showAllQuotes, setShowAllQuotes] = useState(false); + const [shouldShowNetworkFeesInGasToken, setShouldShowNetworkFeesInGasToken] = + useState(false); + + return ( + <> + setShowAllQuotes(false)} + /> + {activeQuote ? ( + + + + {t('bestPrice')} + + {t('howQuotesWorkExplanation', [BRIDGE_MM_FEE_RATE])} + + + + { + quoteRequestProperties && + requestMetadataProperties && + quoteListProperties && + trackCrossChainSwapsEvent({ + event: MetaMetricsEventName.AllQuotesOpened, + properties: { + ...quoteRequestProperties, + ...requestMetadataProperties, + ...quoteListProperties, + }, + }); + setShowAllQuotes(true); + }} + > + {t('moreQuotes')} + + + + + + + + + { + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + `0x${decimalToHex( + activeQuote.quote.srcChainId, + )}` as keyof typeof NETWORK_TO_SHORT_NETWORK_NAME_MAP + ] + } + + + + + { + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + `0x${decimalToHex( + activeQuote.quote.destChainId, + )}` as keyof typeof NETWORK_TO_SHORT_NETWORK_NAME_MAP + ] + } + + + {destinationAddress && ( + + {destinationAddress} + + )} + + + + + + + {shouldShowNetworkFeesInGasToken + ? `${ + activeQuote.totalNetworkFee?.valueInCurrency + ? formatTokenAmount( + locale, + activeQuote.totalNetworkFee?.amount, + ) + : undefined + } - ${ + activeQuote.totalMaxNetworkFee?.valueInCurrency + ? formatTokenAmount( + locale, + activeQuote.totalMaxNetworkFee?.amount, + ticker, + ) + : undefined + }` + : `${ + formatCurrencyAmount( + activeQuote.totalNetworkFee?.valueInCurrency, + currency, + 2, + ) ?? + formatTokenAmount( + locale, + activeQuote.totalNetworkFee?.amount, + ) + } - ${ + formatCurrencyAmount( + activeQuote.totalMaxNetworkFee?.valueInCurrency, + currency, + 2, + ) ?? + formatTokenAmount( + locale, + activeQuote.totalMaxNetworkFee?.amount, + ticker, + ) + }`} + + + setShouldShowNetworkFeesInGasToken( + !shouldShowNetworkFeesInGasToken, + ) + } + /> + + + + + + {t('bridgeTimingMinutes', [ + formatEtaInMinutes( + activeQuote.estimatedProcessingTimeInSeconds, + ), + ])} + + + + + + + {t('rateIncludesMMFee', [BRIDGE_MM_FEE_RATE])} + + + {t('bridgeTerms')} + + + + + ) : null} + + ); +}; From 999a599c5af555e5b196a775f5e0fe52089370d3 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 13 Feb 2025 10:31:26 -0600 Subject: [PATCH 2/3] feat: new quote card and story --- .../multichain-bridge-quote-card.stories.tsx | 101 ++++++++++++++++++ .../quotes/multichain-bridge-quote-card.tsx | 5 +- 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx new file mode 100644 index 000000000000..79616e27271a --- /dev/null +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; +import { MultichainBridgeQuoteCard } from './multichain-bridge-quote-card'; +import { createBridgeMockStore } from '../../../../test/jest/mock-store'; +import mockBridgeQuotesErc20Erc20 from '../../../../test/data/bridge/mock-quotes-erc20-erc20.json'; + +const storybook = { + title: 'Pages/Bridge/MultichainBridgeQuoteCard', + component: MultichainBridgeQuoteCard, +}; + +const Container = ({ children }: { children: React.ReactNode }) => ( +
{children}
+); + +export const DefaultStory = () => { + return ( + + + + ); +}; +DefaultStory.storyName = 'Default'; +DefaultStory.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const WithDestinationAddress = () => { + return ( + + + + ); +}; +WithDestinationAddress.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const WithLowEstimatedReturn = () => { + return ( + + + + ); +}; +WithLowEstimatedReturn.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export default storybook; diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx index 00d1263c5259..baf6d0848c7e 100644 --- a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx @@ -50,6 +50,7 @@ import { decimalToHex } from '../../../../shared/modules/conversion.utils'; import { TERMS_OF_USE_LINK } from '../../../../shared/constants/terms'; import { getIntlLocale } from '../../../ducks/locale/locale'; import { BridgeQuotesModal } from './bridge-quotes-modal'; +import { shortenString } from '../../../helpers/utils/util'; interface MultichainBridgeQuoteCardProps { destinationAddress?: string; @@ -126,7 +127,7 @@ export const MultichainBridgeQuoteCard = ({ - + - {destinationAddress} + {shortenString(destinationAddress)} )} From 38235afb939369f3abbbbeee1e47b57a7f04cd32 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 13 Feb 2025 13:24:27 -0600 Subject: [PATCH 3/3] chore: fix lint --- ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx index baf6d0848c7e..87767298f014 100644 --- a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx @@ -49,12 +49,12 @@ import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/ import { decimalToHex } from '../../../../shared/modules/conversion.utils'; import { TERMS_OF_USE_LINK } from '../../../../shared/constants/terms'; import { getIntlLocale } from '../../../ducks/locale/locale'; -import { BridgeQuotesModal } from './bridge-quotes-modal'; import { shortenString } from '../../../helpers/utils/util'; +import { BridgeQuotesModal } from './bridge-quotes-modal'; -interface MultichainBridgeQuoteCardProps { +type MultichainBridgeQuoteCardProps = { destinationAddress?: string; -} +}; export const MultichainBridgeQuoteCard = ({ destinationAddress,