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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
description?: string;
titleClassName?: string;
};
customHeader?: React.ReactNode;
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
Expand Down Expand Up @@ -60,6 +61,8 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
</CardHeader>
)}

{props.customHeader && props.customHeader}

<CardContent className={cn(!props.header && "pt-6")}>
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import {
Flex,
GridItem,
SimpleGrid,
Skeleton,
SkeletonText,
useBreakpointValue,
} from "@chakra-ui/react";
import { SkeletonContainer } from "@/components/ui/skeleton";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { useMemo } from "react";
import { type NFT, ZERO_ADDRESS } from "thirdweb";
import { Card, TrackedLink, type TrackedLinkProps } from "tw-components";
import { NFTMediaWithEmptyState } from "tw-components/nft-media";

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

interface NFTCardsProps {
nfts: Array<NFTWithContract>;
trackingCategory: TrackedLinkProps["category"];
trackingCategory: string;
isPending: boolean;
allNfts?: boolean;
}
Expand All @@ -42,61 +36,82 @@ export const NFTCards: React.FC<NFTCardsProps> = ({
isPending,
allNfts,
}) => {
const isMobile = useBreakpointValue({ base: true, md: false });

const dummyData = useMemo(() => {
return Array.from({
length: allNfts ? nfts.length : isMobile ? 2 : 3,
length: allNfts ? nfts.length : 3,
}).map((_, idx) => dummyMetadata(idx));
}, [nfts.length, isMobile, allNfts]);
}, [nfts.length, allNfts]);

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

return (
<GridItem
<div
key={`${token.chainId}_${token.contractAddress}_${tokenId}`}
as={TrackedLink}
category={trackingCategory}
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
_hover={{ opacity: 0.75, textDecoration: "none" }}
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]"
>
<Card p={0} h="full">
<div className="relative aspect-square w-full overflow-hidden rounded-xl">
<Skeleton isLoaded={!isPending}>
<NFTMediaWithEmptyState
metadata={token.metadata}
requireInteraction
width="100%"
height="100%"
/>
</Skeleton>
</div>
<Flex p={4} pb={3} gap={1} direction="column">
<Skeleton w={!isPending ? "100%" : "50%"} isLoaded={!isPending}>
<h2 className="font-semibold tracking-tight">
{token.metadata.name}
</h2>
</Skeleton>
<SkeletonText isLoaded={!isPending}>
<p className="line-clamp-3 text-muted-foreground text-sm">
{token.metadata.description ? (
token.metadata.description
) : (
<i>No description</i>
)}
</p>
</SkeletonText>
</Flex>
</Card>
</GridItem>
{/* border */}
<div className="absolute inset-0 rounded-lg border border-border" />

{/* image */}
<div className="relative aspect-square w-full overflow-hidden">
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
className="h-full w-full rounded-lg"
render={(v) => {
return (
<NFTMediaWithEmptyState
metadata={v}
requireInteraction
width="100%"
height="100%"
/>
);
}}
/>
</div>

<div className="p-4">
{/* title */}
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
className="mb-2"
render={(v) => {
return (
<h2 className="font-semibold tracking-tight">
<TrackedLinkTW
category={trackingCategory}
label="view_nft"
href={`/${token.chainId}/${token.contractAddress}/nfts/${tokenId}`}
className="before:absolute before:inset-0"
>
{v.name}
</TrackedLinkTW>
</h2>
);
}}
/>

{/* Description */}
<SkeletonContainer
skeletonData={token.metadata}
loadedData={isPending ? undefined : token.metadata}
render={(v) => {
return (
<p className="line-clamp-3 text-muted-foreground text-sm">
{v.description ? v.description : <i>No description</i>}
</p>
);
}}
/>
</div>
</div>
);
})}
</SimpleGrid>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
totalSupply,
} from "thirdweb/extensions/erc721";
import { useReadContract } from "thirdweb/react";
import { StatCard } from "../../overview/components/stat-card";
import { Stat } from "../../overview/components/stat-card";

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

