Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/common/src/store/ui/buy-sell/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useTokenSwapForm'
export * from './useAvailableTokens'
export * from './useCurrentTokenPair'
export * from './useTokenStates'
export * from './useBuySellFlowLogic'
85 changes: 85 additions & 0 deletions packages/common/src/store/ui/buy-sell/useBuySellFlowLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useMemo } from 'react'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this hook used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuySellFlow


import { useFeatureFlag } from '~/hooks'
import { buySellMessages as messages } from '~/messages'
import { FeatureFlags } from '~/services'

import type { BuySellTab, TokenInfo, TokenPair } from './types'
import { createFallbackPair } from './utils'

/**
* Creates filtered token lists for buy/sell/convert tabs
*/
export const useBuySellTokenFilters = ({
availableTokens,
baseTokenSymbol,
quoteTokenSymbol,
hasPositiveBalance
}: {
availableTokens: TokenInfo[]
baseTokenSymbol: string
quoteTokenSymbol: string
hasPositiveBalance: (tokenAddress: string) => boolean
}) => {
const availableInputTokensForSell = useMemo(() => {
return availableTokens.filter(
(t) =>
t.symbol !== baseTokenSymbol &&
t.symbol !== 'USDC' &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to avoid hardcode

@faridsalau faridsalau Sep 24, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, USDC isn't coming from BE so we have to

hasPositiveBalance(t.address)
)
}, [availableTokens, baseTokenSymbol, hasPositiveBalance])

const availableInputTokensForConvert = useMemo(() => {
return availableTokens.filter(
(t) =>
t.symbol !== baseTokenSymbol &&
t.symbol !== quoteTokenSymbol &&
hasPositiveBalance(t.address)
)
}, [availableTokens, baseTokenSymbol, quoteTokenSymbol, hasPositiveBalance])

const availableOutputTokensForConvert = useMemo(() => {
return availableTokens.filter((t) => t.symbol !== baseTokenSymbol)
}, [availableTokens, baseTokenSymbol])

return {
availableInputTokensForSell,
availableInputTokensForConvert,
availableOutputTokensForConvert
}
}

/**
* Creates a safe token pair, falling back to AUDIO/USDC if needed
*/
export const useSafeTokenPair = (currentTokenPair: TokenPair | null) => {
return useMemo(() => {
if (currentTokenPair?.baseToken && currentTokenPair?.quoteToken) {
return currentTokenPair
}
return createFallbackPair()
}, [currentTokenPair])
}

/**
* Creates the tabs array based on feature flags
*/
export const useBuySellTabsArray = () => {
const { isEnabled: isArtistCoinsEnabled } = useFeatureFlag(
FeatureFlags.ARTIST_COINS
)

return useMemo(() => {
const baseTabs = [
{ key: 'buy' as BuySellTab, text: messages.buy },
{ key: 'sell' as BuySellTab, text: messages.sell }
]

if (isArtistCoinsEnabled) {
baseTabs.push({ key: 'convert' as BuySellTab, text: messages.convert })
}

return baseTabs
}, [isArtistCoinsEnabled])
}
37 changes: 24 additions & 13 deletions packages/mobile/src/components/buy-sell/InputTokenSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import { useDebouncedCallback } from '@audius/common/hooks'
import { buySellMessages as messages } from '@audius/common/messages'
import type { TokenInfo } from '@audius/common/store'
import { useTokenAmountFormatting } from '@audius/common/store'
import { sanitizeNumericInput } from '@audius/common/utils'
import {
sanitizeNumericInput,
formatTokenInputWithSmartDecimals
} from '@audius/common/utils'

import { Button, Flex, Text, TextInput, useTheme } from '@audius/harmony-native'

import { TokenIcon } from '../core'

import { TokenDropdownSelect } from './TokenDropdownSelect'
import { TokenSelectButton } from './TokenSelectButton'
import { TooltipInfoIcon } from './TooltipInfoIcon'

