Skip to content

Commit a3909e1

Browse files
committed
Add caching to ContractCard, speed up /explore pageload (#7822)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring the `ContractRow`, `ContractPublisher`, and `ContractCard` components to streamline their props and improve caching mechanisms for fetching contract data. ### Detailed summary - Simplified `ContractRow` props by removing the interface. - Refactored `ContractPublisher` to use props directly instead of an interface. - Updated `ContractPublisher` to utilize `Img` and `Blobbie` for avatar rendering. - Introduced caching for `fetchPublishedContractVersion` and `resolvePublisherEnsAndAvatar`. - Changed how `ContractPublisher` is rendered in `ContractCard` to use new props structure. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved performance and consistency for contract publisher information by introducing server-side caching for contract version data and publisher ENS details. * Enhanced publisher display with avatars and ENS names, including fallback visuals when no avatar is available. * **Refactor** * Streamlined the contract publisher display by replacing the previous component with a simplified implementation accepting explicit props. * Updated type definitions for improved code clarity without affecting user-facing behavior. * **Chores** * Minor import and type annotation adjustments for better maintainability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent f92201d commit a3909e1

File tree

3 files changed

+108
-64
lines changed

3 files changed

+108
-64
lines changed

apps/dashboard/src/@/components/contracts/contract-card/contract-card.tsx

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64";
22
import { RocketIcon, ShieldCheckIcon } from "lucide-react";
3+
import { unstable_cache } from "next/cache";
34
import Link from "next/link";
5+
import { resolveAvatar } from "thirdweb/extensions/ens";
46
import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions";
5-
import { ClientOnly } from "@/components/blocks/client-only";
67
import { Badge } from "@/components/ui/badge";
78
import { Button } from "@/components/ui/button";
89
import { Skeleton } from "@/components/ui/skeleton";
9-
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
1010
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
11+
import { resolveEns } from "@/lib/ens";
1112
import { replaceDeployerAddress } from "@/lib/publisher-utils";
1213
import { cn } from "@/lib/utils";
1314
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
1415
import { ContractPublisher } from "./contract-publisher";
1516

16-
interface ContractCardProps {
17+
type ContractCardProps = {
1718
publisher: string;
1819
contractId: string;
1920
titleOverride?: string;
@@ -29,7 +30,7 @@ interface ContractCardProps {
2930
moduleId: string;
3031
version?: string;
3132
}[];
32-
}
33+
};
3334

3435
function getContractUrl(
3536
{
@@ -74,6 +75,72 @@ function getContractUrl(
7475
return replaceDeployerAddress(pathName);
7576
}
7677

78+
const cached_fetchPublishedContractVersion = unstable_cache(
79+
async (publisher: string, contractId: string, version: string = "latest") => {
80+
const result = await fetchPublishedContractVersion(
81+
publisher,
82+
contractId,
83+
serverThirdwebClient,
84+
version,
85+
).catch(() => null);
86+
87+
if (!result) {
88+
return null;
89+
}
90+
91+
const publisherEnsAndAvatar = result.publisher
92+
? await cached_resolvePublisherEnsAndAvatar(result.publisher)
93+
: undefined;
94+
95+
// Note: Do not return BigInt - it can't be serialized and cached by unstable_cache and will throw an error
96+
return {
97+
name: result.name,
98+
displayName: result.displayName,
99+
description: result.description,
100+
publisher: {
101+
address: result.publisher,
102+
ensName: publisherEnsAndAvatar?.ensName,
103+
ensAvatar: publisherEnsAndAvatar?.ensAvatar,
104+
},
105+
version: result.version,
106+
audit: result.audit,
107+
};
108+
},
109+
["fetchPublishedContractVersion"],
110+
{
111+
revalidate: 3600, // 1 hour
112+
},
113+
);
114+
115+
const cached_resolvePublisherEnsAndAvatar = unstable_cache(
116+
async (_addressOrEns: string) => {
117+
const addressOrEns = replaceDeployerAddress(_addressOrEns);
118+
const [ensNameInfo, ensAvatar] = await Promise.allSettled([
119+
resolveEns(addressOrEns, serverThirdwebClient),
120+
resolveAvatar({
121+
client: serverThirdwebClient,
122+
name: addressOrEns,
123+
}),
124+
]);
125+
126+
return {
127+
ensName:
128+
ensNameInfo.status === "fulfilled"
129+
? ensNameInfo.value?.ensName
130+
: undefined,
131+
address:
132+
ensNameInfo.status === "fulfilled"
133+
? ensNameInfo.value?.address
134+
: undefined,
135+
ensAvatar: ensAvatar.status === "fulfilled" ? ensAvatar.value : undefined,
136+
};
137+
},
138+
["resolvePublisherEnsAndAvatar"],
139+
{
140+
revalidate: 3600, // 1 hour
141+
},
142+
);
143+
77144
export async function ContractCard({
78145
publisher,
79146
contractId,
@@ -83,12 +150,11 @@ export async function ContractCard({
83150
modules = [],
84151
isBeta,
85152
}: ContractCardProps) {
86-
const publishedContractResult = await fetchPublishedContractVersion(
153+
const publishedContractResult = await cached_fetchPublishedContractVersion(
87154
publisher,
88155
contractId,
89-
serverThirdwebClient,
90156
version,
91-
).catch(() => null);
157+
);
92158

93159
if (!publishedContractResult) {
94160
return null;
@@ -186,13 +252,12 @@ export async function ContractCard({
186252
!modules?.length && "mt-auto",
187253
)}
188254
>
189-
{publishedContractResult.publisher && (
190-
<ClientOnly ssr={<Skeleton className="size-5 rounded-full" />}>
191-
<ContractPublisher
192-
addressOrEns={publishedContractResult.publisher}
193-
client={getClientThirdwebClient()}
194-
/>
195-
</ClientOnly>
255+
{publishedContractResult.publisher.address && (
256+
<ContractPublisher
257+
address={publishedContractResult.publisher.address}
258+
ensName={publishedContractResult.publisher.ensName || undefined}
259+
ensAvatar={publishedContractResult.publisher.ensAvatar || undefined}
260+
/>
196261
)}
197262

198263
<div className="flex items-center justify-between">
Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,36 @@
11
"use client";
22

33
import Link from "next/link";
4-
import type { ThirdwebClient } from "thirdweb";
5-
import {
6-
AccountAddress,
7-
AccountAvatar,
8-
AccountBlobbie,
9-
AccountName,
10-
AccountProvider,
11-
} from "thirdweb/react";
12-
import { Skeleton } from "@/components/ui/skeleton";
13-
import { replaceDeployerAddress } from "@/lib/publisher-utils";
4+
import { Blobbie } from "thirdweb/react";
5+
import { Img } from "@/components/blocks/Img";
6+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
7+
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
148
import { shortenIfAddress } from "@/utils/usedapp-external";
159

16-
interface ContractPublisherProps {
17-
addressOrEns: string;
18-
client: ThirdwebClient;
19-
}
20-
21-
export const ContractPublisher: React.FC<ContractPublisherProps> = ({
22-
addressOrEns,
23-
client,
24-
}) => {
10+
export function ContractPublisher(props: {
11+
ensName: string | undefined;
12+
address: string;
13+
ensAvatar: string | undefined;
14+
}) {
15+
const displayName = props.ensName || props.address;
2516
return (
26-
<AccountProvider address={addressOrEns} client={client}>
27-
<Link
28-
className="flex shrink-0 items-center gap-1.5 hover:underline"
29-
href={replaceDeployerAddress(`/${addressOrEns}`)}
30-
>
31-
<AccountAvatar
32-
className="size-5 rounded-full object-cover"
33-
fallbackComponent={<AccountBlobbie className="size-5 rounded-full" />}
34-
loadingComponent={<Skeleton className="size-5 rounded-full" />}
35-
/>
17+
<Link
18+
className="flex shrink-0 items-center gap-1.5 hover:underline"
19+
href={`/${displayName}`}
20+
target="_blank"
21+
>
22+
<Img
23+
className="size-5 rounded-full object-cover"
24+
src={
25+
resolveSchemeWithErrorHandler({
26+
client: getClientThirdwebClient(),
27+
uri: props.ensAvatar,
28+
}) || ""
29+
}
30+
fallback={<Blobbie address={props.address} />}
31+
/>
3632

37-
<AccountName
38-
className="text-xs"
39-
fallbackComponent={
40-
<AccountAddress
41-
className="text-xs"
42-
formatFn={(addr) =>
43-
shortenIfAddress(replaceDeployerAddress(addr))
44-
}
45-
/>
46-
}
47-
formatFn={(name) => replaceDeployerAddress(name)}
48-
loadingComponent={<Skeleton className="h-4 w-40" />}
49-
/>
50-
</Link>
51-
</AccountProvider>
33+
<span className="text-xs"> {shortenIfAddress(displayName)}</span>
34+
</Link>
5235
);
53-
};
36+
}

apps/dashboard/src/app/(app)/(dashboard)/explore/components/contract-row/index.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ import {
77
} from "@/components/contracts/contract-card/contract-card";
88
import type { ExploreCategory } from "../../data";
99

10-
interface ContractRowProps {
11-
category: ExploreCategory;
12-
}
13-
14-
export function ContractRow({ category }: ContractRowProps) {
10+
export function ContractRow({ category }: { category: ExploreCategory }) {
1511
return (
1612
<section>
1713
{/* Title, Description + View all link */}

0 commit comments

Comments
 (0)