Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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 src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"withdrawal": "Withdrawal",
"claim": "Claim",
"claiming": "Claiming...",
"confirming": "Confirming...",
"withdrawAndClaim": "Withdraw & Claim",
"overview": "Overview",
"connectWallet": "Connect Wallet",
Expand Down
54 changes: 52 additions & 2 deletions src/lib/yieldxyz/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,60 @@ const TX_TITLE_PATTERNS: [RegExp, string][] = [
[/supply|deposit|enter/i, 'Deposit'],
[/withdraw|exit/i, 'Withdraw'],
[/claim/i, 'Claim'],
[/unstake/i, 'Unstake'],
[/stake/i, 'Stake'],
[/unstake|undelegate/i, 'Unstake'],
[/stake|delegate/i, 'Stake'],
[/bridge/i, 'Bridge'],
[/swap/i, 'Swap'],
]

// Map of transaction types to user-friendly button labels
// These should match the action verbs shown in the step row (without the asset symbol)
const TX_TYPE_TO_LABEL: Record<string, string> = {
APPROVE: 'Approve',
APPROVAL: 'Approve',
DELEGATE: 'Stake', // Monad uses DELEGATE for staking
UNDELEGATE: 'Unstake', // Monad uses UNDELEGATE for unstaking
STAKE: 'Stake',
UNSTAKE: 'Unstake',
DEPOSIT: 'Deposit',
WITHDRAW: 'Withdraw',
SUPPLY: 'Deposit',
EXIT: 'Withdraw',
ENTER: 'Deposit',
BRIDGE: 'Bridge',
SWAP: 'Swap',
CLAIM: 'Claim',
CLAIM_REWARDS: 'Claim',
TRANSFER: 'Transfer',
}

/**
* Gets a clean button label from a transaction type or title.
* Used for the main CTA button in the yield action modal.
*/
export const getTransactionButtonText = (
type: string | undefined,
title: string | undefined,
): string => {
// First try to use the transaction type directly
if (type) {
const normalized = type.toUpperCase().replace(/[_-]/g, '_')
if (TX_TYPE_TO_LABEL[normalized]) {
return TX_TYPE_TO_LABEL[normalized]
}
// Fallback: capitalize the type
return type.charAt(0).toUpperCase() + type.slice(1).toLowerCase()
}

// Fall back to parsing the title
if (title) {
const match = TX_TITLE_PATTERNS.find(([pattern]) => pattern.test(title))
if (match) return match[1]
}

return 'Confirm'
}

