Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f8fc2f6
feat: display strategy names instead of asset symbols in yield cards
gomesalexandre Jan 16, 2026
fca42a0
feat: add maintenance and deprecated warnings for yield opportunities
gomesalexandre Jan 16, 2026
e7b34e7
chore: add temporary monkey patch for testing maintenance/deprecated …
gomesalexandre Jan 16, 2026
ea4338b
chore: remove temporary monkey patch for maintenance/deprecated badges
gomesalexandre Jan 16, 2026
6ebcbc0
feat: add YieldExplainers component for consistent staking info
gomesalexandre Jan 16, 2026
1555437
feat: add documentation link to yield detail page
gomesalexandre Jan 16, 2026
3784581
feat: add yield explainers to YieldForm and improve docs link
gomesalexandre Jan 16, 2026
9f1e96a
feat: add Available to Earn tab and improve yield page navigation
gomesalexandre Jan 16, 2026
ec27e2b
feat: improve yield page UX with filter fixes and code cleanup
gomesalexandre Jan 16, 2026
f90fac7
feat: swap fiat/crypto display on yield detail page
gomesalexandre Jan 16, 2026
1e247f3
feat: improve yield detail page desktop layout with two-column design
gomesalexandre Jan 16, 2026
b18f7ab
feat: fix validator mismatch bug and add New badge to Yields menu
gomesalexandre Jan 16, 2026
eb59b8d
feat: add getYieldDisplayName utility for clean yield names
gomesalexandre Jan 16, 2026
3969437
feat: improve asset/chain/protocol display with icon + label format
gomesalexandre Jan 16, 2026
6abea3f
refactor: use named functions in memo() for better debugging
gomesalexandre Jan 16, 2026
b24cbd9
feat: commit best practices skill
gomesalexandre Jan 16, 2026
2d8bb8d
chore: add project-specific style preferences to react-best-practices…
gomesalexandre Jan 16, 2026
cccc386
refactor: improve yields code quality and patterns
gomesalexandre Jan 16, 2026
fa85b17
fix: add null coalescing to prevent runtime error in sort comparator
gomesalexandre Jan 16, 2026
e872c09
fix: guard claimable section against zero-amount balances
gomesalexandre Jan 16, 2026
513d7ee
Merge remote-tracking branch 'origin/develop' into feat_yield_improve…
gomesalexandre Jan 19, 2026
fee32e6
fix: show all yield opportunities and sort by balance then APY
gomesalexandre Jan 19, 2026
8824980
fix: things
gomesalexandre Jan 19, 2026
dd48b8a
fix: standardize yield terminology and fix performance issues
gomesalexandre Jan 19, 2026
8487b2e
fix: improve yield UI validator/provider handling and i18n
gomesalexandre Jan 19, 2026
8b841ac
Merge branch 'develop' into feat_yield_improve_more
gomesalexandre Jan 19, 2026
cbe7d0e
fix: restore useMemo in YieldDetail.tsx
gomesalexandre Jan 19, 2026
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
165 changes: 165 additions & 0 deletions NEAR_IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Yield Feature Improvements Analysis

Based on exploration of the codebase, here are categorized improvements for the Yields feature.

## Quick Wins (High Impact, Low Effort)

These can be implemented with minimal code changes:

### 1. Typography Punch - APY Emphasis
**File:** `src/pages/Yields/components/YieldItem.tsx`
- Current: APY uses `GradientApy` but same font weight as other text
- Change: Increase `fontWeight` to `bold` or `semibold`, bump `fontSize` slightly
- Also applies to TVL display in `YieldOpportunityStats.tsx`

### 2. Copy Updates
**Files:** `src/assets/translations/en/main.json`, various components
- "My Positions" → "Your positions"
- "Available to Earn" → "Opportunities" or "Put your spare crypto to work"
- Update `translate()` keys

