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 = ``;
+ const svg = ``;
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 }) => (
+
);
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 }) => (
-
+}: 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);