Skip to content

Commit 46a959d

Browse files
committed
[CORE-652] Add chain icon in SingleNetworkSelector + other chain icon related fixes
1 parent 7d8566e commit 46a959d

File tree

19 files changed

+187
-62
lines changed

19 files changed

+187
-62
lines changed

apps/dashboard/src/@/components/blocks/Img.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export function Img(props: imgElementProps) {
6565
"fade-in-0 object-cover transition-opacity duration-300",
6666
className,
6767
)}
68+
decoding="async"
6869
/>
6970

7071
{status !== "loaded" && (
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { useState } from "react";
3+
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
4+
import { MultiNetworkSelector } from "./NetworkSelectors";
5+
6+
const meta = {
7+
title: "blocks/Cards/MultiNetworkSelector",
8+
component: Story,
9+
parameters: {
10+
nextjs: {
11+
appDirectory: true,
12+
},
13+
},
14+
} satisfies Meta<typeof Story>;
15+
16+
export default meta;
17+
type Story = StoryObj<typeof meta>;
18+
19+
export const Desktop: Story = {
20+
args: {},
21+
};
22+
23+
export const Mobile: Story = {
24+
args: {},
25+
parameters: {
26+
viewport: mobileViewport("iphone14"),
27+
},
28+
};
29+
30+
function Story() {
31+
return (
32+
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
33+
<Variant label="No Chains selected by default" selectedChainIds={[]} />
34+
<Variant
35+
label="Polygon, Ethereum selected by default"
36+
selectedChainIds={[1, 137]}
37+
/>
38+
</div>
39+
);
40+
}
41+
42+
function Variant(props: {
43+
label: string;
44+
selectedChainIds: number[];
45+
}) {
46+
const [chainIds, setChainIds] = useState<number[]>(props.selectedChainIds);
47+
return (
48+
<BadgeContainer label={props.label}>
49+
<MultiNetworkSelector
50+
selectedChainIds={chainIds}
51+
onChange={setChainIds}
52+
/>
53+
</BadgeContainer>
54+
);
55+
}

apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { MultiSelect } from "@/components/blocks/multi-select";
44
import { SelectWithSearch } from "@/components/blocks/select-with-search";
55
import { Badge } from "@/components/ui/badge";
66
import { useCallback, useMemo } from "react";
7+
import { ChainIcon } from "../../../components/icons/ChainIcon";
78
import { useAllChainsData } from "../../../hooks/chains/allChains";
89

910
function cleanChainName(chainName: string) {
@@ -51,7 +52,7 @@ export function MultiNetworkSelector(props: {
5152

5253
return (
5354
<div className="flex justify-between gap-4">
54-
<span className="grow truncate text-left">
55+
<span className="flex grow gap-2 truncate text-left">
5556
{cleanChainName(chain.name)}
5657
</span>
5758
<Badge variant="outline" className="gap-2">
@@ -133,8 +134,15 @@ export function SingleNetworkSelector(props: {
133134

134135
return (
135136
<div className="flex justify-between gap-4">
136-
<span className="grow truncate text-left">{chain.name}</span>
137-
<Badge variant="outline" className="gap-2">
137+
<span className="flex grow gap-2 truncate text-left">
138+
<ChainIcon
139+
className="size-5"
140+
ipfsSrc={chain.icon?.url}
141+
loading="lazy"
142+
/>
143+
{chain.name}
144+
</span>
145+
<Badge variant="outline" className="gap-2 max-sm:hidden">
138146
<span className="text-muted-foreground">Chain ID</span>
139147
{chain.chainId}
140148
</Badge>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { useState } from "react";
3+
import { BadgeContainer, mobileViewport } from "../../../stories/utils";
4+
import { SingleNetworkSelector } from "./NetworkSelectors";
5+
6+
const meta = {
7+
title: "blocks/Cards/SingleNetworkSelector",
8+
component: Story,
9+
parameters: {
10+
nextjs: {
11+
appDirectory: true,
12+
},
13+
},
14+
} satisfies Meta<typeof Story>;
15+
16+
export default meta;
17+
type Story = StoryObj<typeof meta>;
18+
19+
export const Desktop: Story = {
20+
args: {},
21+
};
22+
23+
export const Mobile: Story = {
24+
args: {},
25+
parameters: {
26+
viewport: mobileViewport("iphone14"),
27+
},
28+
};
29+
30+
function Story() {
31+
return (
32+
<div className="container flex max-w-[1000px] flex-col gap-8 lg:p-10">
33+
<Variant label="No Chain ID selected by default" chainId={undefined} />
34+
<Variant label="Polygon selected by default" chainId={137} />
35+
<Variant
36+
label="Show certain chains only"
37+
chainId={undefined}
38+
chainIds={[1, 137, 10]}
39+
/>
40+
</div>
41+
);
42+
}
43+
44+
function Variant(props: {
45+
label: string;
46+
chainId: number | undefined;
47+
chainIds?: number[];
48+
}) {
49+
const [chainId, setChainId] = useState<number | undefined>(props.chainId);
50+
return (
51+
<BadgeContainer label={props.label}>
52+
<SingleNetworkSelector
53+
chainId={chainId}
54+
onChange={setChainId}
55+
chainIds={props.chainIds}
56+
/>
57+
</BadgeContainer>
58+
);
59+
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const MetadataHeader: React.FC<MetadataHeaderProps> = ({
8585
href={`/${chain.slug}`}
8686
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"
8787
>
88-
<ChainIcon ipfsSrc={chain.icon?.url} size={16} />
88+
<ChainIcon ipfsSrc={chain.icon?.url} className="size-4" />
8989
{cleanedChainName && (
9090
<span className="text-xs">{cleanedChainName}</span>
9191
)}

apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@ import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
44
import { getThirdwebClient } from "@/constants/thirdweb.server";
55
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
66
import { cn } from "@/lib/utils";
7-
8-
const fallbackChainIcon =
9-
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K";
7+
import {
8+
fallbackChainIcon,
9+
iconsToReplace,
10+
} from "../../../../../utils/chain-icons";
1011

1112
export async function ChainIcon(props: {
1213
iconUrl?: string;
1314
className?: string;
1415
}) {
1516
if (props.iconUrl) {
1617
let imageLink = fallbackChainIcon;
18+
const modifiedIconUrl =
19+
props.iconUrl && props.iconUrl in iconsToReplace
20+
? iconsToReplace[props.iconUrl]
21+
: props.iconUrl;
1722

1823
const resolved = resolveSchemeWithErrorHandler({
1924
client: getThirdwebClient(),
20-
uri: props.iconUrl,
25+
uri: modifiedIconUrl,
2126
});
2227

2328
if (resolved) {
@@ -35,6 +40,11 @@ export async function ChainIcon(props: {
3540

3641
if (res?.status === 200) {
3742
imageLink = resolved;
43+
// check that its an image
44+
const contentType = res.headers.get("content-type");
45+
if (!contentType?.startsWith("image")) {
46+
imageLink = fallbackChainIcon;
47+
}
3848
}
3949
}
4050

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/contract-subscriptions/components/contract-subscriptions-table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const ContractSubscriptionTable: React.FC<
8181
const chain = idToChain.get(cell.getValue());
8282
return (
8383
<Flex align="center" gap={2}>
84-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
84+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
8585
<Text>{chain?.name ?? "N/A"}</Text>
8686
</Flex>
8787
);
@@ -399,7 +399,7 @@ const RemoveModal = ({
399399
<FormControl>
400400
<FormLabel>Chain</FormLabel>
401401
<Flex align="center" gap={2}>
402-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
402+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
403403
<Text>{chain?.name ?? "N/A"}</Text>
404404
</Flex>
405405
</FormControl>

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ const SendFundsModal = ({
541541
<FormControl>
542542
<FormLabel>Chain</FormLabel>
543543
<Flex align="center" gap={2}>
544-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
544+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
545545
<Text>{chain?.name}</Text>
546546
</Flex>
547547
</FormControl>

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const TransactionsTable: React.FC<TransactionsTableProps> = ({
128128
if (chain) {
129129
return (
130130
<Flex align="center" gap={2} className="py-2">
131-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
131+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
132132
<Text maxW={150} isTruncated>
133133
{chain?.name ?? "N/A"}
134134
</Text>
@@ -359,7 +359,7 @@ const TransactionDetailsDrawer = ({
359359
<FormControl>
360360
<FormLabel>Chain</FormLabel>
361361
<Flex align="center" gap={2}>
362-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
362+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
363363
<Text>{chain?.name}</Text>
364364
</Flex>
365365
</FormControl>

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/relayers/components/relayers-table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const RelayersTable: React.FC<RelayersTableProps> = ({
7171
const chain = idToChain.get(Number.parseInt(cell.getValue()));
7272
return (
7373
<Flex align="center" gap={2}>
74-
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
74+
<ChainIcon className="size-3" ipfsSrc={chain?.icon?.url} />
7575
<Text>{chain?.name ?? "N/A"}</Text>
7676
</Flex>
7777
);
@@ -405,7 +405,7 @@ const RemoveModal = ({
405405
<FormLabel>Chain</FormLabel>
406406
<Flex align="center" gap={2}>
407407
<ChainIcon
408-
size={12}
408+
className="size-3"
409409
ipfsSrc={
410410
idToChain.get(Number.parseInt(relayer.chainId))?.icon?.url
411411
}

0 commit comments

Comments
 (0)