type InputTokenSectionProps = {
Expand All @@ -28,6 +31,7 @@ type InputTokenSectionProps = {
isTokenPriceLoading?: boolean
tokenPriceDecimalPlaces?: number
availableTokens?: TokenInfo[]
onTokenChange?: (token: TokenInfo) => void
}

export const InputTokenSection = ({
Expand All @@ -41,14 +45,15 @@ export const InputTokenSection = ({
placeholder = '0.00',
error,
errorMessage,
availableTokens
availableTokens,
onTokenChange
}: InputTokenSectionProps) => {
const { logoURI } = tokenInfo
const { iconSizes } = useTheme()
const iconSize = iconSizes.s
const { spacing } = useTheme()
const { symbol, isStablecoin } = tokenInfo
const [localAmount, setLocalAmount] = useState(amount || '')
const [localAmount, setLocalAmount] = useState(amount ?? '')

const { formattedAvailableBalance } = useTokenAmountFormatting({
amount,
Expand All @@ -61,9 +66,12 @@ export const InputTokenSection = ({

const displayTokenDropdown = availableTokens && availableTokens.length > 0

// Sync local state with prop changes
// Sync local state with prop changes and apply smart decimal formatting
useEffect(() => {
setLocalAmount(amount || '')
const formattedAmount = amount
? formatTokenInputWithSmartDecimals(amount)
: ''
setLocalAmount(formattedAmount)
}, [amount])

const debouncedOnAmountChange = useDebouncedCallback(
Expand All @@ -75,8 +83,9 @@ export const InputTokenSection = ({
const handleTextChange = useCallback(
(text: string) => {
const sanitizedText = sanitizeNumericInput(text)
setLocalAmount(sanitizedText)
debouncedOnAmountChange(sanitizedText)
const formattedText = formatTokenInputWithSmartDecimals(sanitizedText)
setLocalAmount(formattedText)
debouncedOnAmountChange(formattedText)
},
[debouncedOnAmountChange]
)
Expand Down Expand Up @@ -106,16 +115,18 @@ export const InputTokenSection = ({
</Flex>

<Flex row alignItems='center' gap='s'>
{displayTokenDropdown ? (
{displayTokenDropdown && onTokenChange ? (
<Flex flex={1}>
<TokenDropdownSelect
<TokenSelectButton
selectedToken={tokenInfo}
navigationRoute='BaseTokenDropdownSelect'
availableTokens={availableTokens ?? []}
onTokenChange={onTokenChange}
title={title}
/>
</Flex>
) : null}

{!displayTokenDropdown ? (
{!displayTokenDropdown || !onTokenChange ? (
<Flex flex={1}>
<TextInput
label={messages.amountInputLabel(symbol)}
Expand All @@ -142,7 +153,7 @@ export const InputTokenSection = ({
) : null}
</Flex>

{displayTokenDropdown ? (
{displayTokenDropdown && onTokenChange ? (
<Flex flex={1}>
<TextInput
label={messages.amountInputLabel(symbol)}
Expand Down
30 changes: 20 additions & 10 deletions packages/mobile/src/components/buy-sell/OutputTokenSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import React, { useCallback, useEffect, useState } from 'react'
import { useDebouncedCallback } from '@audius/common/hooks'
import { buySellMessages as messages } from '@audius/common/messages'
import type { TokenInfo } from '@audius/common/store'
import { sanitizeNumericInput } from '@audius/common/utils'
import {
sanitizeNumericInput,
formatTokenInputWithSmartDecimals
} from '@audius/common/utils'

import { Flex, Text, TextInput } from '@audius/harmony-native'

import { TokenDropdownSelect } from './TokenDropdownSelect'
import { TokenSelectButton } from './TokenSelectButton'

type OutputTokenSectionProps = {
tokenInfo: TokenInfo
Expand All @@ -30,14 +33,18 @@ export const OutputTokenSection = ({
placeholder = '0.00',
error,
onAmountChange,
availableTokens
availableTokens,
onTokenChange
}: OutputTokenSectionProps) => {
const { symbol, isStablecoin } = tokenInfo
const [localAmount, setLocalAmount] = useState(amount || '')

// Sync local state with prop changes
// Sync local state with prop changes and apply smart decimal formatting
useEffect(() => {
setLocalAmount(amount || '')
const formattedAmount = amount
? formatTokenInputWithSmartDecimals(amount)
: ''
setLocalAmount(formattedAmount)
}, [amount])

const debouncedOnAmountChange = useDebouncedCallback(
Expand All @@ -49,8 +56,9 @@ export const OutputTokenSection = ({
const handleTextChange = useCallback(
(text: string) => {
const sanitizedText = sanitizeNumericInput(text)
setLocalAmount(sanitizedText)
debouncedOnAmountChange(sanitizedText)
const formattedText = formatTokenInputWithSmartDecimals(sanitizedText)
setLocalAmount(formattedText)
debouncedOnAmountChange(formattedText)
},
[debouncedOnAmountChange]
)
Expand All @@ -76,11 +84,13 @@ export const OutputTokenSection = ({
error={error}
/>
</Flex>
{availableTokens && availableTokens.length > 0 && (
{availableTokens && availableTokens.length > 0 && onTokenChange && (
<Flex flex={1}>
<TokenDropdownSelect
<TokenSelectButton
selectedToken={tokenInfo}
navigationRoute='BaseTokenDropdownSelect'
availableTokens={availableTokens}
onTokenChange={onTokenChange}
title={messages.youReceive}
/>
</Flex>
)}
Expand Down
54 changes: 0 additions & 54 deletions packages/mobile/src/components/buy-sell/TokenDropdownSelect.tsx

This file was deleted.

Loading