Skip to content

Commit 48f8aba

Browse files
committed
Dashboard: Migrate contract overview page components to tailwind/shadcn #1
1 parent f2abc8b commit 48f8aba

File tree

22 files changed

+686
-801
lines changed

22 files changed

+686
-801
lines changed

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
3030
description?: string;
3131
titleClassName?: string;
3232
};
33+
customHeader?: React.ReactNode;
3334
// chart config
3435
config: TConfig;
3536
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
@@ -60,6 +61,8 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
6061
</CardHeader>
6162
)}
6263

64+
{props.customHeader && props.customHeader}
65+
6366
<CardContent className={cn(!props.header && "pt-6")}>
6467
<ChartContainer config={props.config} className={props.chartClassName}>
6568
{props.isPending ? (
Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
import {
2-
Flex,
3-
GridItem,
4-
SimpleGrid,
5-
Skeleton,
6-
SkeletonText,
7-
useBreakpointValue,
8-
} from "@chakra-ui/react";
1+
import { SkeletonContainer } from "@/components/ui/skeleton";
2+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
93
import { useMemo } from "react";
104
import { type NFT, ZERO_ADDRESS } from "thirdweb";
11-
import { Card, TrackedLink, type TrackedLinkProps } from "tw-components";
125
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
136

147
type NFTWithContract = NFT & { contractAddress: string; chainId: number };
@@ -20,7 +13,8 @@ const dummyMetadata: (idx: number) => NFTWithContract = (idx) => ({
2013
tokenURI: `1-0x123-${idx}`,
2114
metadata: {
2215
name: "Loading...",
23-
description: "lorem ipsum loading sit amet",
16+
description:
17+
"lorem ipsum loading sit amet consectetur adipisicing elit. Quisquam, quos.",
2418
id: BigInt(idx || 0),
2519
uri: `1-0x123-${idx}`,
2620
},
@@ -31,7 +25,7 @@ const dummyMetadata: (idx: number) => NFTWithContract = (idx) => ({
3125

3226
interface NFTCardsProps {
3327
nfts: Array<NFTWithContract>;
34-
trackingCategory: TrackedLinkProps["category"];
28+
trackingCategory: string;
3529
isPending: boolean;
3630
allNfts?: boolean;
3731
}
@@ -42,61 +36,82 @@ export const NFTCards: React.FC<NFTCardsProps> = ({
4236
isPending,
4337
allNfts,
4438
}) => {
45-
const isMobile = useBreakpointValue({ base: true, md: false });
46-
4739
const dummyData = useMemo(() => {
4840
return Array.from({
49-
length: allNfts ? nfts.length : isMobile ? 2 : 3,
41+
length: allNfts ? nfts.length : 3,
5042
}).map((_, idx) => dummyMetadata(idx));
51-
}, [nfts.length, isMobile, allNfts]);
43+
}, [nfts.length, allNfts]);
5244

5345
return (
54-
<SimpleGrid
55-
gap={{ base: 3, md: 6 }}
56-
columns={allNfts ? { base: 2, md: 4 } : { base: 2, md: 3 }}
57-
>
46+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
5847
{(isPending ? dummyData : nfts).map((token) => {
5948
const tokenId = token.id.toString();
6049

6150
return (
62-
<GridItem
51+
<div
6352
key={`${token.chainId}_${token.contractAddress}_${tokenId}`}
64-
as={TrackedLink}
65-
category={trackingCategory}
66-
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
67-
_hover={{ opacity: 0.75, textDecoration: "none" }}
53+
className="hover:-translate-y-0.5 relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg bg-background duration-200 hover:scale-[1.01]"
6854
>
69-
<Card p={0} h="full">
70-
<div className="relative aspect-square w-full overflow-hidden rounded-xl">
71-
<Skeleton isLoaded={!isPending}>
72-
<NFTMediaWithEmptyState
73-
metadata={token.metadata}
74-
requireInteraction
75-
width="100%"
76-
height="100%"
77-
/>
78-
</Skeleton>
79-
</div>
80-
<Flex p={4} pb={3} gap={1} direction="column">
81-
<Skeleton w={!isPending ? "100%" : "50%"} isLoaded={!isPending}>
82-
<h2 className="font-semibold tracking-tight">
83-
{token.metadata.name}
84-
</h2>
85-
</Skeleton>
86-
<SkeletonText isLoaded={!isPending}>
87-
<p className="line-clamp-3 text-muted-foreground text-sm">
88-
{token.metadata.description ? (
89-
token.metadata.description
90-
) : (
91-
<i>No description</i>
92-
)}
93-
</p>
94-
</SkeletonText>
95-
</Flex>
96-
</Card>
97-
</GridItem>
55+
{/* border */}
56+
<div className="absolute inset-0 rounded-lg border border-border" />
57+
58+
{/* image */}
59+
<div className="relative aspect-square w-full overflow-hidden">
60+
<SkeletonContainer
61+
skeletonData={token.metadata}
62+
loadedData={isPending ? undefined : token.metadata}
63+
className="h-full w-full rounded-lg"
64+
render={(v) => {
65+
return (
66+
<NFTMediaWithEmptyState
67+
metadata={v}
68+
requireInteraction
69+
width="100%"
70+
height="100%"
71+
/>
72+
);
73+
}}
74+
/>
75+
</div>
76+
77+
<div className="p-4">
78+
{/* title */}
79+
<SkeletonContainer
80+
skeletonData={token.metadata}
81+
loadedData={isPending ? undefined : token.metadata}
82+
className="mb-2"
83+
render={(v) => {
84+
return (
85+
<h2 className="font-semibold tracking-tight">
86+
<TrackedLinkTW
87+
category={trackingCategory}
88+
label="view_nft"
89+
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
90+
className="before:absolute before:inset-0"
91+
>
92+
{v.name}
93+
</TrackedLinkTW>
94+
</h2>
95+
);
96+
}}
97+
/>
98+
99+
{/* Description */}
100+
<SkeletonContainer
101+
skeletonData={token.metadata}
102+
loadedData={isPending ? undefined : token.metadata}
103+
render={(v) => {
104+
return (
105+
<p className="line-clamp-3 text-muted-foreground text-sm">
106+
{v.description ? v.description : <i>No description</i>}
107+
</p>
108+
);
109+
}}
110+
/>
111+
</div>
112+
</div>
98113
);
99114
})}
100-
</SimpleGrid>
115+
</div>
101116
);
102117
};

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/supply-cards.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
totalSupply,
88
} from "thirdweb/extensions/erc721";
99
import { useReadContract } from "thirdweb/react";
10-
import { StatCard } from "../../overview/components/stat-card";
10+
import { Stat } from "../../overview/components/stat-card";
1111

1212
interface SupplyCardsProps {
1313
contract: ThirdwebContract;
@@ -36,17 +36,17 @@ export const SupplyCards: React.FC<SupplyCardsProps> = ({ contract }) => {
3636

3737
return (
3838
<div className="flex flex-row gap-3 md:gap-6 [&>*]:grow">
39-
<StatCard
39+
<Stat
4040
value={realTotalSupply.toString()}
4141
label="Total Supply"
4242
isPending={nextTokenIdQuery.isPending}
4343
/>
44-
<StatCard
44+
<Stat
4545
value={totalSupplyQuery?.data?.toString() || "N/A"}
4646
label="Claimed Supply"
4747
isPending={totalSupplyQuery.isPending}
4848
/>
49-
<StatCard
49+
<Stat
5050
value={unclaimedSupply}
5151
label="Unclaimed Supply"
5252
isPending={totalSupplyQuery.isPending || nextTokenIdQuery.isPending}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/ContractOverviewPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { ThirdwebContract } from "thirdweb";
2-
import { AnalyticsOverview } from "./components/Analytics";
2+
import { TokenDetailsCard } from "../tokens/components/supply";
3+
import { ContractAnalyticsOverviewCard } from "./components/Analytics";
34
import { BuildYourApp } from "./components/BuildYourApp";
45
import { ContractChecklist } from "./components/ContractChecklist";
56
import { LatestEvents } from "./components/LatestEvents";
67
import { MarketplaceDetails } from "./components/MarketplaceDetails";
78
import { NFTDetails } from "./components/NFTDetails";
89
import { PermissionsTable } from "./components/PermissionsTable";
9-
import { TokenDetails } from "./components/TokenDetails";
1010

1111
interface ContractOverviewPageProps {
1212
contract: ThirdwebContract;
@@ -39,7 +39,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
3939
}) => {
4040
return (
4141
<div className="flex flex-col gap-8 lg:flex-row">
42-
<div className="flex flex-col gap-16">
42+
<div className="flex grow flex-col gap-10">
4343
<ContractChecklist
4444
isErc721={isErc721}
4545
isErc1155={isErc1155}
@@ -50,7 +50,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
5050
/>
5151

5252
{isAnalyticsSupported && (
53-
<AnalyticsOverview
53+
<ContractAnalyticsOverviewCard
5454
contractAddress={contract.address}
5555
chainId={contract.chain.id}
5656
trackingCategory={TRACKING_CATEGORY}
@@ -77,7 +77,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
7777
/>
7878
)}
7979

80-
{isErc20 && <TokenDetails contract={contract} />}
80+
{isErc20 && <TokenDetailsCard contract={contract} />}
8181

8282
<LatestEvents
8383
contract={contract}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx

Lines changed: 51 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@ import { ArrowRightIcon } from "lucide-react";
1212
import Link from "next/link";
1313
import { useMemo, useState } from "react";
1414

15-
interface AnalyticsOverviewProps {
16-
chainId: number;
15+
function getDayKey(date: Date) {
16+
return date.toISOString().split("T")[0];
17+
}
18+
19+
export function ContractAnalyticsOverviewCard(props: {
1720
contractAddress: string;
21+
chainId: number;
1822
trackingCategory: string;
1923
chainSlug: string;
20-
}
21-
22-
export const AnalyticsOverview: React.FC<AnalyticsOverviewProps> = ({
23-
chainId,
24-
contractAddress,
25-
trackingCategory,
26-
chainSlug,
27-
}) => {
24+
}) {
2825
const trackEvent = useTrack();
2926
const [startDate] = useState(
3027
(() => {
@@ -35,55 +32,27 @@ export const AnalyticsOverview: React.FC<AnalyticsOverviewProps> = ({
3532
);
3633
const [endDate] = useState(new Date());
3734

38-
return (
39-
<div className="relative">
40-
<div className="mb-4 flex items-center justify-between gap-4">
41-
<h2 className="font-semibold text-2xl tracking-tight">Analytics</h2>
42-
<Button
43-
asChild
44-
className="gap-1"
45-
size="sm"
46-
variant="outline"
47-
onClick={() => {
48-
trackEvent({
49-
category: trackingCategory,
50-
action: "click",
51-
label: "view_all_analytics",
52-
});
53-
}}
54-
>
55-
<Link href={`/${chainSlug}/${contractAddress}/analytics`}>
56-
<span>View All</span>
57-
<ArrowRightIcon className="size-4" />
58-
</Link>
59-
</Button>
60-
</div>
61-
62-
<OverviewAnalytics
63-
chainId={chainId}
64-
contractAddress={contractAddress}
65-
endDate={endDate}
66-
startDate={startDate}
67-
/>
68-
</div>
69-
);
70-
};
35+
const wallets = useContractUniqueWalletAnalytics({
36+
chainId: props.chainId,
37+
contractAddress: props.contractAddress,
38+
startDate,
39+
endDate,
40+
});
7141

72-
type ChartProps = {
73-
contractAddress: string;
74-
chainId: number;
75-
startDate: Date;
76-
endDate: Date;
77-
};
42+
const transactions = useContractTransactionAnalytics({
43+
chainId: props.chainId,
44+
contractAddress: props.contractAddress,
45+
startDate,
46+
endDate,
47+
});
7848

79-
function getDayKey(date: Date) {
80-
return date.toISOString().split("T")[0];
81-
}
49+
const events = useContractEventAnalytics({
50+
chainId: props.chainId,
51+
contractAddress: props.contractAddress,
52+
startDate,
53+
endDate,
54+
});
8255

83-
function OverviewAnalytics(props: ChartProps) {
84-
const wallets = useContractUniqueWalletAnalytics(props);
85-
const transactions = useContractTransactionAnalytics(props);
86-
const events = useContractEventAnalytics(props);
8756
const isPending =
8857
wallets.isPending || transactions.isPending || events.isPending;
8958

@@ -135,7 +104,32 @@ function OverviewAnalytics(props: ChartProps) {
135104
data={mergedData || []}
136105
isPending={isPending}
137106
showLegend
138-
chartClassName="aspect-[1.5] lg:aspect-[3.5]"
107+
chartClassName="aspect-[1.5] lg:aspect-[3]"
108+
customHeader={
109+
<div className="flex items-center justify-between gap-4 border-b p-6 py-4">
110+
<h2 className="font-semibold text-xl tracking-tight">Analytics</h2>
111+
<Button
112+
asChild
113+
className="gap-2 bg-background text-muted-foreground"
114+
size="sm"
115+
variant="outline"
116+
onClick={() => {
117+
trackEvent({
118+
category: props.trackingCategory,
119+
action: "click",
120+
label: "view_all_analytics",
121+
});
122+
}}
123+
>
124+
<Link
125+
href={`/${props.chainSlug}/${props.contractAddress}/analytics`}
126+
>
127+
<span>View All</span>
128+
<ArrowRightIcon className="size-4" />
129+
</Link>
130+
</Button>
131+
</div>
132+
}
139133
/>
140134
);
141135
}

0 commit comments

Comments
 (0)