Skip to content
Closed
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 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