### 3. Strategy Naming - Use `metadata.name`
**File:** `src/pages/Yields/components/YieldItem.tsx` (line ~86, ~156)
- Currently: Card titles show asset name/symbol, subtitle shows provider
- Change: For single yields, show `metadata.name` (e.g., "Aave v3 USDC Lending") as primary title
- Provider becomes secondary label

### 4. Lightning Icon on Enter/Deposit Buttons
**Files:** `YieldOpportunityCard.tsx`, `YieldHero.tsx`, `YieldEnterModal.tsx`
- Add `FaBolt` icon to "Start Earning", "Deposit", "Enter" buttons
- Pattern: `<Button leftIcon={<FaBolt />}>Deposit</Button>`

### 5. Maintenance/Deprecated Warnings
**Files:** `YieldItem.tsx`, `YieldHero.tsx`
- Data exists: `metadata.underMaintenance`, `metadata.deprecated`
- Add: Warning badge/alert when these are `true`
- Use Chakra `<Alert status='warning'>` or `<Badge colorScheme='orange'>`

---

## Medium Effort Improvements

These require more component work but use existing data:

### 6. Reward Rate Components Breakdown
**Files:** `YieldHero.tsx`, `YieldStats.tsx`, new tooltip component
- Data: `rewardRate.components[]` has `{ rate, token, yieldSource, description }`
- Display: Tooltip or expandable section showing multi-token yield breakdown
- Example: "8.5% APY = 7% ETH + 1.5% COMP rewards"

### 7. Fee Structure Display
**File:** `YieldHero.tsx`, `YieldStats.tsx`
- Data: `mechanics.possibleFeeTakingMechanisms`
- `{ depositFee, managementFee, performanceFee, validatorRebates }`
- Display: Small badges/pills showing which fees apply
- Pattern: `{depositFee && <Badge>Deposit Fee</Badge>}`

### 8. Type Pills/Tags
**Files:** `YieldItem.tsx`, `YieldFilters.tsx`
- Data: `mechanics.type` (staking, lending, liquidity_pool)
- Display: Colored pill at top-left of card
- Colors: Green for lending, Blue for staking, Purple for LP

### 9. Cooldown/Lockup Period Warning
**File:** `YieldHero.tsx`, `YieldEnterModal.tsx`
- Data: `mechanics.cooldownPeriod?.seconds`
- Display: Warning text like "7-day unstaking period" before deposit
- Pattern: `<Alert status='info'>`

### 10. Entry Limits Display
**File:** `YieldEnterModal.tsx`
- Data: `mechanics.entryLimits` `{ minimum, maximum }`
- Already partially used for validation
- Make more prominent with visible text showing min/max

### 11. Documentation Links
**File:** `YieldHero.tsx`
- Data: `metadata.documentation` (URL string)
- Add: External link button/icon to strategy docs
- Pattern: `<IconButton as={Link} href={documentation} icon={<FaExternalLinkAlt />} />`

### 12. Capacity State Indicator
**Files:** `YieldItem.tsx`, `YieldHero.tsx`
- Data: `state?.capacityState` `{ current, max, remaining }`
- Display: Progress bar or "85% filled" indicator when near capacity
- Alert when `remaining` is low

---

## Larger Efforts (Significant Refactoring)

### 13. Pill-Style Filters
**File:** `src/pages/Yields/components/YieldFilters.tsx`
- Current: Standard Chakra `MenuButton` dropdowns
- Change: Rounded pill buttons with filled state when selected
- Pattern: Custom Button variant `rounded='full'` `bg={selected ? 'blue.500' : 'gray.700'}`

### 14. Featured/Handpicked Section
**Files:** `YieldsList.tsx`, new component
- No curation logic exists currently
- Options:
a. Backend flag: Add `featured` field from API
b. Frontend curation: Hardcode list of featured yield IDs
c. Smart curation: Top 3 by user's held assets weighted by APY
- Display: Separate "Handpicked" card row above main list