export const formatYieldTxTitle = (title: string, assetSymbol: string): string => {
const normalized = title.replace(/ transaction$/i, '').toLowerCase()
const match = TX_TITLE_PATTERNS.find(([pattern]) => pattern.test(normalized))
Expand Down
20 changes: 14 additions & 6 deletions src/pages/Yields/YieldAssetDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ export const YieldAssetDetails = memo(() => {
const setViewMode = useCallback(
(mode: 'grid' | 'list') => {
setSearchParams(prev => {
if (mode === 'grid') prev.delete('view')
else prev.set('view', mode)
return prev
const next = new URLSearchParams(prev)
if (mode === 'grid') next.delete('view')
else next.set('view', mode)
return next
})
},
[setSearchParams],
Expand Down Expand Up @@ -85,6 +86,8 @@ export const YieldAssetDetails = memo(() => {
[yields, decodedSymbol],
)

// Networks available for THIS asset - since we're on an asset-specific page,
// we show only networks that have yields for this particular asset (not all global networks)
const networks = useMemo(
() =>
Array.from(new Set(assetYields.map(y => y.network))).map(net => ({
Expand All @@ -95,6 +98,7 @@ export const YieldAssetDetails = memo(() => {
[assetYields],
)

// Providers available for THIS asset - shows only providers that offer yields for this asset
const providers = useMemo(
() =>
Array.from(new Set(assetYields.map(y => y.providerId))).map(pId => ({
Expand Down Expand Up @@ -287,6 +291,8 @@ export const YieldAssetDetails = memo(() => {
onSortingChange: setSorting,
})

const sortedRows = table.getSortedRowModel().rows

const handleYieldClick = useCallback(
(yieldId: string) => {
const balances = allBalances?.[yieldId]
Expand Down Expand Up @@ -353,7 +359,7 @@ export const YieldAssetDetails = memo(() => {
const gridViewElement = useMemo(
() => (
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>
{table.getSortedRowModel().rows.map(row => (
{sortedRows.map(row => (
<YieldItem
key={row.original.id}
data={{
Expand All @@ -375,7 +381,7 @@ export const YieldAssetDetails = memo(() => {
))}
</SimpleGrid>
),
[allBalances, getProviderLogo, handleYieldClick, table],
[allBalances, getProviderLogo, handleYieldClick, sortedRows],
)

const listViewElement = useMemo(
Expand All @@ -384,7 +390,9 @@ export const YieldAssetDetails = memo(() => {
<YieldTable table={table} isLoading={false} onRowClick={handleRowClick} />
</Box>
),
[handleRowClick, table],
// sortedRows needed to trigger re-memoization when filtered data changes (table ref is stable)
// eslint-disable-next-line react-hooks/exhaustive-deps
[handleRowClick, sortedRows, table],
)

const contentElement = useMemo(() => {
Expand Down
6 changes: 5 additions & 1 deletion src/pages/Yields/YieldDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export const YieldDetail = memo(() => {
[error, heroBg, navigate, translate],
)

const iconBg = useColorModeValue('white', 'gray.800')

const heroIcon = useMemo(() => {
if (!yieldItem) return null
const iconSource = resolveYieldInputAssetIcon(yieldItem)
Expand All @@ -111,6 +113,7 @@ export const YieldDetail = memo(() => {
border='4px solid'
borderColor={heroIconBorderColor}
borderRadius='full'
bg={iconBg}
/>
)
return (
Expand All @@ -121,9 +124,10 @@ export const YieldDetail = memo(() => {
border='4px solid'
borderColor={heroIconBorderColor}
borderRadius='full'
bg={iconBg}
/>
)
}, [heroIconBorderColor, yieldItem])
}, [heroIconBorderColor, yieldItem, iconBg])

const providerOrValidatorsElement = useMemo(() => {
if (!yieldItem) return null
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Yields/components/ValidatorBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,9 @@ export const ValidatorBreakdown = memo(
(validatorAddress: string) => (e: React.MouseEvent) => {
e.stopPropagation()
setSearchParams(prev => {
prev.set('validator', validatorAddress)
return prev
const next = new URLSearchParams(prev)
next.set('validator', validatorAddress)
return next
})
},
[setSearchParams],
Expand Down
49 changes: 44 additions & 5 deletions src/pages/Yields/components/YieldActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import { Amount } from '@/components/Amount/Amount'
import { MiddleEllipsis } from '@/components/MiddleEllipsis/MiddleEllipsis'
import { bnOrZero } from '@/lib/bignumber/bignumber'
import type { AugmentedYieldDto } from '@/lib/yieldxyz/types'
import { formatYieldTxTitle, getTransactionButtonText } from '@/lib/yieldxyz/utils'
import { GradientApy } from '@/pages/Yields/components/GradientApy'
import type { TransactionStep } from '@/pages/Yields/hooks/useYieldTransactionFlow'
import { ModalStep, useYieldTransactionFlow } from '@/pages/Yields/hooks/useYieldTransactionFlow'
import { useYieldProviders } from '@/react-queries/queries/yieldxyz/useYieldProviders'
import { useYieldValidators } from '@/react-queries/queries/yieldxyz/useYieldValidators'
Expand Down Expand Up @@ -86,10 +88,12 @@ export const YieldActionModal = memo(function YieldActionModal({
step,
transactionSteps,
isSubmitting,
activeStepIndex,
canSubmit,
handleConfirm,
handleClose,
isQuoteLoading,
quoteData,
} = useYieldTransactionFlow({
yieldItem,
action,
Expand Down Expand Up @@ -197,16 +201,31 @@ export const YieldActionModal = memo(function YieldActionModal({

const loadingText = useMemo(() => {
if (isQuoteLoading) return translate('yieldXYZ.loadingQuote')
// Use the current step's loading message if available
if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]?.loadingMessage) {
return transactionSteps[activeStepIndex].loadingMessage
}
if (action === 'enter') return translate('yieldXYZ.depositing')
if (action === 'exit') return translate('yieldXYZ.withdrawing')
return translate('common.claiming')
}, [isQuoteLoading, action, translate])
}, [isQuoteLoading, action, translate, activeStepIndex, transactionSteps])

const buttonText = useMemo(() => {
// Use the current step's type/title for a clean button label (e.g., "Delegate", "Undelegate", "Approve")
if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]) {
const step = transactionSteps[activeStepIndex]
return getTransactionButtonText(step.type, step.originalTitle)
}
// Before execution starts, use the first CREATED transaction from quoteData
const firstCreatedTx = quoteData?.transactions?.find(tx => tx.status === 'CREATED')
if (firstCreatedTx) {
return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title)
}
// Fallback to action-based text
if (action === 'enter') return translate('yieldXYZ.deposit')
if (action === 'exit') return translate('yieldXYZ.withdraw')
return translate('common.claim')
}, [action, translate])
}, [action, translate, activeStepIndex, transactionSteps, quoteData])

const modalHeading = useMemo(() => {
if (action === 'enter') return translate('yieldXYZ.supplySymbol', { symbol: assetSymbol })
Expand All @@ -227,6 +246,26 @@ export const YieldActionModal = memo(function YieldActionModal({
[feeAsset?.networkIcon, feeAsset?.icon],
)

// Show steps from quoteData before execution starts, then switch to actual transactionSteps
const displaySteps = useMemo((): TransactionStep[] => {
// If we have transactionSteps (execution has started or completed), use those
if (transactionSteps.length > 0) {
return transactionSteps
}
// Before execution, create preview steps from quoteData (filter out SKIPPED transactions)
if (quoteData?.transactions?.length) {
return quoteData.transactions
.filter(tx => tx.status === 'CREATED')
.map((tx, i) => ({
title: formatYieldTxTitle(tx.title || `Transaction ${i + 1}`, assetSymbol),
originalTitle: tx.title || '',
type: tx.type,
status: 'pending' as const,
}))
}
return []
}, [transactionSteps, quoteData, assetSymbol])

const statusCard = useMemo(
() => (
<Box
Expand Down Expand Up @@ -433,13 +472,13 @@ export const YieldActionModal = memo(function YieldActionModal({
overflow='hidden'
mt={4}
>
{transactionSteps.map((s, idx) => (
{displaySteps.map((s, idx) => (
<Flex
key={idx}
justify='space-between'
align='center'
p={4}
borderBottomWidth={idx !== transactionSteps.length - 1 ? '1px' : '0'}
borderBottomWidth={idx !== displaySteps.length - 1 ? '1px' : '0'}
borderColor='whiteAlpha.50'
bg={s.status === 'loading' ? 'whiteAlpha.50' : 'transparent'}
transition='all 0.2s'
Expand Down Expand Up @@ -514,7 +553,7 @@ export const YieldActionModal = memo(function YieldActionModal({
feeAsset,
networkAvatarSrc,
yieldItem.network,
transactionSteps,
displaySteps,
],
)

Expand Down
14 changes: 3 additions & 11 deletions src/pages/Yields/components/YieldFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ const FilterMenu = memo(({ label, value, options, onSelect, renderIcon }: Filter
)
const bg = useColorModeValue('white', 'gray.800')
const borderColor = useColorModeValue('gray.200', 'gray.700')
const selectedBg = useColorModeValue('blue.50', 'blue.900')
const selectedColor = useColorModeValue('blue.600', 'blue.200')
const selectedColor = useColorModeValue('blue.500', 'blue.300')
const hoverBg = useColorModeValue('gray.50', 'gray.750')
const activeBg = useColorModeValue('gray.100', 'gray.700')

Expand All @@ -74,7 +73,6 @@ const FilterMenu = memo(({ label, value, options, onSelect, renderIcon }: Filter
[selectedOption, renderIcon],
)

const allItemBg = useMemo(() => (value === null ? selectedBg : undefined), [value, selectedBg])
const allItemColor = useMemo(
() => (value === null ? selectedColor : undefined),
[value, selectedColor],
Expand All @@ -89,7 +87,6 @@ const FilterMenu = memo(({ label, value, options, onSelect, renderIcon }: Filter
<MenuItem
key={opt.id}
onClick={() => onSelect(opt.id)}
bg={isSelected ? selectedBg : undefined}
color={isSelected ? selectedColor : undefined}
fontWeight={isSelected ? 'semibold' : undefined}
>
Expand All @@ -100,7 +97,7 @@ const FilterMenu = memo(({ label, value, options, onSelect, renderIcon }: Filter
</MenuItem>
)
}),
[options, value, selectedBg, selectedColor, renderIcon, onSelect],
[options, value, selectedColor, renderIcon, onSelect],
)

return (
Expand All @@ -126,12 +123,7 @@ const FilterMenu = memo(({ label, value, options, onSelect, renderIcon }: Filter
</HStack>
</MenuButton>
<MenuList zIndex={10} maxH='300px' overflowY='auto'>
<MenuItem
onClick={handleSelectAll}
bg={allItemBg}
color={allItemColor}
fontWeight={allItemFontWeight}
>
<MenuItem onClick={handleSelectAll} color={allItemColor} fontWeight={allItemFontWeight}>
{label}
</MenuItem>
{menuItems}
Expand Down
Loading