return (
<div className="flex flex-row gap-3 md:gap-6 [&>*]:grow">
<StatCard
<Stat
value={realTotalSupply.toString()}
label="Total Supply"
isPending={nextTokenIdQuery.isPending}
/>
<StatCard
<Stat
value={totalSupplyQuery?.data?.toString() || "N/A"}
label="Claimed Supply"
isPending={totalSupplyQuery.isPending}
/>
<StatCard
<Stat
value={unclaimedSupply}
label="Unclaimed Supply"
isPending={totalSupplyQuery.isPending || nextTokenIdQuery.isPending}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { ThirdwebContract } from "thirdweb";
import { AnalyticsOverview } from "./components/Analytics";
import { TokenDetailsCard } from "../tokens/components/supply";
import { ContractAnalyticsOverviewCard } from "./components/Analytics";
import { BuildYourApp } from "./components/BuildYourApp";
import { ContractChecklist } from "./components/ContractChecklist";
import { LatestEvents } from "./components/LatestEvents";
import { MarketplaceDetails } from "./components/MarketplaceDetails";
import { NFTDetails } from "./components/NFTDetails";
import { PermissionsTable } from "./components/PermissionsTable";
import { TokenDetails } from "./components/TokenDetails";

interface ContractOverviewPageProps {
contract: ThirdwebContract;
Expand Down Expand Up @@ -39,7 +39,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
}) => {
return (
<div className="flex flex-col gap-8 lg:flex-row">
<div className="flex flex-col gap-16">
<div className="flex grow flex-col gap-10">
<ContractChecklist
isErc721={isErc721}
isErc1155={isErc1155}
Expand All @@ -50,7 +50,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
/>

{isAnalyticsSupported && (
<AnalyticsOverview
<ContractAnalyticsOverviewCard
contractAddress={contract.address}
chainId={contract.chain.id}
trackingCategory={TRACKING_CATEGORY}
Expand All @@ -77,7 +77,7 @@ export const ContractOverviewPage: React.FC<ContractOverviewPageProps> = ({
/>
)}

{isErc20 && <TokenDetails contract={contract} />}
{isErc20 && <TokenDetailsCard contract={contract} />}

<LatestEvents
contract={contract}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@ import { ArrowRightIcon } from "lucide-react";
import Link from "next/link";
import { useMemo, useState } from "react";

interface AnalyticsOverviewProps {
chainId: number;
function getDayKey(date: Date) {
return date.toISOString().split("T")[0];
}

export function ContractAnalyticsOverviewCard(props: {
contractAddress: string;
chainId: number;
trackingCategory: string;
chainSlug: string;
}

export const AnalyticsOverview: React.FC<AnalyticsOverviewProps> = ({
chainId,
contractAddress,
trackingCategory,
chainSlug,
}) => {
}) {
const trackEvent = useTrack();
const [startDate] = useState(
(() => {
Expand All @@ -35,55 +32,27 @@ export const AnalyticsOverview: React.FC<AnalyticsOverviewProps> = ({
);
const [endDate] = useState(new Date());

return (
<div className="relative">
<div className="mb-4 flex items-center justify-between gap-4">
<h2 className="font-semibold text-2xl tracking-tight">Analytics</h2>
<Button
asChild
className="gap-1"
size="sm"
variant="outline"
onClick={() => {
trackEvent({
category: trackingCategory,
action: "click",
label: "view_all_analytics",
});
}}
>
<Link href={`/${chainSlug}/${contractAddress}/analytics`}>
<span>View All</span>
<ArrowRightIcon className="size-4" />
</Link>
</Button>
</div>

<OverviewAnalytics
chainId={chainId}
contractAddress={contractAddress}
endDate={endDate}
startDate={startDate}
/>
</div>
);
};
const wallets = useContractUniqueWalletAnalytics({
chainId: props.chainId,
contractAddress: props.contractAddress,
startDate,
endDate,
});

type ChartProps = {
contractAddress: string;
chainId: number;
startDate: Date;
endDate: Date;
};
const transactions = useContractTransactionAnalytics({
chainId: props.chainId,
contractAddress: props.contractAddress,
startDate,
endDate,
});

function getDayKey(date: Date) {
return date.toISOString().split("T")[0];
}
const events = useContractEventAnalytics({
chainId: props.chainId,
contractAddress: props.contractAddress,
startDate,
endDate,
});

function OverviewAnalytics(props: ChartProps) {
const wallets = useContractUniqueWalletAnalytics(props);
const transactions = useContractTransactionAnalytics(props);
const events = useContractEventAnalytics(props);
const isPending =
wallets.isPending || transactions.isPending || events.isPending;

Expand Down Expand Up @@ -135,7 +104,32 @@ function OverviewAnalytics(props: ChartProps) {
data={mergedData || []}
isPending={isPending}
showLegend
chartClassName="aspect-[1.5] lg:aspect-[3.5]"
chartClassName="aspect-[1.5] lg:aspect-[3]"
customHeader={
<div className="flex items-center justify-between gap-4 border-b p-6 py-4">
<h2 className="font-semibold text-xl tracking-tight">Analytics</h2>
<Button
asChild
className="gap-2 bg-background text-muted-foreground"
size="sm"
variant="outline"
onClick={() => {
trackEvent({
category: props.trackingCategory,
action: "click",
label: "view_all_analytics",
});
}}
>
<Link
href={`/${props.chainSlug}/${props.contractAddress}/analytics`}
>
<span>View All</span>
<ArrowRightIcon className="size-4" />
</Link>
</Button>
</div>
}
/>
);
}
Loading
Loading