### 15. Card Redesign for Grid View
**File:** `YieldItem.tsx` (card variant)
- Current: Flat dark cards with dense info
- Changes:
- Add ambient glow/gradient background for high-APY cards
- Restructure: Type pill top-left, Network badge top-right
- APY as "hero stat" (larger, more prominent)
- Quick action button (lightning icon) floating bottom-right

### 16. Detail Page 2-Column Layout (Desktop)
**File:** `YieldDetail.tsx`, `YieldHero.tsx`
- Current: Centered single-column layout
- Change: Left column (info/about), Right column (actions/position)
- Mobile: Stacked view (keep current)

---

## Critical Files Reference

| Component | Path | Purpose |
|-----------|------|---------|
| YieldsList | `src/pages/Yields/components/YieldsList.tsx` | Main list with tabs, filters |
| YieldItem | `src/pages/Yields/components/YieldItem.tsx` | Card/row renderer |
| YieldHero | `src/pages/Yields/components/YieldHero.tsx` | Detail page header |
| YieldStats | `src/pages/Yields/components/YieldStats.tsx` | Stats grid (TVL, type) |
| YieldFilters | `src/pages/Yields/components/YieldFilters.tsx` | Filter dropdowns |
| YieldOpportunityStats | `src/pages/Yields/components/YieldOpportunityStats.tsx` | Top dashboard cards |
| YieldEnterModal | `src/pages/Yields/components/YieldEnterModal.tsx` | Deposit modal |
| GradientApy | `src/pages/Yields/components/GradientApy.tsx` | APY gradient text |
| Types | `src/lib/yieldxyz/types.ts` | All yield data types |
| Translations | `src/assets/translations/en/main.json` | Text strings |

---

## Available but Unused Data Fields

These fields exist in the API response but are not currently displayed:

| Field | Type | Potential Use |
|-------|------|---------------|
| `metadata.documentation` | string | External docs link |
| `metadata.underMaintenance` | boolean | Warning banner |
| `metadata.deprecated` | boolean | Hide or warn |
| `rewardRate.components[]` | array | Multi-token yield breakdown |
| `mechanics.possibleFeeTakingMechanisms` | object | Fee badges |
| `mechanics.cooldownPeriod` | object | Lockup warning |
| `mechanics.entryLimits` | object | Min/max display |
| `statistics.uniqueUsers` | number | Social proof |
| `state.capacityState` | object | Capacity indicator |
| `tags[]` | array | Filtering/categorization |

---

## Mobile Considerations

