From 00b6c2e19a1367a11317de957b4fe4e48394d4e7 Mon Sep 17 00:00:00 2001 From: MananTank Date: Thu, 19 Dec 2024 14:55:40 +0000 Subject: [PATCH] [CORE-652] Add chain icon in SingleNetworkSelector + other chain icon related fixes (#5794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem solved Short description of the bug fixed or feature added --- ## PR-Codex overview This PR primarily focuses on updating the `ChainIcon` component's implementation and its usage across various files, specifically changing the way the icon's size is handled and integrating a new fallback mechanism. ### Detailed summary - Added `decoding="async"` to an `Image` component. - Replaced `size` prop with `className` for `ChainIcon` across multiple components, adjusting sizes accordingly. - Updated `ChainIcon` implementation to use a new `Img` component. - Introduced a fallback image for `ChainIcon` in case of loading failures. - Added stories for `MultiNetworkSelector` and `SingleNetworkSelector` components. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../dashboard/src/@/components/blocks/Img.tsx | 1 + .../blocks/MultiNetworkSelector.stories.tsx | 55 +++++++++++++++++ .../@/components/blocks/NetworkSelectors.tsx | 14 ++++- .../blocks/SingleNetworkSelector.stories.tsx | 59 +++++++++++++++++++ .../_layout/metadata-header.tsx | 2 +- .../(chain)/components/server/chain-icon.tsx | 9 ++- .../contract-subscriptions-table.tsx | 4 +- .../components/backend-wallets-table.tsx | 2 +- .../components/transactions-table.tsx | 4 +- .../relayers/components/relayers-table.tsx | 4 +- .../src/components/cmd-k-search/index.tsx | 3 +- .../ConfigureNetworkForm.tsx | 2 +- .../tables/deployed-contracts.tsx | 2 +- .../src/components/icons/ChainIcon.tsx | 56 ++++++------------ .../selects/CustomChainRenderer.tsx | 2 +- .../selects/NetworkSelectDropdown.tsx | 6 +- .../selects/NetworkSelectorButton.tsx | 2 +- .../settings/Account/Billing/CreditsItem.tsx | 2 +- apps/dashboard/src/utils/chain-icons.ts | 2 + 19 files changed, 170 insertions(+), 61 deletions(-) create mode 100644 apps/dashboard/src/@/components/blocks/MultiNetworkSelector.stories.tsx create mode 100644 apps/dashboard/src/@/components/blocks/SingleNetworkSelector.stories.tsx create mode 100644 apps/dashboard/src/utils/chain-icons.ts diff --git a/apps/dashboard/src/@/components/blocks/Img.tsx b/apps/dashboard/src/@/components/blocks/Img.tsx index 765d284b8b6..1ff672b760b 100644 --- a/apps/dashboard/src/@/components/blocks/Img.tsx +++ b/apps/dashboard/src/@/components/blocks/Img.tsx @@ -65,6 +65,7 @@ export function Img(props: imgElementProps) { "fade-in-0 object-cover transition-opacity duration-300", className, )} + decoding="async" /> {status !== "loaded" && ( diff --git a/apps/dashboard/src/@/components/blocks/MultiNetworkSelector.stories.tsx b/apps/dashboard/src/@/components/blocks/MultiNetworkSelector.stories.tsx new file mode 100644 index 00000000000..707469273aa --- /dev/null +++ b/apps/dashboard/src/@/components/blocks/MultiNetworkSelector.stories.tsx @@ -0,0 +1,55 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; +import { BadgeContainer, mobileViewport } from "../../../stories/utils"; +import { MultiNetworkSelector } from "./NetworkSelectors"; + +const meta = { + title: "blocks/Cards/MultiNetworkSelector", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Desktop: Story = { + args: {}, +}; + +export const Mobile: Story = { + args: {}, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +function Story() { + return ( +
+ + +
+ ); +} + +function Variant(props: { + label: string; + selectedChainIds: number[]; +}) { + const [chainIds, setChainIds] = useState(props.selectedChainIds); + return ( + + + + ); +} diff --git a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx index bb7eaa2c459..a8e8423554c 100644 --- a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx +++ b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx @@ -4,6 +4,7 @@ import { MultiSelect } from "@/components/blocks/multi-select"; import { SelectWithSearch } from "@/components/blocks/select-with-search"; import { Badge } from "@/components/ui/badge"; import { useCallback, useMemo } from "react"; +import { ChainIcon } from "../../../components/icons/ChainIcon"; import { useAllChainsData } from "../../../hooks/chains/allChains"; function cleanChainName(chainName: string) { @@ -51,7 +52,7 @@ export function MultiNetworkSelector(props: { return (
- + {cleanChainName(chain.name)} @@ -133,8 +134,15 @@ export function SingleNetworkSelector(props: { return (
- {chain.name} - + + + {chain.name} + + Chain ID {chain.chainId} diff --git a/apps/dashboard/src/@/components/blocks/SingleNetworkSelector.stories.tsx b/apps/dashboard/src/@/components/blocks/SingleNetworkSelector.stories.tsx new file mode 100644 index 00000000000..8526e1eadaf --- /dev/null +++ b/apps/dashboard/src/@/components/blocks/SingleNetworkSelector.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; +import { BadgeContainer, mobileViewport } from "../../../stories/utils"; +import { SingleNetworkSelector } from "./NetworkSelectors"; + +const meta = { + title: "blocks/Cards/SingleNetworkSelector", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Desktop: Story = { + args: {}, +}; + +export const Mobile: Story = { + args: {}, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +function Story() { + return ( +
+ + + +
+ ); +} + +function Variant(props: { + label: string; + chainId: number | undefined; + chainIds?: number[]; +}) { + const [chainId, setChainId] = useState(props.chainId); + return ( + + + + ); +} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx index f44b2c25a78..b82e2f7812b 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx @@ -85,7 +85,7 @@ export const MetadataHeader: React.FC = ({ href={`/${chain.slug}`} className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-muted/50 px-2.5 py-1.5 hover:bg-muted" > - + {cleanedChainName && ( {cleanedChainName} )} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx index 6ca29ce31f5..e57a1c77945 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx @@ -4,9 +4,7 @@ import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env"; import { getThirdwebClient } from "@/constants/thirdweb.server"; import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { cn } from "@/lib/utils"; - -const fallbackChainIcon = - "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K"; +import { fallbackChainIcon } from "../../../../../utils/chain-icons"; export async function ChainIcon(props: { iconUrl?: string; @@ -35,6 +33,11 @@ export async function ChainIcon(props: { if (res?.status === 200) { imageLink = resolved; + // check that its an image + const contentType = res.headers.get("content-type"); + if (!contentType?.startsWith("image")) { + imageLink = fallbackChainIcon; + } } } diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx index d6ec94148fc..69c18a2d571 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx @@ -81,7 +81,7 @@ export const ContractSubscriptionTable: React.FC< const chain = idToChain.get(cell.getValue()); return ( - + {chain?.name ?? "N/A"} ); @@ -399,7 +399,7 @@ const RemoveModal = ({ Chain - + {chain?.name ?? "N/A"} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx index 5f37a53cd61..a17ff3405e8 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx @@ -541,7 +541,7 @@ const SendFundsModal = ({ Chain - + {chain?.name} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx index 2f8e85c2e8c..72df90c251c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx @@ -128,7 +128,7 @@ export const TransactionsTable: React.FC = ({ if (chain) { return ( - + {chain?.name ?? "N/A"} @@ -359,7 +359,7 @@ const TransactionDetailsDrawer = ({ Chain - + {chain?.name} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx index 622a15a92d6..a31782c0fc5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx @@ -71,7 +71,7 @@ export const RelayersTable: React.FC = ({ const chain = idToChain.get(Number.parseInt(cell.getValue())); return ( - + {chain?.name ?? "N/A"} ); @@ -405,7 +405,7 @@ const RemoveModal = ({ Chain = ({ )} >

diff --git a/apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx b/apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx index 08a77ab8c1d..4b09a1db7ac 100644 --- a/apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx +++ b/apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx @@ -366,7 +366,7 @@ export const ConfigureNetworkForm: React.FC = ({ label="Icon" >
- + { form.setValue("icon", uri, { shouldDirty: true }); diff --git a/apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx b/apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx index f0b3a9509fe..3a8809b48ba 100644 --- a/apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx +++ b/apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx @@ -194,7 +194,7 @@ const ContractTable: React.FC = ({ `Unknown Network (#${cell.row.original.chainId})`; return (
- + ; type ChainIconProps = ImageProps & { ipfsSrc?: string; - size: ImageProps["width"]; }; -export const ChainIcon = forwardRef( - ({ ipfsSrc, size, ...restProps }, ref) => { - const src = ipfsSrc ? replaceIpfsUrl(ipfsSrc) : fallbackIcon; - // treat size of number as "px" - size = typeof size === "number" ? `${size}px` : size; +export const ChainIcon = ({ ipfsSrc, ...restProps }: ChainIconProps) => { + const src = ipfsSrc ? replaceIpfsUrl(ipfsSrc) : fallbackChainIcon; - return ( - { - event.currentTarget.srcset = `${fallbackIcon} 1x`; - event.currentTarget.src = fallbackIcon; - }} - /> - ); - }, -); + return ( + } + skeleton={
} + /> + ); +}; diff --git a/apps/dashboard/src/components/selects/CustomChainRenderer.tsx b/apps/dashboard/src/components/selects/CustomChainRenderer.tsx index 811fe3154fa..629d68828c6 100644 --- a/apps/dashboard/src/components/selects/CustomChainRenderer.tsx +++ b/apps/dashboard/src/components/selects/CustomChainRenderer.tsx @@ -49,7 +49,7 @@ export const CustomChainRenderer = ({ } }} > - +

= ({ onSelect(v === "all-chains" ? undefined : v); }} > - +

- + All Networks
{chains.map((chain) => (
- + {useCleanChainName ? cleanChainName(chain.name) : chain.name}
diff --git a/apps/dashboard/src/components/selects/NetworkSelectorButton.tsx b/apps/dashboard/src/components/selects/NetworkSelectorButton.tsx index 20f7d9d5e1d..f6508e0afc8 100644 --- a/apps/dashboard/src/components/selects/NetworkSelectorButton.tsx +++ b/apps/dashboard/src/components/selects/NetworkSelectorButton.tsx @@ -196,7 +196,7 @@ export const NetworkSelectorButton: React.FC = ({ }); }} > - + {chain?.name || "Select Network"} diff --git a/apps/dashboard/src/components/settings/Account/Billing/CreditsItem.tsx b/apps/dashboard/src/components/settings/Account/Billing/CreditsItem.tsx index 085d85c0443..3780e53a125 100644 --- a/apps/dashboard/src/components/settings/Account/Billing/CreditsItem.tsx +++ b/apps/dashboard/src/components/settings/Account/Billing/CreditsItem.tsx @@ -51,7 +51,7 @@ export const CreditsItem: React.FC = ({ ) : isTwCredit ? (