diff --git a/apps/dashboard/.eslintrc.js b/apps/dashboard/.eslintrc.js index 65a8b931ebf..6cdff17ef4e 100644 --- a/apps/dashboard/.eslintrc.js +++ b/apps/dashboard/.eslintrc.js @@ -34,6 +34,11 @@ module.exports = { message: "Use useV5DashboardChain instead if you are using it inside a component", }, + { + selector: "CallExpression[callee.name='resolveScheme']", + message: + "resolveScheme can throw error if resolution fails. Either catch the error and ignore the lint warning or Use `resolveSchemeWithErrorHandler` / `replaceIpfsUrl` utility in dashboard instead", + }, ], "no-restricted-imports": [ "error", diff --git a/apps/dashboard/src/@/components/blocks/wallet-address.tsx b/apps/dashboard/src/@/components/blocks/wallet-address.tsx index 581fd87be99..bee1e2611b7 100644 --- a/apps/dashboard/src/@/components/blocks/wallet-address.tsx +++ b/apps/dashboard/src/@/components/blocks/wallet-address.tsx @@ -1,5 +1,4 @@ "use client"; - import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { HoverCard, @@ -7,6 +6,7 @@ import { HoverCardTrigger, } from "@/components/ui/hover-card"; import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { useClipboard } from "hooks/useClipboard"; import { Check, Copy, ExternalLinkIcon } from "lucide-react"; import { useMemo } from "react"; @@ -18,7 +18,6 @@ import { type SocialProfile, useSocialProfiles, } from "thirdweb/react"; -import { resolveScheme } from "thirdweb/storage"; import { cn } from "../../lib/utils"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; @@ -113,22 +112,20 @@ export function WalletAddress(props: { ) : !profiles.data?.length ? (

No profiles found

) : ( - profiles.data?.map((profile) => ( -
- {profile.avatar && - (profile.avatar.startsWith("http") || - profile.avatar?.startsWith("ipfs")) && ( + profiles.data?.map((profile) => { + const walletAvatarLink = resolveSchemeWithErrorHandler({ + client: thirdwebClient, + uri: profile.avatar, + }); + + return ( +
+ {walletAvatarLink && ( - + {profile.name && ( {profile.name.slice(0, 2)} @@ -136,19 +133,20 @@ export function WalletAddress(props: { )} )} -
-
-

{profile.name}

- {profile.type} +
+
+

{profile.name}

+ {profile.type} +
+ {profile.bio && ( +

+ {profile.bio} +

+ )}
- {profile.bio && ( -

- {profile.bio} -

- )}
-
- )) + ); + }) )} + {tokenURIHttpLink && ( + + )} {nft.metadata.image && ( <> @@ -287,17 +295,13 @@ export const TokenIdPage: React.FC = ({ tooltip="The media URI of this NFT" copyIconPosition="right" /> - + {nftImageLink && ( + + )} )} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx index ef3ed2039c0..9ab1b93ba3a 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx @@ -89,7 +89,8 @@ export const SettingsMetadata = ({ let image: string | undefined = metadata.data?.image; try { image = image - ? resolveScheme({ + ? // eslint-disable-next-line no-restricted-syntax + resolveScheme({ client: contract.client, uri: image, }) 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 f8fc5738ca1..6ca29ce31f5 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 @@ -2,8 +2,8 @@ import "server-only"; import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env"; import { getThirdwebClient } from "@/constants/thirdweb.server"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { cn } from "@/lib/utils"; -import { resolveScheme } from "thirdweb/storage"; const fallbackChainIcon = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K"; @@ -13,24 +13,35 @@ export async function ChainIcon(props: { className?: string; }) { if (props.iconUrl) { - const resolved = resolveScheme({ + let imageLink = fallbackChainIcon; + + const resolved = resolveSchemeWithErrorHandler({ client: getThirdwebClient(), uri: props.iconUrl, }); - const res = await fetch(resolved, { - // revalidate every hour - next: { revalidate: 60 * 60 }, - method: "HEAD", - headers: DASHBOARD_THIRDWEB_SECRET_KEY - ? { - "x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY, - } - : {}, - }).catch(() => null); + + if (resolved) { + // check if it loads or not + const res = await fetch(resolved, { + // revalidate every hour + next: { revalidate: 60 * 60 }, + method: "HEAD", + headers: DASHBOARD_THIRDWEB_SECRET_KEY + ? { + "x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY, + } + : {}, + }).catch(() => null); + + if (res?.status === 200) { + imageLink = resolved; + } + } + return ( ); diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx index d96e8bf91e5..b0d2e316263 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx @@ -14,6 +14,7 @@ import { import { Skeleton } from "@/components/ui/skeleton"; import { TabLinks } from "@/components/ui/tabs"; import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { AlertTriangleIcon, CheckIcon, @@ -23,7 +24,6 @@ import { import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { resolveScheme } from "thirdweb/storage"; import { useEcosystemList } from "../../../../hooks/use-ecosystem-list"; import type { Ecosystem } from "../../../../types"; import { useEcosystem } from "../../hooks/use-ecosystem"; @@ -127,6 +127,11 @@ export function EcosystemHeader(props: { const ecosystem = fetchedEcosystem ?? props.ecosystem; + const ecosystemImageLink = resolveSchemeWithErrorHandler({ + uri: ecosystem.imageUrl, + client, + }); + return (
@@ -136,13 +141,10 @@ export function EcosystemHeader(props: { {!ecosystem.imageUrl ? ( ) : ( - ecosystem.imageUrl && ( + ecosystemImageLink && (
{ecosystem.name}
- {props.logo && ( + {contractImageLink && (
{/*eslint-disable-next-line @next/next/no-img-element*/} - {props.name} + {props.name}
)} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx index 6639e6492f3..e5d3e3ed461 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx @@ -7,11 +7,11 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Input } from "@/components/ui/input"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { useMutation } from "@tanstack/react-query"; import { FileInput } from "components/shared/FileInput"; import { useState } from "react"; import { toast } from "sonner"; -import { resolveScheme } from "thirdweb/storage"; type UpdateTeamField = (team: Partial) => Promise; @@ -152,12 +152,10 @@ function TeamAvatarFormControl(props: { avatar: string | undefined; }) { const client = useThirdwebClient(); - const teamUrl = props.avatar - ? resolveScheme({ - client: client, - uri: props.avatar, - }) - : undefined; + const teamAvatarUrl = resolveSchemeWithErrorHandler({ + client: client, + uri: props.avatar, + }); const [teamAvatar, setTeamAvatar] = useState(); @@ -200,7 +198,7 @@ function TeamAvatarFormControl(props: { setValue={setTeamAvatar} className="w-20 rounded-full lg:w-28" disableHelperText - fileUrl={teamUrl} + fileUrl={teamAvatarUrl} />
diff --git a/apps/dashboard/src/components/contract-components/publisher/masked-avatar.tsx b/apps/dashboard/src/components/contract-components/publisher/masked-avatar.tsx index bc3e63ead8a..7e82ae0b8fd 100644 --- a/apps/dashboard/src/components/contract-components/publisher/masked-avatar.tsx +++ b/apps/dashboard/src/components/contract-components/publisher/masked-avatar.tsx @@ -1,7 +1,7 @@ "use client"; import { useThirdwebClient } from "@/constants/thirdweb.client"; -import { resolveScheme } from "thirdweb/storage"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { MaskedAvatar, type MaskedAvatarProps, @@ -22,17 +22,16 @@ export const PublisherAvatar: React.FC = ({ const publisherProfile = usePublisherProfile( ensQuery.data?.address || undefined, ); + + const publisherImageUrl = resolveSchemeWithErrorHandler({ + uri: publisherProfile.data?.avatar, + client, + }); + return ( ); diff --git a/apps/dashboard/src/components/explore/contract-card/index.tsx b/apps/dashboard/src/components/explore/contract-card/index.tsx index f3484a3cb77..185d6534d52 100644 --- a/apps/dashboard/src/components/explore/contract-card/index.tsx +++ b/apps/dashboard/src/components/explore/contract-card/index.tsx @@ -5,12 +5,12 @@ import { Button } from "@/components/ui/button"; import { Skeleton, SkeletonContainer } from "@/components/ui/skeleton"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { cn } from "@/lib/utils"; import { useQuery } from "@tanstack/react-query"; import { moduleToBase64 } from "app/(dashboard)/published-contract/utils/module-base-64"; import { RocketIcon, ShieldCheckIcon } from "lucide-react"; import Link from "next/link"; -import { resolveScheme } from "thirdweb/storage"; import invariant from "tiny-invariant"; import { fetchPublishedContractVersion } from "../../contract-components/fetch-contracts-with-versions"; import { ContractPublisher, replaceDeployerAddress } from "../publisher"; @@ -97,6 +97,11 @@ export const ContractCard: React.FC = ({ const showSkeleton = publishedContractResult.isPending; + const auditLink = resolveSchemeWithErrorHandler({ + uri: publishedContractResult.data?.audit, + client, + }); + return (
= ({
{/* Audited */} - {publishedContractResult.data?.audit && ( + {auditLink && ( <> Audited diff --git a/apps/dashboard/src/lib/sdk.ts b/apps/dashboard/src/lib/sdk.ts index 69f2b8b2e6e..54c8f982d81 100644 --- a/apps/dashboard/src/lib/sdk.ts +++ b/apps/dashboard/src/lib/sdk.ts @@ -3,6 +3,7 @@ import { resolveScheme } from "thirdweb/storage"; export function replaceIpfsUrl(uri: string) { try { + // eslint-disable-next-line no-restricted-syntax return resolveScheme({ uri, client: getThirdwebClient(),