Skip to content
Open
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
26 changes: 2 additions & 24 deletions apps/web/src/features/earn/components/discover/discover-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,11 @@ import { formatPct, formatUsd, formatToken } from "@/shared/lib/format"
import { useMarketPoolAmounts } from "../../hooks/useMarketPoolAmounts"
import { useGLVVaultData, useGMPoolData, useStakingInfo } from "../../queries"
import { fromSorobanAmount } from "@/shared/lib/bignum"
import { TokenIcon } from "@/shared/components/TokenIcon"

type Filter = "all" | "glv" | "gm"
type SortKey = "apy" | "tvl"



const TOKEN_COLORS: Record<string, string> = {
BTC: "bg-orange-500/10 text-orange-400 ring-orange-500/20",
ETH: "bg-indigo-500/10 text-indigo-400 ring-indigo-500/20",
XLM: "bg-sky-500/10 text-sky-400 ring-sky-500/20",
GLV: "bg-teal-500/10 text-teal-400 ring-teal-500/20",
}

function TokenAvatar({ symbol }: { symbol: string }) {
const color = TOKEN_COLORS[symbol] ?? "bg-muted/60 text-muted-foreground ring-border"
return (
<div
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold ring-1",
color,
)}
>
{symbol.slice(0, 2)}
</div>
)
}

function PoolCompositionBar({ longPct, shortPct }: { longPct: number; shortPct: number }) {
return (
<div className="space-y-1">
Expand Down Expand Up @@ -280,7 +258,7 @@ function DiscoverRow({
<tr className={cn("transition-colors hover:bg-muted/20", !isLast && "border-b border-border/40")}>
<td className="px-5 py-4">
<div className="flex items-center gap-3">
<TokenAvatar symbol={row.longToken} />
<TokenIcon symbol={row.longToken} size={32} />
<span className="font-medium">{row.name}</span>
</div>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatPct, formatUsd } from "@/shared/lib/format"
import { queryKeys } from "../../lib/query-keys"
import { useWalletStore } from "@/features/wallet/store/wallet-store"
import { CollateralDialog } from "./CollateralDialog"
import { TokenIcon } from "@/shared/components/TokenIcon"

type Props = {
onSelectPosition?: (position: Position) => void
Expand Down Expand Up @@ -154,6 +155,7 @@ export function PositionsList({ onSelectPosition }: Props) {
>
<td className="px-4 py-2">
<div className="flex items-center gap-1.5">
<TokenIcon symbol={p.indexToken} size={20} />
<span className="font-medium">{p.marketName}</span>
<Badge
variant="secondary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ApplyReferralCodePrompt } from "./ApplyReferralCodePrompt"
import type { TradeType } from "../../hooks/useTradeState"
import { useDebounce } from "@/shared/hooks/useDebounce"
import { useWalletStore } from "@/features/wallet/store/wallet-store"
import { TokenIcon } from "@/shared/components/TokenIcon"

export function TradePanel() {
const trade = useTradeState()
Expand Down Expand Up @@ -283,8 +284,9 @@ function TradeInputs({ trade, validationError }: { trade: ReturnType<typeof useT
onChange={(e) => setFromAmount(e.target.value)}
className="pr-16 font-mono text-sm"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs font-medium text-muted-foreground">
{fromTokenAddress}
<span className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
<TokenIcon symbol={fromTokenAddress} size={16} />
<span className="text-xs font-medium text-muted-foreground">{fromTokenAddress}</span>
</span>
</div>
{fromUsd > 0 && (
Expand All @@ -308,7 +310,10 @@ function TradeInputs({ trade, validationError }: { trade: ReturnType<typeof useT
{/* Receive / Index token label */}
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{tradeFlags.isSwap ? "Receive" : "Market"}</span>
<span className="font-medium">{toTokenAddress}/USD</span>
<span className="flex items-center gap-1.5 font-medium">
<TokenIcon symbol={toTokenAddress} size={16} />
{toTokenAddress}/USD
</span>
</div>
</div>
)
Expand Down
64 changes: 64 additions & 0 deletions apps/web/src/shared/components/TokenIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useState } from "react"
import { cn } from "@workspace/ui/lib/utils"

type Props = {
symbol: string
size?: number
className?: string
}

const CDN_BASE = "https://cdn.jsdelivr.net/gh/spothq/cryptocurrency-icons@master/svg/color"

const SYMBOL_TO_CDN: Record<string, string> = {
BTC: `${CDN_BASE}/btc.svg`,
ETH: `${CDN_BASE}/eth.svg`,
XLM: `${CDN_BASE}/xlm.svg`,
USDC: `${CDN_BASE}/usdc.svg`,
USDT: `${CDN_BASE}/usdt.svg`,
SOL: `${CDN_BASE}/sol.svg`,
BNB: `${CDN_BASE}/bnb.svg`,
}

const PLACEHOLDER_COLORS: Record<string, string> = {
BTC: "bg-orange-500/15 text-orange-400 ring-orange-500/25",
ETH: "bg-indigo-500/15 text-indigo-400 ring-indigo-500/25",
XLM: "bg-sky-500/15 text-sky-400 ring-sky-500/25",
USDC: "bg-blue-500/15 text-blue-400 ring-blue-500/25",
GLV: "bg-teal-500/15 text-teal-400 ring-teal-500/25",
}

export function TokenIcon({ symbol, size = 32, className }: Props) {
const [imgError, setImgError] = useState(false)
const upper = symbol.toUpperCase()
const cdnUrl = SYMBOL_TO_CDN[upper]
const showImg = !!cdnUrl && !imgError
const color = PLACEHOLDER_COLORS[upper] ?? "bg-muted/60 text-muted-foreground ring-border"

return (
<div
role="img"
aria-label={`${symbol} token icon`}
className={cn(
"flex shrink-0 items-center justify-center rounded-full ring-1 overflow-hidden",
!showImg && color,
className,
)}
style={{ width: size, height: size }}
>
{showImg ? (
<img
src={cdnUrl}
alt={`${symbol} icon`}
width={size}
height={size}
onError={() => setImgError(true)}
className="h-full w-full object-contain p-0.5"
/>
) : (
<span style={{ fontSize: Math.max(8, size * 0.3) }} className="font-semibold leading-none">
{upper.slice(0, 2)}
</span>
)}
</div>
)
}