Skip to content

Commit b49149a

Browse files
authored
Add extra caching, fix loading UX (#19)
* fix: Cache getLicensesTotalSupply and fix ParamsPagination scroll to top * fix: Loading UX and caching per license for the Licenses page * fix: Nodes page restructuring * fix: Create ActiveNodes componet so it correctly triggers Suspense boundry * chore: Move the pingBackend definition * fix: Implement cachedGetLicenses, move getActiveNodesCached * fix: Small bugs reported in the PR * fix: Loading pages
1 parent f9222ff commit b49149a

File tree

34 files changed

+478
-358
lines changed

34 files changed

+478
-358
lines changed

app/account/[ownerEthAddr]/page.tsx

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import ClientWrapper from '@/components/shared/ClientWrapper';
88
import { CopyableAddress } from '@/components/shared/CopyableValue';
99
import config from '@/config';
1010
import { getPublicProfiles } from '@/lib/api/backend';
11-
import { fetchCSPDetails, fetchErc20Balance, getLicenses } from '@/lib/api/blockchain';
11+
import { fetchCSPDetails, fetchErc20Balance } from '@/lib/api/blockchain';
12+
import { cachedGetLicenses } from '@/lib/api/cache';
1213
import { cachedGetENSName, fBI, getShortAddress, isZeroAddress } from '@/lib/utils';
1314
import * as types from '@/typedefs/blockchain';
1415
import type { PublicProfileInfo } from '@/typedefs/general';
@@ -30,48 +31,48 @@ const getCachedNodeOperatorProfile = unstable_cache(
3031
{ revalidate: 60 },
3132
);
3233

33-
export async function generateMetadata({ params }) {
34-
const { ownerEthAddr } = await params;
35-
const errorMetadata = {
36-
title: 'Error',
37-
openGraph: {
38-
title: 'Error',
39-
},
40-
};
41-
42-
if (!ownerEthAddr || !isAddress(ownerEthAddr) || isZeroAddress(ownerEthAddr)) {
43-
return errorMetadata;
44-
}
45-
46-
const canonical = `/account/${encodeURIComponent(ownerEthAddr)}`;
47-
const errorMetadataWithCanonical = {
48-
...errorMetadata,
49-
alternates: {
50-
canonical,
51-
},
52-
};
53-
54-
try {
55-
const [ensName, publicProfile] = await Promise.all([
56-
cachedGetENSName(ownerEthAddr),
57-
getCachedNodeOperatorProfile(ownerEthAddr as types.EthAddress),
34+
export async function generateMetadata({ params }) {
35+
const { ownerEthAddr } = await params;
36+
const errorMetadata = {
37+
title: 'Error',
38+
openGraph: {
39+
title: 'Error',
40+
},
41+
};
42+
43+
if (!ownerEthAddr || !isAddress(ownerEthAddr) || isZeroAddress(ownerEthAddr)) {
44+
return errorMetadata;
45+
}
46+
47+
const canonical = `/account/${encodeURIComponent(ownerEthAddr)}`;
48+
const errorMetadataWithCanonical = {
49+
...errorMetadata,
50+
alternates: {
51+
canonical,
52+
},
53+
};
54+
55+
try {
56+
const [ensName, publicProfile] = await Promise.all([
57+
cachedGetENSName(ownerEthAddr),
58+
getCachedNodeOperatorProfile(ownerEthAddr as types.EthAddress),
5859
]);
5960

6061
const primaryName = publicProfile?.name || ensName || getShortAddress(ownerEthAddr, 4, true);
6162

62-
return {
63-
title: `Node Operator • ${primaryName}`,
64-
openGraph: {
65-
title: `Node Operator • ${primaryName}`,
66-
},
67-
alternates: {
68-
canonical,
69-
},
70-
};
71-
} catch (error) {
72-
return errorMetadataWithCanonical;
73-
}
74-
}
63+
return {
64+
title: `Node Operator • ${primaryName}`,
65+
openGraph: {
66+
title: `Node Operator • ${primaryName}`,
67+
},
68+
alternates: {
69+
canonical,
70+
},
71+
};
72+
} catch (error) {
73+
return errorMetadataWithCanonical;
74+
}
75+
}
7576

7677
export default async function NodeOperatorPage({ params }) {
7778
const { ownerEthAddr } = await params;
@@ -81,15 +82,15 @@ export default async function NodeOperatorPage({ params }) {
8182
return <NotFound />;
8283
}
8384

84-
let licenses: types.LicenseInfo[],
85+
let licenses: types.CachedLicense[],
8586
ensName: string | undefined,
8687
r1Balance: bigint,
8788
publicProfileInfo: PublicProfileInfo | undefined,
8889
cspDetails: types.CSP | undefined;
8990

9091
try {
9192
[licenses, ensName, r1Balance, publicProfileInfo, cspDetails] = await Promise.all([
92-
getLicenses(ownerEthAddr),
93+
cachedGetLicenses(ownerEthAddr),
9394
cachedGetENSName(ownerEthAddr),
9495
fetchErc20Balance(ownerEthAddr, config.r1ContractAddress),
9596
getCachedNodeOperatorProfile(ownerEthAddr as types.EthAddress),
@@ -133,7 +134,7 @@ export default async function NodeOperatorPage({ params }) {
133134
value={
134135
<div className="text-primary">
135136
{fBI(
136-
licenses.reduce((acc, license) => acc + license.totalClaimedAmount, 0n),
137+
licenses.reduce((acc, license) => acc + BigInt(license.totalClaimedAmount), 0n),
137138
18,
138139
)}
139140
</div>
@@ -148,9 +149,12 @@ export default async function NodeOperatorPage({ params }) {
148149
value={
149150
<div className="w-full min-w-52 xs:min-w-56 md:min-w-60">
150151
<UsageStats
151-
totalClaimedAmount={licenses.reduce((acc, license) => acc + license.totalClaimedAmount, 0n)}
152+
totalClaimedAmount={licenses.reduce(
153+
(acc, license) => acc + BigInt(license.totalClaimedAmount),
154+
0n,
155+
)}
152156
totalAssignedAmount={licenses.reduce(
153-
(acc, license) => acc + license.totalAssignedAmount,
157+
(acc, license) => acc + BigInt(license.totalAssignedAmount),
154158
0n,
155159
)}
156160
/>

app/cloud-service-providers/loading.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PAGE_SIZE } from '@/lib/api';
12
import { Skeleton } from '@heroui/skeleton';
23

34
export default function Loading() {
@@ -8,12 +9,11 @@ export default function Loading() {
89
<div className="col w-full gap-2">
910
<Skeleton className="only-lg min-h-[56px] w-full rounded-xl" />
1011

11-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
12-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
13-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
14-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
15-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
16-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
12+
{Array(PAGE_SIZE)
13+
.fill(null)
14+
.map((_, index) => (
15+
<Skeleton key={index} className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
16+
))}
1717
</div>
1818
</div>
1919
);

app/cloud-service-providers/page.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,22 @@ import CSPsList from '../server-components/CPSs/CSPsList';
66
import { BorderedCard } from '../server-components/shared/cards/BorderedCard';
77
import { CardHorizontal } from '../server-components/shared/cards/CardHorizontal';
88

9-
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
10-
const resolvedSearchParams = await searchParams;
11-
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
12-
const canonical =
13-
Number.isFinite(pageParam) && pageParam > 1
14-
? `/cloud-service-providers?page=${pageParam}`
15-
: '/cloud-service-providers';
16-
17-
return {
18-
title: 'Cloud Service Providers',
19-
openGraph: {
20-
title: 'Cloud Service Providers',
21-
},
22-
alternates: {
23-
canonical,
24-
},
25-
};
26-
}
9+
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
10+
const resolvedSearchParams = await searchParams;
11+
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
12+
const canonical =
13+
Number.isFinite(pageParam) && pageParam > 1 ? `/cloud-service-providers?page=${pageParam}` : '/cloud-service-providers';
14+
15+
return {
16+
title: 'Cloud Service Providers',
17+
openGraph: {
18+
title: 'Cloud Service Providers',
19+
},
20+
alternates: {
21+
canonical,
22+
},
23+
};
24+
}
2725

2826
export default async function CSPsPage(props: {
2927
searchParams?: Promise<{

app/licenses/loading.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PAGE_SIZE } from '@/lib/api';
12
import { Skeleton } from '@heroui/skeleton';
23

34
export default function Loading() {
@@ -8,10 +9,11 @@ export default function Loading() {
89
<div className="col w-full gap-2">
910
<Skeleton className="only-lg min-h-[56px] w-full rounded-2xl" />
1011

11-
<Skeleton className="min-h-[92px] w-full rounded-2xl" />
12-
<Skeleton className="min-h-[92px] w-full rounded-2xl" />
13-
<Skeleton className="min-h-[92px] w-full rounded-2xl" />
14-
<Skeleton className="min-h-[92px] w-full rounded-2xl" />
12+
{Array(PAGE_SIZE)
13+
.fill(null)
14+
.map((_, index) => (
15+
<Skeleton key={index} className="min-h-[92px] w-full rounded-2xl" />
16+
))}
1517
</div>
1618
</div>
1719
);

app/licenses/page.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import ErrorComponent from '@/app/server-components/shared/ErrorComponent';
22
import { getLicensesTotalSupply } from '@/lib/api/blockchain';
33
import { LicenseItem } from '@/typedefs/general';
4+
import { unstable_cache } from 'next/cache';
45
import List from '../server-components/Licenses/List';
56
import { BorderedCard } from '../server-components/shared/cards/BorderedCard';
67
import { CardHorizontal } from '../server-components/shared/cards/CardHorizontal';
78

8-
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
9-
const resolvedSearchParams = await searchParams;
10-
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
11-
const canonical = Number.isFinite(pageParam) && pageParam > 1 ? `/licenses?page=${pageParam}` : '/licenses';
12-
13-
return {
14-
title: 'Licenses',
15-
openGraph: {
16-
title: 'Licenses',
17-
},
18-
alternates: {
19-
canonical,
20-
},
21-
};
22-
}
9+
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
10+
const resolvedSearchParams = await searchParams;
11+
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
12+
const canonical = Number.isFinite(pageParam) && pageParam > 1 ? `/licenses?page=${pageParam}` : '/licenses';
13+
14+
return {
15+
title: 'Licenses',
16+
openGraph: {
17+
title: 'Licenses',
18+
},
19+
alternates: {
20+
canonical,
21+
},
22+
};
23+
}
24+
25+
const getCachedSupply = unstable_cache(getLicensesTotalSupply, ['licenses-total-supply'], { revalidate: 300 });
2326

2427
export default async function LicensesPage(props: {
2528
searchParams?: Promise<{
@@ -29,11 +32,15 @@ export default async function LicensesPage(props: {
2932
const searchParams = await props.searchParams;
3033
const currentPage = Number(searchParams?.page) || 1;
3134

32-
let ndTotalSupply: bigint, mndTotalSupply: bigint;
35+
let ndTotalSupply: number, mndTotalSupply: number;
3336
let licenses: LicenseItem[];
3437

3538
try {
36-
({ ndTotalSupply, mndTotalSupply } = await getLicensesTotalSupply());
39+
const { ndTotalSupply: ndTotalSupplyStr, mndTotalSupply: mndTotalSupplyStr } = await getCachedSupply();
40+
41+
ndTotalSupply = Number(ndTotalSupplyStr);
42+
mndTotalSupply = Number(mndTotalSupplyStr);
43+
3744
licenses = [
3845
...Array.from({ length: Number(mndTotalSupply) }, (_, i) => ({
3946
licenseId: i + 1,
@@ -59,12 +66,12 @@ export default async function LicensesPage(props: {
5966
<div className="flexible-row">
6067
<CardHorizontal
6168
label="Total"
62-
value={Number(ndTotalSupply + mndTotalSupply)}
69+
value={ndTotalSupply + mndTotalSupply}
6370
isFlexible
6471
widthClasses="min-w-[192px]"
6572
/>
66-
<CardHorizontal label="ND" value={Number(ndTotalSupply)} isFlexible widthClasses="min-w-[192px]" />
67-
<CardHorizontal label="MND" value={Number(mndTotalSupply)} isFlexible widthClasses="min-w-[192px]" />
73+
<CardHorizontal label="ND" value={ndTotalSupply} isFlexible widthClasses="min-w-[192px]" />
74+
<CardHorizontal label="MND" value={mndTotalSupply} isFlexible widthClasses="min-w-[192px]" />
6875
</div>
6976
</BorderedCard>
7077
</div>

app/loading.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PAGE_SIZE } from '@/lib/api';
12
import { Skeleton } from '@heroui/skeleton';
23

34
export default function Loading() {
@@ -8,12 +9,11 @@ export default function Loading() {
89
<div className="col w-full gap-2">
910
<Skeleton className="only-lg min-h-[56px] w-full rounded-xl" />
1011

11-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
12-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
13-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
14-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
15-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
16-
<Skeleton className="min-h-[84px] w-full rounded-2xl" />
12+
{Array(PAGE_SIZE)
13+
.fill(null)
14+
.map((_, index) => (
15+
<Skeleton key={index} className="min-h-[84px] w-full rounded-2xl" />
16+
))}
1717
</div>
1818
</div>
1919
);

app/node-operators/loading.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PAGE_SIZE } from '@/lib/api';
12
import { Skeleton } from '@heroui/skeleton';
23

34
export default function Loading() {
@@ -8,12 +9,11 @@ export default function Loading() {
89
<div className="col w-full gap-2">
910
<Skeleton className="only-lg min-h-[56px] w-full rounded-xl" />
1011

11-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
12-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
13-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
14-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
15-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
16-
<Skeleton className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
12+
{Array(PAGE_SIZE)
13+
.fill(null)
14+
.map((_, index) => (
15+
<Skeleton key={index} className="min-h-[68px] w-full rounded-2xl lg:min-h-[60px]" />
16+
))}
1717
</div>
1818
</div>
1919
);

app/node-operators/page.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@ import List from '../server-components/NodeOperators/List';
77
import { BorderedCard } from '../server-components/shared/cards/BorderedCard';
88
import { CardHorizontal } from '../server-components/shared/cards/CardHorizontal';
99

10-
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
11-
const resolvedSearchParams = await searchParams;
12-
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
13-
const canonical =
14-
Number.isFinite(pageParam) && pageParam > 1 ? `/node-operators?page=${pageParam}` : '/node-operators';
15-
16-
return {
17-
title: 'Node Operators',
18-
openGraph: {
19-
title: 'Node Operators',
20-
},
21-
alternates: {
22-
canonical,
23-
},
24-
};
25-
}
10+
export async function generateMetadata({ searchParams }: { searchParams?: Promise<{ page?: string }> }) {
11+
const resolvedSearchParams = await searchParams;
12+
const pageParam = Number.parseInt(resolvedSearchParams?.page ?? '', 10);
13+
const canonical = Number.isFinite(pageParam) && pageParam > 1 ? `/node-operators?page=${pageParam}` : '/node-operators';
14+
15+
return {
16+
title: 'Node Operators',
17+
openGraph: {
18+
title: 'Node Operators',
19+
},
20+
alternates: {
21+
canonical,
22+
},
23+
};
24+
}
2625

2726
const fetchLicenseHolders = async (environment: 'mainnet' | 'testnet' | 'devnet') => {
2827
const url = await getSSURL(`license-holders?env=${environment}`);

0 commit comments

Comments
 (0)