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
158 changes: 96 additions & 62 deletions apps/dashboard/src/@/components/blocks/charts/area-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
"use client";

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
type ChartConfig,
ChartContainer,
Expand All @@ -15,8 +22,14 @@ import {
EmptyChartState,
LoadingChartState,
} from "../../../../components/analytics/empty-chart-state";
import { cn } from "../../../lib/utils";

type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
header?: {
title: string;
description?: string;
titleClassName?: string;
};
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
Expand All @@ -25,78 +38,99 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
// chart className
chartClassName?: string;
isPending: boolean;
hideLabel?: boolean;
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
};

export function ThirdwebAreaChart<TConfig extends ChartConfig>(
props: ThirdwebAreaChartProps<TConfig>,
) {
const configKeys = useMemo(() => Object.keys(props.config), [props.config]);

return (
<div className="rounded-lg border border-border px-4 pt-10 pb-4">
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart
accessibilityLayer
data={props.data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<defs>
<Card>
{props.header && (
<CardHeader>
<CardTitle className={cn("mb-2", props.header.titleClassName)}>
{props.header.title}
</CardTitle>
{props.header.description && (
<CardDescription>{props.header.description}</CardDescription>
)}
</CardHeader>
)}

<CardContent className={cn(!props.header && "pt-6")}>
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
/>
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent
hideLabel={
props.hideLabel !== undefined ? props.hideLabel : true
}
labelFormatter={props.toolTipLabelFormatter}
/>
}
/>
<defs>
{configKeys.map((key) => (
<linearGradient
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
))}
</defs>
{configKeys.map((key) => (
<linearGradient
<Area
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
/>
))}
</defs>
{configKeys.map((key) => (
<Area
key={key}
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
/>
))}

{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
)}
</AreaChart>
)}
</ChartContainer>
</div>
{props.showLegend && (
<ChartLegend
content={<ChartLegendContent className="pt-8" />}
/>
)}
</AreaChart>
)}
</ChartContainer>
</CardContent>
</Card>
);
}
33 changes: 20 additions & 13 deletions apps/dashboard/src/@/components/blocks/charts/bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ import { cn } from "../../../lib/utils";

type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
// metadata
title: string;
description?: string;
header?: {
title: string;
description?: string;
titleClassName?: string;
};
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
Expand All @@ -39,7 +42,6 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
isPending: boolean;
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
hideLabel?: boolean;
titleClassName?: string;
};

export function ThirdwebBarChart<TConfig extends ChartConfig>(
Expand All @@ -52,15 +54,18 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(

return (
<Card>
<CardHeader>
<CardTitle className={cn("mb-2", props.titleClassName)}>
{props.title}
</CardTitle>
{props.description && (
<CardDescription>{props.description}</CardDescription>
)}
</CardHeader>
<CardContent>
{props.header && (
<CardHeader>
<CardTitle className={cn("mb-2", props.header.titleClassName)}>
{props.header.title}
</CardTitle>
{props.header.description && (
<CardDescription>{props.header.description}</CardDescription>
)}
</CardHeader>
)}

<CardContent className={cn(!props.header && "pt-6")}>
<ChartContainer config={props.config} className={props.chartClassName}>
{props.isPending ? (
<LoadingChartState />
Expand All @@ -87,7 +92,9 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
}
/>
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} />
<ChartLegend
content={<ChartLegendContent className="pt-5" />}
/>
)}
{configKeys.map((key, idx) => (
<Bar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ function UniqueWalletsChart(props: ChartProps) {

return (
<ThirdwebBarChart
title="Unique Wallets"
description="The number of unique wallet addresses that have sent a transaction to this contract."
header={{
title: "Unique Wallets",
description:
"The number of unique wallet addresses that have sent a transaction to this contract.",
}}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
Expand All @@ -151,8 +154,11 @@ function TotalTransactionsChart(props: ChartProps) {

return (
<ThirdwebBarChart
title="Total Transactions"
description="The number of transactions that have been sent to this contract."
header={{
title: "Total Transactions",
description:
"The number of transactions that have been sent to this contract.",
}}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
Expand All @@ -171,8 +177,11 @@ function TotalEventsChart(props: ChartProps) {

return (
<ThirdwebBarChart
title="Total Events"
description="The number of on-chain events that have been emitted from this contract."
header={{
title: "Total Events",
description:
"The number of on-chain events that have been emitted from this contract.",
}}
data={analyticsQuery.data || []}
isPending={analyticsQuery.isPending}
config={{
Expand Down Expand Up @@ -216,8 +225,11 @@ function FunctionBreakdownChart(

return (
<ThirdwebBarChart
title="Function Breakdown"
description="The breakdown of calls to each write function from transactions."
header={{
title: "Function Breakdown",
description:
"The breakdown of calls to each write function from transactions.",
}}
data={mappedQueryData || []}
isPending={analyticsQuery.isPending}
config={Object.keys(mappedQueryData?.[0] || {}).reduce(
Expand Down Expand Up @@ -269,8 +281,10 @@ function EventBreakdownChart(

return (
<ThirdwebBarChart
title="Event Breakdown"
description="The breakdown of events emitted by this contract."
header={{
title: "Event Breakdown",
description: "The breakdown of events emitted by this contract.",
}}
data={mappedQueryData || []}
isPending={analyticsQuery.isPending}
config={Object.keys(mappedQueryData?.[0] || {}).reduce(
Expand Down Expand Up @@ -319,7 +333,7 @@ const AnalyticsStat: React.FC<AnalyticsStatProps> = ({

const AnalyticsSkeleton: React.FC<{ label: string }> = ({ label }) => {
return (
<Card as={Stat}>
<Card as={Stat} className="bg-card">
<StatLabel mb={{ base: 1, md: 0 }}>{label}</StatLabel>
<Skeleton isLoaded={false}>
<StatNumber>{0}</StatNumber>
Expand Down Expand Up @@ -350,7 +364,7 @@ const AnalyticsData: React.FC<AnalyticsStatProps> = ({
}, [totalQuery.data]);

return (
<Card as={Stat}>
<Card as={Stat} className="bg-card">
<StatLabel mb={{ base: 1, md: 0 }}>{label}</StatLabel>
<Skeleton isLoaded={totalQuery.isFetched}>
<StatNumber>{data.toLocaleString()}</StatNumber>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function OverviewAnalytics(props: ChartProps) {
data={mergedData || []}
isPending={isPending}
showLegend
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
chartClassName="aspect-[1.5] lg:aspect-[3.5]"
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,15 @@ export function TransactionCharts(props: {

return (
<ThirdwebBarChart
title="Transactions Breakdown"
description="Transactions sent from your backend wallets per day"
header={{
title: "Transactions Breakdown",
description: "Transactions sent from your backend wallets per day",
titleClassName: "text-xl mb-0",
}}
config={chartConfig}
data={analyticsData}
isPending={transactionsQuery.isPending}
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
titleClassName="text-xl mb-0"
hideLabel={false}
variant="stacked"
showLegend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getTeamBySlug } from "@/api/team";
import { getValidAccount } from "../../../../../account/settings/getAccount";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
import { loginRedirect } from "../../../../../login/loginRedirect";
import { NebulaAnalyticsPage } from "../../../[project_slug]/nebula/components/analytics/nebula-analytics-ui";
import { NebulaAnalyticsPage } from "../../../[project_slug]/nebula/components/analytics/nebula-analytics-page";
import { NebulaWaitListPage } from "../../../[project_slug]/nebula/components/nebula-waitlist-page";

export default async function Page(props: {
Expand Down
Loading
Loading