Skip to content

Commit

Permalink
Merge branch 'master' into feat/bridge-input-touchup
Browse files Browse the repository at this point in the history
  • Loading branch information
bigboydiamonds committed Oct 10, 2023
2 parents f9e8f2d + 1db681a commit fc8188f
Show file tree
Hide file tree
Showing 27 changed files with 703 additions and 87 deletions.
6 changes: 3 additions & 3 deletions packages/synapse-interface/components/InteractiveInputRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import Button from '@tw/Button'
import ButtonLoadingSpinner from '@components/buttons/ButtonLoadingSpinner'
import ButtonLoadingDots from '@/components/buttons/ButtonLoadingDots'
import { getMenuItemBgForCoin } from '@styles/tokens'
import { Token } from '@types'

Expand Down Expand Up @@ -155,11 +155,11 @@ const InteractiveInputRow = ({
<>
{loadingLabel ? (
<div className="flex items-center justify-center space-x-5 animate-pulse">
<ButtonLoadingSpinner className="mr-2" />
<ButtonLoadingDots className="mr-2" />
<span>{loadingLabel}</span>
</div>
) : (
<ButtonLoadingSpinner />
<ButtonLoadingDots />
)}
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useMemo, useCallback, useState } from 'react'
import { useAppDispatch } from '@/store/hooks'
import { RootState } from '@/store/store'
import { useAccount } from 'wagmi'
import {
setFromChainId,
Expand All @@ -24,10 +23,7 @@ import { useBridgeState } from '@/slices/bridge/hooks'
import { fetchAndStoreSingleTokenAllowance } from '@/slices/portfolio/hooks'
import { AVALANCHE, ETH, ARBITRUM } from '@/constants/chains/master'
import { USDC } from '@/constants/tokens/bridgeable'

function hasOnlyZeros(input: string): boolean {
return /^0+(\.0+)?$/.test(input)
}
import { hasOnlyZeroes } from '@/utils/hasOnlyZeroes'

const handleFocusOnInput = () => {
inputRef.current.focus()
Expand Down Expand Up @@ -97,7 +93,7 @@ export const PortfolioTokenAsset = ({
decimals[portfolioChainId],
3
)
return balance > 0n && hasOnlyZeros(formattedBalance)
return balance > 0n && hasOnlyZeroes(formattedBalance)
? '< 0.001'
: formattedBalance
}, [balance, portfolioChainId])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const InputContainer = () => {
useEffect(() => {
if (
fromToken &&
fromToken.decimals[fromChainId] &&
stringToBigInt(fromValue, fromToken.decimals[fromChainId]) !== 0n
fromToken.decimals[fromChainId]
// && stringToBigInt(fromValue, fromToken.decimals[fromChainId]) !== 0n
// stringToBigInt(fromValue, fromToken.decimals[fromChainId]) ===
// fromTokenBalance
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { Address, useAccount } from 'wagmi'

import LoadingSpinner from '../ui/tailwind/LoadingSpinner'
import LoadingDots from '../ui/tailwind/LoadingDots'
import { ToChainSelector } from './ToChainSelector'
import { shortenAddress } from '@/utils/shortenAddress'
import { ToTokenSelector } from './ToTokenSelector'
Expand Down Expand Up @@ -47,7 +47,7 @@ export const OutputContainer = ({}) => {
<ToTokenSelector />
<div className="flex ml-4">
{isLoading ? (
<LoadingSpinner className="opacity-50" />
<LoadingDots className="opacity-50" />
) : (
<input
pattern="[0-9.]+"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import _ from 'lodash'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState, useMemo } from 'react'
import { useDispatch } from 'react-redux'
import { Address } from 'viem'
import Fuse from 'fuse.js'

import { useKeyPress } from '@hooks/useKeyPress'
import SlideSearchBox from '@pages/bridge/SlideSearchBox'
import { Token } from '@/utils/types'
import { setToToken } from '@/slices/bridge/reducer'
import { BridgeState, setToToken } from '@/slices/bridge/reducer'
import { setShowToTokenListOverlay } from '@/slices/bridgeDisplaySlice'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
import { useBridgeState } from '@/slices/bridge/hooks'
Expand All @@ -18,16 +19,31 @@ import { CHAINS_BY_ID } from '@/constants/chains'
import useCloseOnOutsideClick from '@/utils/hooks/useCloseOnOutsideClick'
import { CloseButton } from './components/CloseButton'
import { SearchResults } from './components/SearchResults'
import { formatBigIntToString } from '@/utils/bigint/format'
import { FetchState } from '@/slices/portfolio/actions'
import { calculateEstimatedTransactionTime } from '@/utils/calculateEstimatedTransactionTime'

interface TokenWithExchangeRate extends Token {
exchangeRate: bigint
}

export const ToTokenListOverlay = () => {
const { fromChainId, toTokens, toChainId, toToken } = useBridgeState()
const {
fromChainId,
fromToken,
toTokens,
toChainId,
toToken,
toTokensBridgeQuotes,
toTokensBridgeQuotesStatus,
}: BridgeState = useBridgeState()

const [currentIdx, setCurrentIdx] = useState(-1)
const [searchStr, setSearchStr] = useState('')
const dispatch = useDispatch()
const overlayRef = useRef(null)

let possibleTokens = sortByPriorityRank(toTokens)
let possibleTokens: Token[] = sortByPriorityRank(toTokens)

const { toTokens: allToChainTokens } = getRoutePossibilities({
fromChainId,
Expand Down Expand Up @@ -151,10 +167,71 @@ export const ToTokenListOverlay = () => {
onClose()
}

const isLoadingExchangeRate = useMemo(() => {
const hasRequiredUserInput: boolean = Boolean(
fromChainId && toChainId && fromToken && toToken
)
const isFetchLoading: boolean =
toTokensBridgeQuotesStatus === FetchState.IDLE ||
toTokensBridgeQuotesStatus === FetchState.LOADING

return hasRequiredUserInput && isFetchLoading
}, [fromChainId, toChainId, fromToken, toToken, toTokensBridgeQuotesStatus])

const bridgeQuotesMatchDestination: boolean = useMemo(() => {
return (
Array.isArray(toTokensBridgeQuotes) &&
toTokensBridgeQuotes[0]?.destinationChainId === toChainId
)
}, [toTokensBridgeQuotes, toChainId])

const orderedPossibleTokens: TokenWithExchangeRate[] | Token[] =
useMemo(() => {
if (
toTokensBridgeQuotesStatus === FetchState.VALID &&
bridgeQuotesMatchDestination &&
possibleTokens &&
possibleTokens.length > 0
) {
const bridgeQuotesMap = new Map(
toTokensBridgeQuotes.map((quote) => [quote.destinationToken, quote])
)

const tokensWithExchangeRates: TokenWithExchangeRate[] =
possibleTokens.map((token) => {
const bridgeQuote = bridgeQuotesMap.get(token)
if (bridgeQuote) {
return {
...token,
exchangeRate: bridgeQuote.exchangeRate,
}
} else {
return token as TokenWithExchangeRate
}
})

const sortedTokens = tokensWithExchangeRates.sort(
(a, b) => Number(b.exchangeRate) - Number(a.exchangeRate)
)

return sortedTokens
}
return possibleTokens
}, [
possibleTokens,
toTokensBridgeQuotes,
toTokensBridgeQuotesStatus,
bridgeQuotesMatchDestination,
])

const totalPossibleTokens: number = useMemo(() => {
return orderedPossibleTokens.length
}, [orderedPossibleTokens])

return (
<div
ref={overlayRef}
data-test-id="token-slide-over"
data-test-id="to-token-list-overlay"
className="max-h-full pb-4 mt-2 overflow-auto scrollbar-hide"
>
<div className="z-10 w-full px-2 ">
Expand All @@ -167,31 +244,50 @@ export const ToTokenListOverlay = () => {
<CloseButton onClick={onClose} />
</div>
</div>
{possibleTokens && possibleTokens.length > 0 && (
{orderedPossibleTokens && orderedPossibleTokens.length > 0 && (
<>
<div className="px-2 pt-2 pb-2 text-sm text-primaryTextColor ">
Receive…
</div>
<div className="px-2 pb-2 md:px-2">
{possibleTokens.map((token, idx) => {
return (
<SelectSpecificTokenButton
isOrigin={false}
key={idx}
token={token}
selectedToken={toToken}
active={idx === currentIdx}
showAllChains={false}
onClick={() => {
if (token === toToken) {
onClose()
} else {
handleSetToToken(toToken, token)
{orderedPossibleTokens.map(
(token: TokenWithExchangeRate, idx: number) => {
return (
<SelectSpecificTokenButton
isOrigin={false}
key={idx}
token={token}
selectedToken={toToken}
active={idx === currentIdx}
showAllChains={false}
isLoadingExchangeRate={isLoadingExchangeRate}
isBestExchangeRate={totalPossibleTokens > 1 && idx === 0}
exchangeRate={formatBigIntToString(
token?.exchangeRate,
18,
4
)}
estimatedDurationInSeconds={
toTokensBridgeQuotesStatus === FetchState.VALID &&
bridgeQuotesMatchDestination &&
calculateEstimatedTransactionTime({
originChainId: fromChainId,
originTokenAddress: fromToken.addresses[
fromChainId
] as Address,
})
}
}}
/>
)
})}
onClick={() => {
if (token === toToken) {
onClose()
} else {
handleSetToToken(toToken, token)
}
}}
/>
)
}
)}
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { usePortfolioBalances } from '@/slices/portfolio/hooks'
import { useBridgeState } from '@/slices/bridge/hooks'
import { CHAINS_BY_ID } from '@/constants/chains'
import { findChainIdsWithPausedToken } from '@/constants/tokens'
import LoadingDots from '@/components/ui/tailwind/LoadingDots'

const SelectSpecificTokenButton = ({
showAllChains,
Expand All @@ -21,6 +22,10 @@ const SelectSpecificTokenButton = ({
selectedToken,
onClick,
alternateBackground = false,
isLoadingExchangeRate = false,
exchangeRate,
isBestExchangeRate = false,
estimatedDurationInSeconds,
}: {
showAllChains?: boolean
isOrigin: boolean
Expand All @@ -29,6 +34,10 @@ const SelectSpecificTokenButton = ({
selectedToken: Token
onClick: () => void
alternateBackground?: boolean
isLoadingExchangeRate?: boolean
exchangeRate?: string
isBestExchangeRate?: boolean
estimatedDurationInSeconds?: number
}) => {
const ref = useRef<any>(null)
const isCurrentlySelected = selectedToken?.routeSymbol === token?.routeSymbol
Expand Down Expand Up @@ -56,6 +65,7 @@ const SelectSpecificTokenButton = ({

return (
<button
data-test-id="select-specific-token-button"
ref={ref}
tabIndex={active ? 1 : 0}
onClick={onClick}
Expand All @@ -78,10 +88,70 @@ const SelectSpecificTokenButton = ({
isOrigin={isOrigin}
showAllChains={showAllChains}
/>
{isLoadingExchangeRate ? (
<LoadingDots className="mr-8 opacity-50" />
) : (
<>
{exchangeRate && isBestExchangeRate && (
<OptionTag type={BestOptionType.RATE} />
)}

{exchangeRate && (
<OptionDetails
exchangeRate={exchangeRate}
estimatedDurationInSeconds={estimatedDurationInSeconds}
/>
)}
</>
)}
</button>
)
}

export enum BestOptionType {
RATE = 'Best rate',
SPEED = 'Fastest',
}

export const OptionTag = ({ type }: { type: BestOptionType }) => {
return (
<div
data-test-id="option-tag"
className="flex px-3 py-0.5 mr-3 text-sm whitespace-nowrap text-primary rounded-xl"
style={{
background:
'linear-gradient(to right, rgba(128, 0, 255, 0.2), rgba(255, 0, 191, 0.2))',
}}
>{`${type}`}</div>
)
}

export const OptionDetails = ({
exchangeRate,
estimatedDurationInSeconds,
}: {
exchangeRate: string
estimatedDurationInSeconds: number
}) => {
const estimatedDurationInMinutes: number = Math.floor(
estimatedDurationInSeconds / 60
)

return (
<div data-test-id="option-details" className="flex flex-col">
<div className="flex items-center font-normal">
<div className="flex text-sm text-secondary whitespace-nowrap">
1&nbsp;:&nbsp;
</div>
<div className="mb-[1px] text-primary">{exchangeRate}</div>
</div>
<div className="text-sm text-right text-secondary">
{estimatedDurationInMinutes} min
</div>
</div>
)
}

const ButtonContent = memo(
({
token,
Expand All @@ -101,7 +171,7 @@ const ButtonContent = memo(
)?.parsedBalance

return (
<div className="flex items-center w-full">
<div data-test-id="button-content" className="flex items-center w-full">
<img
alt="token image"
className="w-8 h-8 ml-2 mr-4 rounded-full"
Expand Down
Loading

0 comments on commit fc8188f

Please sign in to comment.