Per the proposal, mobile should retain dense list layout:
- Quick Wins (#1-5) apply to both mobile and desktop
- Pill filters (#13) can work on mobile
- Grid redesign (#15) is desktop-only
- 2-column detail (#16) is desktop-only; mobile stays stacked
18 changes: 17 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2801,7 +2801,23 @@
"initiateFailedDescription": "Failed to initiate transaction sequence.",
"quoteFailedTitle": "Quote failed",
"quoteFailedDescription": "Unable to get a quote for this transaction. Please try again."
}
},
"underMaintenance": "Under Maintenance",
Comment thread
gomesalexandre marked this conversation as resolved.
"underMaintenanceDescription": "This yield opportunity is currently under maintenance. Deposits may be unavailable.",
"deprecated": "Deprecated",
"deprecatedDescription": "This yield opportunity has been deprecated and may be discontinued soon.",
"learnMore": "Learn more",
"noAvailableYields": "No yield opportunities available for your assets",
"connectWalletAvailable": "Connect a wallet to see yields available for your assets",
"aboutProvider": "About %{provider}",
"visitWebsite": "Visit Website",
"otherYields": "Other %{symbol} Yields",
"availableToDeposit": "Available to Deposit",
"availableToDepositTooltip": "This is the amount of %{symbol} in your wallet that you can deposit into this yield opportunity.",
"potentialEarningsAmount": "%{amount}/yr at %{apy}% APY",
"depositNow": "Deposit Now",
"strategyInfo": "Strategy Info",
"overview": "Overview"
},
"earn": {
"enterFrom": "Enter from",
Expand Down
38 changes: 17 additions & 21 deletions src/components/Layout/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,16 @@ export const Header = memo(() => {
const height = useMemo(() => ref.current?.getBoundingClientRect()?.height ?? 0, [])
const { scrollY } = useScroll()

// Responsive display based on viewport width
const searchBoxDisplay = useMemo(
() => ({
base: 'none',
'2xl': 'flex',
// Hide at smaller breakpoints where it would get cramped
xl: 'none',
}),
[],
)
const searchBoxDisplay = {
base: 'none',
'2xl': 'flex',
xl: 'none',
}

const iconButtonDisplay = useMemo(
() => ({
base: 'flex',
'2xl': 'none',
}),
[],
)
const iconButtonDisplay = {
base: 'flex',
'2xl': 'none',
}

useEffect(() => {
return scrollY.onChange(() => setY(scrollY.get()))
Expand All @@ -124,12 +116,12 @@ export const Header = memo(() => {
const hasWallet = Boolean(walletInfo?.deviceId)
const earnSubMenuItems = useMemo(
() => [
...(isYieldXyzEnabled
? [{ label: 'navBar.yields', path: '/yields', icon: TbTrendingUp, isNew: true }]
: []),
{ label: 'navBar.tcy', path: '/tcy', icon: TCYIcon },
{ label: 'navBar.pools', path: '/pools', icon: TbPool },
{ label: 'navBar.lending', path: '/lending', icon: TbBuildingBank },
...(isYieldXyzEnabled
? [{ label: 'navBar.yields', path: '/yields', icon: TbTrendingUp }]
: []),
],
[isYieldXyzEnabled],
)
Expand Down Expand Up @@ -188,7 +180,11 @@ export const Header = memo(() => {
items={exploreSubMenuItems}
defaultPath='/assets'
/>
<NavigationDropdown label='defi.earn' items={earnSubMenuItems} defaultPath='/tcy' />
<NavigationDropdown
label='defi.earn'
items={earnSubMenuItems}
defaultPath={isYieldXyzEnabled ? '/yields' : '/tcy'}
Comment thread
gomesalexandre marked this conversation as resolved.
/>
<Link
as={ReactRouterLink}
to={isRfoxFoxEcosystemPageEnabled ? '/fox-ecosystem' : '/fox'}
Expand Down
18 changes: 17 additions & 1 deletion src/components/Layout/Header/NavBar/NavigationDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
Badge,
Box,
Button,
HStack,
Icon,
Menu,
MenuButton,
Expand All @@ -23,6 +25,7 @@ type NavigationDropdownItem = {
label: string
path: string
icon?: React.ComponentType
isNew?: boolean
}

type NavigationDropdownProps = {
Expand Down Expand Up @@ -134,7 +137,20 @@ export const NavigationDropdown = ({ label, items, defaultPath }: NavigationDrop
_hover={menuItemHoverSx}
icon={item.icon && <Icon as={item.icon} boxSize={6} />}
>
<Text>{translate(item.label)}</Text>
<HStack spacing={2}>
<Text>{translate(item.label)}</Text>
{item.isNew && (
<Badge
colorScheme='blue'
fontSize='2xs'
variant='solid'
borderRadius='full'
px={1.5}
>
{translate('common.new')}
</Badge>
)}
</HStack>
</MenuItem>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getTransactionButtonText } from '@/lib/yieldxyz/utils'
import { GradientApy } from '@/pages/Yields/components/GradientApy'
import { TransactionStepsList } from '@/pages/Yields/components/TransactionStepsList'
import { YieldAssetFlow } from '@/pages/Yields/components/YieldAssetFlow'
import { YieldExplainers } from '@/pages/Yields/components/YieldExplainers'
import { YieldSuccess } from '@/pages/Yields/components/YieldSuccess'
import { ModalStep, useYieldTransactionFlow } from '@/pages/Yields/hooks/useYieldTransactionFlow'
import { useYieldProviders } from '@/react-queries/queries/yieldxyz/useYieldProviders'
Expand Down Expand Up @@ -310,6 +311,12 @@ export const EarnConfirm = memo(() => {
)}
</Box>

{selectedYield && (
<Box mt={4}>
<YieldExplainers selectedYield={selectedYield} sellAssetSymbol={sellAsset?.symbol} />
</Box>
)}

{stepsToShow.length > 0 && (
<Box mt={4}>
<TransactionStepsList steps={stepsToShow} />
Expand Down
47 changes: 47 additions & 0 deletions src/lib/yieldxyz/getYieldDisplayName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest'

import { getYieldDisplayName } from './getYieldDisplayName'
import type { AugmentedYieldDto } from './types'

const mockYield = (providerId: string, tokenSymbol: string, metadataName: string) =>
({
providerId,
token: { symbol: tokenSymbol },
metadata: { name: metadataName },
}) as AugmentedYieldDto

describe('getYieldDisplayName', () => {
describe('returns token symbol for standard yields', () => {
it.each([
['aave', 'USDC', 'Aave v3 Lending'],
['fluid', 'USDT', '(PoS) Tether USD Lending Fluid Vault'],
['compound', 'WETH', 'Compound v3 Lending'],
['lido', 'stETH', 'Lido Ethereum Staking'],
['gearbox', 'USDC', 'USDC Trade USDC v3 Gearbox Vault'],
])('%s %s → %s', (providerId, symbol, metadataName) => {
expect(getYieldDisplayName(mockYield(providerId, symbol, metadataName))).toBe(symbol)
})
})

describe('returns curator name for Morpho/Yearn vaults with known curators', () => {
it.each([
['morpho', 'Steakhouse High Yield USDC Morpho Vault', 'Steakhouse High Yield'],
['morpho', 'Steakhouse Prime USDC Morpho Vault', 'Steakhouse Prime'],
['morpho', 'Gauntlet USDT Vault Morpho Vault', 'Gauntlet'],
['morpho', 'Clearstar USDC Reactor Morpho Vault', 'Clearstar'],
['morpho', 'Yearn OG USDT Morpho Vault', 'Yearn OG'],
['yearn', 'Yearn OG vbETH Compounder Yearn Vault V3', 'Yearn OG'],
])('%s "%s" → %s', (providerId, metadataName, expected) => {
expect(getYieldDisplayName(mockYield(providerId, 'TOKEN', metadataName))).toBe(expected)
})
})

describe('returns symbol when vault has no matching curator prefix', () => {
it.each([
['yearn', 'vbUSDT', 'Morpho Yearn OG USDT Compounder Yearn Vault V3'],
['yearn', 'AUSD', 'AUSD yVault Yearn Vault V3'],
])('%s %s → %s (no curator prefix)', (providerId, symbol, metadataName) => {
expect(getYieldDisplayName(mockYield(providerId, symbol, metadataName))).toBe(symbol)
})
})
})
31 changes: 31 additions & 0 deletions src/lib/yieldxyz/getYieldDisplayName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { AugmentedYieldDto } from './types'

const VAULT_CURATORS = [
'Steakhouse High Yield',
'Steakhouse Prime',
'Steakhouse',
'Gauntlet',
'Clearstar',
'Yearn OG',
'Yearn',
'Re7',
'Usual',
'Smokehouse',
]
Comment thread
gomesalexandre marked this conversation as resolved.

export const getYieldDisplayName = (yieldItem: AugmentedYieldDto): string => {
const { token, providerId, metadata } = yieldItem
const metadataName = metadata?.name ?? ''

const isVaultWithCurator =
providerId === 'morpho' ||
metadataName.includes('Morpho Vault') ||
metadataName.includes('Yearn Vault')

if (isVaultWithCurator) {
const curator = VAULT_CURATORS.find(c => metadataName.startsWith(c))
if (curator) return curator
}

return token.symbol
}
Loading