Skip to content

Commit

Permalink
feat: holistic custom recipient address part 1 (#6062)
Browse files Browse the repository at this point in the history
* feat: holistic receive address part 1

* feat: add missing translation

* fix: ci
  • Loading branch information
gomesalexandre committed Jan 24, 2024
1 parent 8079ee0 commit fa850d1
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 2 deletions.
1 change: 1 addition & 0 deletions .env.base
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ REACT_APP_FEATURE_THORCHAIN_LENDING=true
REACT_APP_FEATURE_THORCHAIN_LP=false
REACT_APP_FEATURE_MULTI_HOP_TRADES=true
REACT_APP_FEATURE_THORCHAINSWAP_LONGTAIL=false
REACT_APP_FEATURE_HOLISTIC_RECIPIENT_ADDRESS=false

# absolute URL prefix
REACT_APP_ABSOLUTE_URL_PREFIX=https://app.shapeshift.com
Expand Down
1 change: 1 addition & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
REACT_APP_FEATURE_ARBITRUM_NOVA=true
REACT_APP_FEATURE_THORCHAIN_LP=true
REACT_APP_FEATURE_THORCHAINSWAP_LONGTAIL=true
REACT_APP_FEATURE_HOLISTIC_RECIPIENT_ADDRESS=true

# logging
REACT_APP_REDUX_WINDOW=false
Expand Down
1 change: 1 addition & 0 deletions .env.develop
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ REACT_APP_FEATURE_CHATWOOT=true
REACT_APP_FEATURE_ARBITRUM_NOVA=true
REACT_APP_FEATURE_THORCHAIN_LP=true
REACT_APP_FEATURE_THORCHAINSWAP_LONGTAIL=true
REACT_APP_FEATURE_HOLISTIC_RECIPIENT_ADDRESS=true

# mixpanel
REACT_APP_MIXPANEL_TOKEN=1c1369f6ea23a6404bac41b42817cc4b
Expand Down
4 changes: 4 additions & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,10 @@
"updatingQuote": "Updating quote...",
"intoAssetSymbolBody": "You can proceed with this trade, but transaction history for receiving %{assetSymbol} is temporarily unavailable",
"manualReceiveAddress": "Receive address",
"recipientAddress": "Recipient address",
"customRecipientAddress": "Custom recipient address",
"customRecipientAddressDescription": "Enter a custom recipient address for this trade",
"enterCustomRecipientAddress": "Enter custom recipient address",
"tooltip": {
"rate": "This is the expected rate for this trade pair.",
"noRateAvailable": "This is often due to very limited or no liquidity on the trade pair.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
import { ChevronDownIcon, ChevronUpIcon, QuestionIcon } from '@chakra-ui/icons'
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
CloseIcon,
EditIcon,
QuestionIcon,
} from '@chakra-ui/icons'
import {
Box,
Collapse,
Divider,
Flex,
IconButton,
Input,
InputGroup,
InputRightElement,
Skeleton,
Stack,
Tooltip,
useColorModeValue,
useDisclosure,
} from '@chakra-ui/react'
import type { AssetId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import type { AmountDisplayMeta, ProtocolFee, SwapSource } from '@shapeshiftoss/swapper'
import { SwapperName } from '@shapeshiftoss/swapper'
import type { PartialRecord } from '@shapeshiftoss/types'
import { type FC, memo, useCallback, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
import { Amount } from 'components/Amount/Amount'
import { HelperTooltip } from 'components/HelperTooltip/HelperTooltip'
import { useReceiveAddress } from 'components/MultiHopTrade/hooks/useReceiveAddress'
import { Row, type RowProps } from 'components/Row/Row'
import { RawText, Text } from 'components/Text'
import type { TextPropTypes } from 'components/Text/Text'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import { useFeatureFlag } from 'hooks/useFeatureFlag/useFeatureFlag'
import { useWallet } from 'hooks/useWallet/useWallet'
import { bnOrZero } from 'lib/bignumber/bignumber'
import { fromBaseUnit } from 'lib/math'
import {
THORCHAIN_LONGTAIL_STREAMING_SWAP_SOURCE,
THORCHAIN_STREAM_SWAP_SOURCE,
} from 'lib/swapper/swappers/ThorchainSwapper/constants'
import { isSome } from 'lib/utils'
import { isSome, middleEllipsis } from 'lib/utils'
import {
selectActiveQuoteAffiliateBps,
selectQuoteAffiliateFeeUserCurrency,
Expand All @@ -53,13 +70,23 @@ type ReceiveSummaryProps = {
swapSource?: SwapSource
} & RowProps

const editIcon = <EditIcon />
const checkIcon = <CheckIcon />
const closeIcon = <CloseIcon />

const shapeShiftFeeModalRowHover = { textDecoration: 'underline', cursor: 'pointer' }

const tradeFeeSourceTranslation: TextPropTypes['translation'] = [
'trade.tradeFeeSource',
{ tradeFeeSource: 'ShapeShift' },
]

// TODO(gomes): implement me
const isCustomRecipientAddress = false
const recipientAddressTranslation: TextPropTypes['translation'] = isCustomRecipientAddress
? 'trade.customRecipientAddress'
: 'trade.recipientAddress'

export const ReceiveSummary: FC<ReceiveSummaryProps> = memo(
({
symbol,
Expand Down Expand Up @@ -135,11 +162,46 @@ export const ReceiveSummary: FC<ReceiveSummaryProps> = memo(
setShowFeeModal(!showFeeModal)
}, [showFeeModal])

// Recipient address state and handlers
const [isRecipientAddressEditing, setIsRecipientAddressEditing] = useState(false)
const handleEditRecipientAddressClick = useCallback(() => {
setIsRecipientAddressEditing(true)
}, [])

const handleCancelClick = useCallback(() => {
setIsRecipientAddressEditing(false)
}, [])

const handleSaveClick = useCallback(() => {
setIsRecipientAddressEditing(false)
}, [])

const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(event => {
// TODO(gomes): dispatch here and make the input controlled with a local state value and form context so that
// typing actually does something
// dispatch(tradeInput.actions.setManualReceiveAddress(undefined))
console.log(event.target.value)
}, [])

const minAmountAfterSlippageTranslation: TextPropTypes['translation'] = useMemo(
() => ['trade.minAmountAfterSlippage', { slippage: slippageAsPercentageString }],
[slippageAsPercentageString],
)

const wallet = useWallet().state.wallet
const useReceiveAddressArgs = useMemo(
() => ({
fetchUnchainedAddress: Boolean(wallet && isLedger(wallet)),
}),
[wallet],
)
const isHolisticRecipientAddressEnabled = useFeatureFlag('HolisticRecipientAddress')

const receiveAddress = useReceiveAddress(useReceiveAddressArgs)

// This should never happen but it may
if (isHolisticRecipientAddressEnabled && !receiveAddress) return null

return (
<>
<Row fontSize='sm' fontWeight='medium' alignItems='flex-start' {...rest}>
Expand Down Expand Up @@ -289,6 +351,77 @@ export const ReceiveSummary: FC<ReceiveSummaryProps> = memo(
</Row>
</>
)}

{/* TODO(gomes): This should probably be made its own component and <ManualAddressEntry /> removed */}
{/* TODO(gomes): we can safely remove this condition when this feature goes live */}
{isHolisticRecipientAddressEnabled &&
receiveAddress &&
(isRecipientAddressEditing ? (
<InputGroup size='sm'>
<Input
value={''} // TODO: Controlled input value
onChange={handleInputChange}
autoFocus
placeholder={translate('trade.customRecipientAddressDescription')}
/>
<InputRightElement width='4.5rem'>
<Box
display='flex'
alignItems='center'
justifyContent='space-between'
width='full'
px='2'
>
<Box
as='button'
display='flex'
alignItems='center'
justifyContent='center'
borderRadius='md'
onClick={handleSaveClick}
>
{checkIcon}
</Box>
<Box
as='button'
display='flex'
alignItems='center'
justifyContent='center'
borderRadius='md'
onClick={handleCancelClick}
>
{closeIcon}
</Box>
</Box>
</InputRightElement>
</InputGroup>
) : (
<>
<Divider borderColor='border.base' />
<Row>
<Row.Label>
<Text translation={recipientAddressTranslation} />
</Row.Label>
<Row.Value whiteSpace='nowrap'>
<Stack direction='row' spacing={1} alignItems='center'>
<RawText>{middleEllipsis(receiveAddress)}</RawText>
<Tooltip
label={translate('trade.customRecipientAddressDescription')}
placement='top'
hasArrow
>
<IconButton
aria-label='Edit recipient address'
icon={editIcon}
variant='ghost'
onClick={handleEditRecipientAddressClick}
/>
</Tooltip>
</Stack>
</Row.Value>
</Row>
</>
))}
</Stack>
</Collapse>
<FeeModal isOpen={showFeeModal} onClose={handleFeeModal} />
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const validators = {
REACT_APP_FEATURE_THORCHAIN_LP: bool({ default: false }),
REACT_APP_FEATURE_MULTI_HOP_TRADES: bool({ default: false }),
REACT_APP_FEATURE_THORCHAINSWAP_LONGTAIL: bool({ default: false }),
REACT_APP_FEATURE_HOLISTIC_RECIPIENT_ADDRESS: bool({ default: false }),
}

function reporter<T>({ errors }: envalid.ReporterOptions<T>) {
Expand Down
2 changes: 2 additions & 0 deletions src/state/slices/preferencesSlice/preferencesSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type FeatureFlags = {
LedgerWallet: boolean
MultiHopTrades: boolean
ThorchainSwapLongtail: boolean
HolisticRecipientAddress: boolean
}

export type Flag = keyof FeatureFlags
Expand Down Expand Up @@ -115,6 +116,7 @@ const initialState: Preferences = {
LedgerWallet: getConfig().REACT_APP_FEATURE_LEDGER_WALLET,
MultiHopTrades: getConfig().REACT_APP_FEATURE_MULTI_HOP_TRADES,
ThorchainSwapLongtail: getConfig().REACT_APP_FEATURE_THORCHAINSWAP_LONGTAIL,
HolisticRecipientAddress: getConfig().REACT_APP_FEATURE_HOLISTIC_RECIPIENT_ADDRESS,
},
selectedLocale: simpleLocale(),
balanceThreshold: '0',
Expand Down
1 change: 1 addition & 0 deletions src/test/mocks/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const mockStore: ReduxState = {
LedgerWallet: false,
MultiHopTrades: false,
ThorchainSwapLongtail: false,
HolisticRecipientAddress: false,
},
selectedLocale: 'en',
balanceThreshold: '0',
Expand Down

0 comments on commit fa850d1

Please sign in to comment.