Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/dashboard/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
54 changes: 26 additions & 28 deletions apps/dashboard/src/@/components/blocks/wallet-address.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
HoverCard,
HoverCardContent,
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";
Expand All @@ -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";
Expand Down Expand Up @@ -113,42 +112,41 @@ export function WalletAddress(props: {
) : !profiles.data?.length ? (
<p className="text-muted-foreground text-sm">No profiles found</p>
) : (
profiles.data?.map((profile) => (
<div
className="flex flex-row items-center gap-2"
key={profile.type + profile.name}
>
{profile.avatar &&
(profile.avatar.startsWith("http") ||
profile.avatar?.startsWith("ipfs")) && (
profiles.data?.map((profile) => {
const walletAvatarLink = resolveSchemeWithErrorHandler({
client: thirdwebClient,
uri: profile.avatar,
});

return (
<div
className="flex flex-row items-center gap-2"
key={profile.type + profile.name}
>
{walletAvatarLink && (
<Avatar>
<AvatarImage
src={resolveScheme({
client: thirdwebClient,
uri: profile.avatar,
})}
alt={profile.name}
/>
<AvatarImage src={walletAvatarLink} alt={profile.name} />
{profile.name && (
<AvatarFallback>
{profile.name.slice(0, 2)}
</AvatarFallback>
)}
</Avatar>
)}
<div className="flex w-full flex-col gap-1">
<div className="flex w-full flex-row items-center justify-between gap-4">
<h4 className="font-semibold text-md">{profile.name}</h4>
<Badge variant="outline">{profile.type}</Badge>
<div className="flex w-full flex-col gap-1">
<div className="flex w-full flex-row items-center justify-between gap-4">
<h4 className="font-semibold text-md">{profile.name}</h4>
<Badge variant="outline">{profile.type}</Badge>
</div>
{profile.bio && (
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
{profile.bio}
</p>
)}
</div>
{profile.bio && (
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
{profile.bio}
</p>
)}
</div>
</div>
))
);
})
)}
<Button
asChild
Expand Down
21 changes: 21 additions & 0 deletions apps/dashboard/src/@/lib/resolveSchemeWithErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ThirdwebClient } from "thirdweb";
import { resolveScheme } from "thirdweb/storage";

export function resolveSchemeWithErrorHandler(options: {
uri: string | undefined;
client: ThirdwebClient;
}) {
if (!options.uri) {
return undefined;
}
try {
// eslint-disable-next-line no-restricted-syntax
return resolveScheme({
uri: options.uri,
client: options.client,
});
} catch (err) {
console.error("error resolving ipfs url", options.uri, err);
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import {
Box,
ButtonGroup,
Expand All @@ -25,7 +26,6 @@ import type { ThirdwebContract } from "thirdweb";
import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721";
import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155";
import { useReadContract } from "thirdweb/react";
import { resolveScheme } from "thirdweb/storage";
import { Badge, Button, Card, CodeBlock, Heading, Text } from "tw-components";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
import { shortenString } from "utils/usedapp-external";
Expand Down Expand Up @@ -83,6 +83,15 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
},
);

const tokenURIHttpLink = resolveSchemeWithErrorHandler({
client,
uri: nft?.tokenURI,
});
const nftImageLink = resolveSchemeWithErrorHandler({
client,
uri: nft?.metadata.image,
});

if (isPending) {
return (
<div className="flex h-[400px] items-center justify-center">
Expand Down Expand Up @@ -263,14 +272,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
tooltip="The URI of this NFT"
copyIconPosition="right"
/>
<Button variant="ghost" size="sm">
<Link
href={resolveScheme({ client, uri: nft.tokenURI })}
target="_blank"
>
<ExternalLinkIcon className="size-4" />
</Link>
</Button>
{tokenURIHttpLink && (
<Button variant="ghost" size="sm">
<Link href={tokenURIHttpLink} target="_blank">
<ExternalLinkIcon className="size-4" />
</Link>
</Button>
)}
</GridItem>
{nft.metadata.image && (
<>
Expand All @@ -287,17 +295,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
tooltip="The media URI of this NFT"
copyIconPosition="right"
/>
<Button variant="ghost" size="sm">
<Link
href={resolveScheme({
client,
uri: nft.metadata.image,
})}
target="_blank"
>
<ExternalLinkIcon className="size-4" />
</Link>
</Button>
{nftImageLink && (
<Button variant="ghost" size="sm">
<Link href={nftImageLink} target="_blank">
<ExternalLinkIcon className="size-4" />
</Link>
</Button>
)}
</GridItem>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 (
<img
alt=""
src={res?.status === 200 ? resolved : fallbackChainIcon}
src={imageLink}
className={cn("object-contain", props.className)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -127,6 +127,11 @@ export function EcosystemHeader(props: {

const ecosystem = fetchedEcosystem ?? props.ecosystem;

const ecosystemImageLink = resolveSchemeWithErrorHandler({
uri: ecosystem.imageUrl,
client,
});

return (
<div className="flex flex-col gap-8">
<EcosystemAlertBanner ecosystem={ecosystem} />
Expand All @@ -136,13 +141,10 @@ export function EcosystemHeader(props: {
{!ecosystem.imageUrl ? (
<Skeleton className="size-24" />
) : (
ecosystem.imageUrl && (
ecosystemImageLink && (
<div className="relative size-24 overflow-hidden rounded-md">
<Image
src={resolveScheme({
uri: ecosystem.imageUrl,
client,
})}
src={ecosystemImageLink}
sizes="100px"
alt={ecosystem.name}
fill
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { resolveScheme } from "thirdweb/storage";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";

export function DeployContractInfo(props: {
name: string;
Expand All @@ -9,20 +9,18 @@ export function DeployContractInfo(props: {
}) {
const contractNameDisplay = props.displayName || props.name;

const contractImageLink = resolveSchemeWithErrorHandler({
client: getThirdwebClient(),
uri: props.logo,
});

return (
<div className="flex flex-col gap-2">
<div className="flex flex-1 items-center gap-4">
{props.logo && (
{contractImageLink && (
<div className="hidden shrink-0 items-center justify-center rounded-xl border border-border p-2 md:flex">
{/*eslint-disable-next-line @next/next/no-img-element*/}
<img
className="size-12"
alt={props.name}
src={resolveScheme({
client: getThirdwebClient(),
uri: props.logo,
})}
/>
<img className="size-12" alt={props.name} src={contractImageLink} />
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Team>) => Promise<void>;

Expand Down Expand Up @@ -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<File | undefined>();

Expand Down Expand Up @@ -200,7 +198,7 @@ function TeamAvatarFormControl(props: {
setValue={setTeamAvatar}
className="w-20 rounded-full lg:w-28"
disableHelperText
fileUrl={teamUrl}
fileUrl={teamAvatarUrl}
/>
</div>
</SettingsCard>
Expand Down
Loading
Loading