From ebe34bd98b2a8605c098d4261a3c0ab9bc06b5a8 Mon Sep 17 00:00:00 2001 From: Sandeep Prajapati <278588035+sandy-yield@users.noreply.github.com> Date: Wed, 10 Jun 2026 01:28:17 +0530 Subject: [PATCH 1/3] fix(widget): clean up icon fallbacks and pending action labels - Render a grey circle with the chain initial when a network logo is missing or fails to load, on a white badge so dark chain logos (e.g. Ethereum) stay visible. - Use a white background with black text for the token icon monogram fallback. - Humanize unmapped pending action types (e.g. RWA actions) so the position details "Action required" card, action button, and review heading never render raw translation keys. --- .../src/components/atoms/image/index.tsx | 4 +- .../src/components/atoms/token-icon/index.tsx | 1 + .../token-icon/network-icon-image/index.tsx | 66 ++++++++++++++++--- .../network-icon-image/style.css.ts | 16 ++++- .../atoms/token-icon/provider-icon/index.tsx | 1 + .../molecules/reward-token-details/index.tsx | 7 ++ .../components/position-details-actions.tsx | 8 ++- .../position-details-model.tsx | 19 ++++-- .../components/static-action-block.tsx | 9 ++- packages/widget/src/utils/formatters.ts | 10 +++ 10 files changed, 117 insertions(+), 24 deletions(-) diff --git a/packages/widget/src/components/atoms/image/index.tsx b/packages/widget/src/components/atoms/image/index.tsx index 9e9e018b..b8c9a79e 100644 --- a/packages/widget/src/components/atoms/image/index.tsx +++ b/packages/widget/src/components/atoms/image/index.tsx @@ -1,6 +1,5 @@ import type { HTMLProps } from "react"; import { useMemo } from "react"; -import { getBackgroundColor } from "../../../utils"; import type { BoxProps } from "../box"; import { Box } from "../box"; @@ -53,9 +52,8 @@ const createMonogramImageSrc = (name?: string) => { if (!firstCharacter) return undefined; const initial = escapeForSvg(firstCharacter.toUpperCase()); - const backgroundColor = getBackgroundColor(name); - const svg = `${initial}`; + const svg = `${initial}`; return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`; }; diff --git a/packages/widget/src/components/atoms/token-icon/index.tsx b/packages/widget/src/components/atoms/token-icon/index.tsx index 1c14f4bb..acc183a5 100644 --- a/packages/widget/src/components/atoms/token-icon/index.tsx +++ b/packages/widget/src/components/atoms/token-icon/index.tsx @@ -38,6 +38,7 @@ export const TokenIcon = ({ {!hideNetwork && !hideNetworkLogo && ( )} diff --git a/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx b/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx index c604dcc0..492e4fe6 100644 --- a/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx +++ b/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx @@ -1,22 +1,70 @@ +import { useState } from "react"; import type { Atoms } from "../../../../styles/theme/atoms.css"; import { Box } from "../../box"; import { Image } from "../../image"; -import { logoContainer, logoImage } from "./style.css"; +import { fallbackContainer, logoContainer, logoImage } from "./style.css"; type NetworkLogoImageProps = { networkLogoUri: string; + networkName?: string; tokenNetworkLogoHw?: Atoms["hw"]; }; export const NetworkLogoImage = ({ networkLogoUri, + networkName, tokenNetworkLogoHw = "3", -}: NetworkLogoImageProps) => ( - - - +}: NetworkLogoImageProps) => { + const [erroredUri, setErroredUri] = useState(null); + + const hasError = erroredUri === networkLogoUri; + + if (!networkLogoUri || hasError) { + const initial = networkName?.trim()?.[0]?.toUpperCase() ?? "?"; + + return ( + + + + + + ); + } + + return ( + + setErroredUri(networkLogoUri), + }} + /> + + ); +}; + +const ChainInitial = ({ initial }: { initial: string }) => ( + + + {initial} + + ); diff --git a/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts b/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts index 7e2fe1cb..8925885a 100644 --- a/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts +++ b/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts @@ -1,15 +1,25 @@ import { style } from "@vanilla-extract/css"; -export const logoContainer = style({ +const baseContainer = { position: "absolute", bottom: -2, right: -2, borderRadius: "50%", - padding: "4px", - backgroundColor: "rgba(37,37,37, 0.95)", + padding: "3px", display: "flex", alignItems: "center", justifyContent: "center", +} as const; + +export const logoContainer = style({ + ...baseContainer, + backgroundColor: "#ffffff", + boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.06)", +}); + +export const fallbackContainer = style({ + ...baseContainer, + backgroundColor: "#9ca3af", }); export const logoImage = style({ diff --git a/packages/widget/src/components/atoms/token-icon/provider-icon/index.tsx b/packages/widget/src/components/atoms/token-icon/provider-icon/index.tsx index 4ad88d01..19dcd2aa 100644 --- a/packages/widget/src/components/atoms/token-icon/provider-icon/index.tsx +++ b/packages/widget/src/components/atoms/token-icon/provider-icon/index.tsx @@ -38,6 +38,7 @@ export const ProviderIcon = ({ {!hideNetwork && !hideNetworkLogo && ( )} diff --git a/packages/widget/src/components/molecules/reward-token-details/index.tsx b/packages/widget/src/components/molecules/reward-token-details/index.tsx index 183aad2f..e4e1b333 100644 --- a/packages/widget/src/components/molecules/reward-token-details/index.tsx +++ b/packages/widget/src/components/molecules/reward-token-details/index.tsx @@ -3,6 +3,7 @@ import type { ComponentProps } from "react"; import { Trans } from "react-i18next"; import type { YieldPendingActionType } from "../../../domain/types/pending-action"; import type { useRewardTokenDetails } from "../../../hooks/use-reward-token-details"; +import { humanizePendingActionType } from "../../../utils/formatters"; import { Box } from "../../atoms/box"; import { MorphoStarsIcon } from "../../atoms/icons/morpho-stars"; import { Image } from "../../atoms/image"; @@ -35,6 +36,11 @@ export const RewardTokenDetails = ({ return "unstake_review.unstake_from"; })(); + const i18nDefaults = + rest.type === "pendingAction" + ? humanizePendingActionType(rest.pendingAction) + : undefined; + return rewardToken .map((rt) => { return ( @@ -66,6 +72,7 @@ export const RewardTokenDetails = ({ { label={t( `position_details.pending_action_button.${ val.pendingActionDto.type.toLowerCase() as Lowercase - }` + }`, + { + defaultValue: humanizePendingActionType( + val.pendingActionDto.type + ), + } )} onMaxClick={null} formattedAmount={val.formattedAmount} diff --git a/packages/widget/src/pages-dashboard/position-details/position-details-model.tsx b/packages/widget/src/pages-dashboard/position-details/position-details-model.tsx index b8c37533..1a35b55c 100644 --- a/packages/widget/src/pages-dashboard/position-details/position-details-model.tsx +++ b/packages/widget/src/pages-dashboard/position-details/position-details-model.tsx @@ -419,13 +419,7 @@ const getStatusSummary = ({ pendingAction.pendingActionDto.type === "CLAIM_REWARDS" ? "claim" : "action", - value: t( - `position_details.pending_action.${ - pendingAction.pendingActionDto.type.toLowerCase() as Lowercase< - YieldPendingActionDto["type"] - > - }` - ), + value: formatPendingActionLabel(pendingAction.pendingActionDto.type, t), }; } @@ -635,6 +629,17 @@ const balanceTypePriority: YieldBalanceType[] = [ const formatBalanceTypeLabel = (type: YieldBalanceType, t: TFunction) => t(`position_details.balance_type.${type}`); +// Pending action types come from the API and can outpace our translation map +// (e.g. RWA-specific actions). Fall back to a humanized version of the type so +// the card never renders a raw translation key. +const formatPendingActionLabel = ( + type: YieldPendingActionDto["type"], + t: TFunction +) => + t(`position_details.pending_action.${type.toLowerCase()}`, { + defaultValue: formatEnumValue(type), + }); + const formatUsdSubValue = ( value: string | number | BigNumber | null | undefined ) => { diff --git a/packages/widget/src/pages/position-details/components/static-action-block.tsx b/packages/widget/src/pages/position-details/components/static-action-block.tsx index 0aa39069..50473c80 100644 --- a/packages/widget/src/pages/position-details/components/static-action-block.tsx +++ b/packages/widget/src/pages/position-details/components/static-action-block.tsx @@ -10,6 +10,7 @@ import type { import type { YieldBalanceDto } from "../../../domain/types/positions"; import { isEthenaUsdeStaking, type Yield } from "../../../domain/types/yields"; import { defaultFormattedNumber } from "../../../utils"; +import { humanizePendingActionType } from "../../../utils/formatters"; import type { usePositionDetails } from "../hooks/use-position-details"; type StaticActionBlockProps = { @@ -60,6 +61,9 @@ export const StaticActionBlock = ({ context: isEthenaUsdeStaking(yieldId) ? "ethena_usde" : undefined, + defaultValue: humanizePendingActionType( + pendingActionDto.type + ), } ), }} @@ -93,7 +97,10 @@ export const StaticActionBlock = ({ {t( `position_details.pending_action_button.${ pendingActionDto.type.toLowerCase() as Lowercase - }` + }`, + { + defaultValue: humanizePendingActionType(pendingActionDto.type), + } )} diff --git a/packages/widget/src/utils/formatters.ts b/packages/widget/src/utils/formatters.ts index a806e4ed..f36880c2 100644 --- a/packages/widget/src/utils/formatters.ts +++ b/packages/widget/src/utils/formatters.ts @@ -136,6 +136,16 @@ export const formatCompactUsd = (value: string | number | null | undefined) => { return `$${compactUsdFormatter.format(amount.toNumber())}`; }; +// Pending action types come straight from the API and can outpace our +// translation maps (e.g. RWA-specific actions like WITHDRAWAL_REQUEST). Use this +// as the i18n `defaultValue`/`defaults` so we never render a raw translation key. +export const humanizePendingActionType = (type: string): string => + type + .split("_") + .filter(Boolean) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); + export const capitalizeFirstLetters = (text: string): string => Maybe.fromNullable(text) .map((t) => From 325829f3cfebda320c9a5db94d9f0c567f6e0227 Mon Sep 17 00:00:00 2001 From: Sandeep Prajapati <278588035+sandy-yield@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:23:11 +0530 Subject: [PATCH 2/3] refactor: reuse Image fallback for network logo + compose styles Drop the manual error state and ChainInitial SVG in NetworkLogoImage in favor of the Image component's built-in fallbackName monogram. Compose network-icon styles with the style() utility for type safety, and remove the now-unused coin icon. --- .../token-icon/network-icon-image/index.tsx | 65 +++---------------- .../network-icon-image/style.css.ts | 19 +++--- 2 files changed, 18 insertions(+), 66 deletions(-) diff --git a/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx b/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx index 492e4fe6..3f4946bf 100644 --- a/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx +++ b/packages/widget/src/components/atoms/token-icon/network-icon-image/index.tsx @@ -1,8 +1,7 @@ -import { useState } from "react"; import type { Atoms } from "../../../../styles/theme/atoms.css"; import { Box } from "../../box"; import { Image } from "../../image"; -import { fallbackContainer, logoContainer, logoImage } from "./style.css"; +import { logoContainer, logoImage } from "./style.css"; type NetworkLogoImageProps = { networkLogoUri: string; @@ -14,57 +13,13 @@ export const NetworkLogoImage = ({ networkLogoUri, networkName, tokenNetworkLogoHw = "3", -}: NetworkLogoImageProps) => { - const [erroredUri, setErroredUri] = useState(null); - - const hasError = erroredUri === networkLogoUri; - - if (!networkLogoUri || hasError) { - const initial = networkName?.trim()?.[0]?.toUpperCase() ?? "?"; - - return ( - - - - - - ); - } - - return ( - - setErroredUri(networkLogoUri), - }} - /> - - ); -}; - -const ChainInitial = ({ initial }: { initial: string }) => ( - - - {initial} - - +}: NetworkLogoImageProps) => ( + + + ); diff --git a/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts b/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts index 8925885a..0b097e61 100644 --- a/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts +++ b/packages/widget/src/components/atoms/token-icon/network-icon-image/style.css.ts @@ -1,6 +1,6 @@ import { style } from "@vanilla-extract/css"; -const baseContainer = { +const baseContainer = style({ position: "absolute", bottom: -2, right: -2, @@ -9,18 +9,15 @@ const baseContainer = { display: "flex", alignItems: "center", justifyContent: "center", -} as const; - -export const logoContainer = style({ - ...baseContainer, - backgroundColor: "#ffffff", - boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.06)", }); -export const fallbackContainer = style({ - ...baseContainer, - backgroundColor: "#9ca3af", -}); +export const logoContainer = style([ + baseContainer, + { + backgroundColor: "#ffffff", + boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.06)", + }, +]); export const logoImage = style({ maxWidth: "100%", From 4d2d70bd4e299102a33b2bc1ed8854a3957030a8 Mon Sep 17 00:00:00 2001 From: Sandeep Prajapati <278588035+sandy-yield@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:56:36 +0530 Subject: [PATCH 3/3] chore: remove unused getBackgroundColor util --- packages/widget/src/utils/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/widget/src/utils/index.ts b/packages/widget/src/utils/index.ts index 908203d7..20232712 100644 --- a/packages/widget/src/utils/index.ts +++ b/packages/widget/src/utils/index.ts @@ -43,13 +43,13 @@ export const defaultFormattedNumber = (number: string | BigNumber | number) => export const APToPercentage = (ap: number) => formatNumber((ap * 100).toFixed(2)); -const colorsTuple = ["#6B69D6", "#F1C40F", "#1ABC9C", "#E74C3C"]; +// const colorsTuple = ["#6B69D6", "#F1C40F", "#1ABC9C", "#E74C3C"]; -export const getBackgroundColor = (stringInput: string) => { - const char = stringInput.charCodeAt(0); +// export const getBackgroundColor = (stringInput: string) => { +// const char = stringInput.charCodeAt(0); - return colorsTuple[char % colorsTuple.length] ?? colorsTuple[0]; -}; +// return colorsTuple[char % colorsTuple.length] ?? colorsTuple[0]; +// }; export const isIframe = () => MaybeWindow.map((w) => w.parent !== w).orDefault(false);