Skip to content

Commit 50af773

Browse files
authored
feat/improve T22 account page (#534)
## Description This PR displays `Token Extensions` for other accounts. ## Type of change - [x] New feature ## Screenshots <img width="732" alt="image" src="https://github.com/user-attachments/assets/44de309c-f2cf-43f9-9f29-19391be764e5" /> <img width="739" alt="image" src="https://github.com/user-attachments/assets/d24eacd5-2715-4683-9667-6c2c8808f2cc" /> ## Testing `pnpm dev` > Visit links below Token Account: http://localhost:3000/address/7owTEfBdJ2nEdxaUZ2Hm6XevkG7FqHgmeVhkZbUTCCQT/token-extensions Multisig Account: http://localhost:3000/address/FtrZqgM9begkaLHq3f4hGEUrAK1jjJpkc8hZ1hVk3EDN/token-extensions ## Related Issues Relates to: #516 ## Checklist - [x] My code follows the project's style guidelines - [x] I have added tests that prove my fix/feature works - [x] All tests pass locally and in CI - [x] CI/CD checks pass - [x] I have included screenshots for protocol screens (if applicable) <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Add feature to display token extensions for accounts, handling different account types and rendering logic in `page-client.tsx` and `TokenExtensionsCard.tsx`. > > - **Behavior**: > - Displays `Token Extensions` for accounts in `page-client.tsx` using `TokenExtensionsCard`. > - Handles `mint` and `account` types, rendering `TokenExtensionsCard` if extensions exist, otherwise shows `NoResults`. > - Uses `ErrorBoundary` to handle errors in `TokenExtensionsEntriesPageClient`. > - **Components**: > - `TokenExtensionsCard` in `TokenExtensionsCard.tsx` now fetches token info and displays extensions using `TokenExtensionsSection`. > - Introduces `populateTokenExtensions()` to parse and sort extensions. > - **Misc**: > - Removes unused `epoch` variable in `TokenAccountSection.tsx`. > - Updates `TokenExtensionsPage` in `page.tsx` to use `TokenExtensionsEntriesPageClient`. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=solana-foundation%2Fexplorer&utm_source=github&utm_medium=referral)<sup> for f41dd7e. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent e8c6ead commit 50af773

File tree

4 files changed

+75
-22
lines changed

4 files changed

+75
-22
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
3+
import { ParsedAccountRenderer } from '@components/account/ParsedAccountRenderer';
4+
import React from 'react';
5+
import { ErrorBoundary } from 'react-error-boundary';
6+
import { create } from 'superstruct';
7+
8+
import { TokenExtensionsCard } from '@/app/components/account/TokenExtensionsCard';
9+
import { ErrorCard } from '@/app/components/common/ErrorCard';
10+
import { MintAccountInfo, TokenAccountInfo } from '@/app/validators/accounts/token';
11+
12+
export type Props = Readonly<{
13+
params: {
14+
address: string;
15+
};
16+
}>;
17+
18+
function TokenExtensionsEntriesRenderer({
19+
account,
20+
}: React.ComponentProps<React.ComponentProps<typeof ParsedAccountRenderer>['renderComponent']>) {
21+
const parsedData = account?.data.parsed;
22+
23+
if (parsedData && parsedData.parsed.type === 'mint') {
24+
const mintInfo = create(parsedData.parsed.info, MintAccountInfo);
25+
const address = account.pubkey.toBase58();
26+
27+
if (!mintInfo.extensions?.length) return <ErrorCard text="Can not fetch extensions." />;
28+
29+
return <TokenExtensionsCard address={address} extensions={mintInfo.extensions} />;
30+
} else if (parsedData && parsedData.parsed.type === 'account') {
31+
const accountInfo = create(parsedData.parsed.info, TokenAccountInfo);
32+
const address = accountInfo.mint.toBase58();
33+
34+
if (!accountInfo.extensions?.length) return <ErrorCard text="Can not fetch extensions." />;
35+
36+
return <TokenExtensionsCard address={address} extensions={accountInfo.extensions} />;
37+
} else {
38+
// possible cases:
39+
// - absent parsed data
40+
// - multisig type of account
41+
return <ErrorCard text="Can not fetch extensions." />;
42+
}
43+
}
44+
45+
export default function TokenExtensionsEntriesPageClient({ params: { address } }: Props) {
46+
return (
47+
<ErrorBoundary fallback={<ErrorCard text="Can not fetch extensions." />}>
48+
<ParsedAccountRenderer address={address} renderComponent={TokenExtensionsEntriesRenderer} />
49+
</ErrorBoundary>
50+
);
51+
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import './styles.css';
22

3-
import { TokenExtensionsCard } from '@components/account/TokenExtensionsCard';
43
import getReadableTitleFromAddress, { AddressPageMetadataProps } from '@utils/get-readable-title-from-address';
54
import { Metadata } from 'next/types';
65

7-
type Props = Readonly<{
8-
params: {
9-
address: string;
10-
};
11-
}>;
6+
import TokenExtensionsEntriesPageClient, { Props } from './page-client';
127

138
export async function generateMetadata(props: AddressPageMetadataProps): Promise<Metadata> {
149
return {
@@ -17,6 +12,6 @@ export async function generateMetadata(props: AddressPageMetadataProps): Promise
1712
};
1813
}
1914

20-
export default function TokenExtensionsPage({ params: { address: _address } }: Props) {
21-
return <TokenExtensionsCard address={_address} />;
15+
export default function TokenExtensionsPage(props: Props) {
16+
return <TokenExtensionsEntriesPageClient {...props} />;
2217
}

app/components/account/TokenAccountSection.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,7 @@ async function fetchTokenInfo([_, address, cluster, url]: ['get-token-info', str
400400

401401
function TokenAccountCard({ account, info }: { account: Account; info: TokenAccountInfo }) {
402402
const refresh = useFetchAccountInfo();
403-
const { cluster, clusterInfo, url } = useCluster();
404-
const epoch = clusterInfo?.epochInfo.epoch;
403+
const { cluster, url } = useCluster();
405404
const label = addressLabel(account.pubkey.toBase58(), cluster);
406405
const swrKey = useMemo(() => getTokenInfoSwrKey(info.mint.toString(), cluster, url), [cluster, info.mint, url]);
407406

@@ -513,9 +512,6 @@ function TokenAccountCard({ account, info }: { account: Account; info: TokenAcco
513512
</tr>
514513
</>
515514
)}
516-
{accountExtensions?.map(extension =>
517-
TokenExtensionRow(extension, epoch, info.tokenAmount.decimals, symbol)
518-
)}
519515
</TableCardBody>
520516
</div>
521517
);

app/components/account/TokenExtensionsCard.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { AccountHeader } from '@components/common/Account';
4-
import { useFetchAccountInfo, useMintAccountInfo } from '@providers/accounts';
4+
import { useFetchAccountInfo } from '@providers/accounts';
55
import { useCluster } from '@providers/cluster';
66
import { PublicKey } from '@solana/web3.js';
77
import { Cluster } from '@utils/cluster';
@@ -11,33 +11,44 @@ import useSWR from 'swr';
1111
import { getTokenInfo, getTokenInfoSwrKey } from '@/app/utils/token-info';
1212
import { TokenExtension, TokenExtensionType } from '@/app/validators/accounts/token-extension';
1313

14+
import { LoadingCard } from '../common/LoadingCard';
1415
import { TokenExtensionsSection } from './TokenExtensionsSection';
1516
import { ParsedTokenExtension } from './types';
1617

1718
async function fetchTokenInfo([_, address, cluster, url]: ['get-token-info', string, Cluster, string]) {
1819
return await getTokenInfo(new PublicKey(address), cluster, url);
1920
}
2021

21-
export function TokenExtensionsCard({ address }: { address: string }) {
22+
export function TokenExtensionsCard({
23+
address,
24+
extensions: mintExtensions,
25+
}: {
26+
address: string;
27+
extensions: TokenExtension[];
28+
}) {
2229
const { cluster, url } = useCluster();
2330
const refresh = useFetchAccountInfo();
24-
const mintInfo = useMintAccountInfo(address);
2531
const swrKey = useMemo(() => getTokenInfoSwrKey(address, cluster, url), [address, cluster, url]);
26-
const { data: tokenInfo } = useSWR(swrKey, fetchTokenInfo);
32+
const { data: tokenInfo, isLoading } = useSWR(swrKey, fetchTokenInfo);
2733

28-
if (!mintInfo || !mintInfo.extensions) return null;
34+
const extensions = populateTokenExtensions(mintExtensions);
2935

30-
const extensions = populateTokenExtensions(mintInfo.extensions);
36+
// check for nullish decimals to satisty constraint for required decimals.
37+
if (isLoading) {
38+
return <LoadingCard />;
39+
} else if (!tokenInfo || tokenInfo.decimals === null) {
40+
throw new Error('Can not fetch token info.');
41+
}
3142

3243
return (
3344
<div className="card">
3445
<AccountHeader title="Extensions" refresh={() => refresh(new PublicKey(address), 'parsed')} />
3546
<div className="card-body p-0 e-overflow-x-scroll">
3647
<TokenExtensionsSection
37-
decimals={mintInfo.decimals}
38-
extensions={mintInfo.extensions}
48+
decimals={tokenInfo.decimals}
49+
extensions={mintExtensions}
3950
parsedExtensions={extensions}
40-
symbol={tokenInfo?.symbol}
51+
symbol={tokenInfo.symbol}
4152
/>
4253
</div>
4354
</div>

0 commit comments

Comments
 (0)