Skip to content

Commit a887ded

Browse files
committed
Add charts on usage page
1 parent bfa7449 commit a887ded

File tree

9 files changed

+181
-190
lines changed

9 files changed

+181
-190
lines changed

apps/dashboard/src/app/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { UserOpStats } from "@/api/analytics";
2+
import { cn } from "@/lib/utils";
23
import { defineChain } from "thirdweb";
34
import { type ChainMetadata, getChainMetadata } from "thirdweb/chains";
5+
import { EmptyAccountAbstractionChartContent } from "../../../../../components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard";
6+
import { BarChart } from "../../../components/Analytics/BarChart";
47
import { CombinedBarChartCard } from "../../../components/Analytics/CombinedBarChartCard";
58

69
export async function TotalSponsoredChartCardUI({
@@ -81,19 +84,33 @@ export async function TotalSponsoredChartCardUI({
8184
},
8285
};
8386

87+
if (onlyMainnet) {
88+
const filteredData = timeSeriesData.filter((d) => d.mainnet > 0);
89+
return (
90+
<div className={cn("rounded-lg border p-4 lg:p-6", className)}>
91+
<h3 className="mb-1 font-semibold text-xl tracking-tight">
92+
{title || "Total Sponsored"}
93+
</h3>
94+
<p className="text-muted-foreground"> {description}</p>
95+
<BarChart
96+
isCurrency
97+
chartConfig={chartConfig}
98+
data={filteredData}
99+
activeKey="mainnet"
100+
emptyChartContent={<EmptyAccountAbstractionChartContent />}
101+
/>
102+
</div>
103+
);
104+
}
105+
84106
return (
85107
<CombinedBarChartCard
86108
isCurrency
87109
title={title || "Total Sponsored"}
88-
description={description}
89110
chartConfig={chartConfig}
90111
data={timeSeriesData}
91-
hideTabs={onlyMainnet}
92112
activeChart={
93-
onlyMainnet
94-
? "mainnet"
95-
: ((searchParams?.totalSponsored as keyof typeof chartConfig) ??
96-
"mainnet")
113+
(searchParams?.totalSponsored as keyof typeof chartConfig) ?? "mainnet"
97114
}
98115
queryKey="totalSponsored"
99116
existingQueryParams={searchParams}

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/billing/components/PlanInfoCard.tsx

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,10 @@ function BillingInfo({
101101
(subscription) => subscription.type === "USAGE",
102102
);
103103

104-
// only plan and usage subscriptions are considered for now
105-
const totalUsd = getAllSubscriptionsTotal(
106-
subscriptions.filter((sub) => sub.type === "PLAN" || sub.type === "USAGE"),
107-
);
108-
109104
return (
110105
<div>
111106
{planSubscription && (
112-
<SubscriptionOverview
113-
subscription={planSubscription}
114-
title="Plan Cost"
115-
/>
107+
<SubscriptionOverview subscription={planSubscription} />
116108
)}
117109

118110
{usageSubscription && (
@@ -124,28 +116,23 @@ function BillingInfo({
124116
/>
125117
</>
126118
)}
127-
128-
<Separator className="my-4" />
129-
130-
<div className="flex items-center justify-between gap-6">
131-
<h5 className="font-medium text-lg">Total Upcoming Bill</h5>
132-
<p className="text-foreground">{totalUsd}</p>
133-
</div>
134119
</div>
135120
);
136121
}
137122

138123
function SubscriptionOverview(props: {
139124
subscription: TeamSubscription;
140-
title: string;
125+
title?: string;
141126
}) {
142127
const { subscription } = props;
143128

144129
return (
145130
<div>
146131
<div className="flex items-center justify-between gap-6">
147132
<div>
148-
<h5 className="font-medium text-lg">{props.title} </h5>
133+
{props.title && (
134+
<h5 className="font-medium text-lg">{props.title}</h5>
135+
)}
149136
<p className="text-muted-foreground text-sm lg:text-base">
150137
{format(
151138
new Date(props.subscription.currentPeriodStart),
@@ -159,7 +146,7 @@ function SubscriptionOverview(props: {
159146
</p>
160147
</div>
161148

162-
<p className="text-muted-foreground">
149+
<p className="text-foreground">
163150
{formatCurrencyAmount(
164151
subscription.upcomingInvoice.amount || 0,
165152
subscription.upcomingInvoice.currency,
@@ -170,21 +157,6 @@ function SubscriptionOverview(props: {
170157
);
171158
}
172159

173-
function getAllSubscriptionsTotal(subscriptions: TeamSubscription[]) {
174-
let totalCents = 0;
175-
let currency = "USD";
176-
177-
for (const subscription of subscriptions) {
178-
const amount = subscription.upcomingInvoice.amount;
179-
currency = subscription.upcomingInvoice.currency;
180-
if (amount) {
181-
totalCents += amount;
182-
}
183-
}
184-
185-
return formatCurrencyAmount(totalCents, currency);
186-
}
187-
188160
function formatCurrencyAmount(centsAmount: number, currency: string) {
189161
return new Intl.NumberFormat(undefined, {
190162
style: "currency",

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/Usage.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { TotalSponsoredChartCardUI } from "../../../../_components/TotalSponsore
1414
import { UsageCard } from "./UsageCard";
1515

1616
type UsageProps = {
17+
// TODO - remove when we have all the data available from team
1718
usage: UsageBillableByService;
1819
subscriptions: TeamSubscription[];
1920
account: Account;
@@ -26,6 +27,7 @@ export const Usage: React.FC<UsageProps> = ({
2627
account,
2728
team,
2829
}) => {
30+
// TODO - get this from team instead of account
2931
const storageMetrics = useMemo(() => {
3032
if (!usageData) {
3133
return {};
@@ -36,18 +38,18 @@ export const Usage: React.FC<UsageProps> = ({
3638
const percent = toPercent(consumedBytes, limitBytes);
3739

3840
return {
39-
total: `${toSize(consumedBytes, "MB")} / ${toSize(
40-
limitBytes,
41-
)} (${percent}%)`,
41+
total: `${toSize(Math.min(consumedBytes, limitBytes), "MB")} of ${toSize(limitBytes)} included storage used`,
4242
progress: percent,
4343
...(usageData.billableUsd.storage > 0
4444
? {
4545
overage: usageData.billableUsd.storage,
4646
}
4747
: {}),
48+
totalUsage: `Total Usage: ${toSize(consumedBytes, "MB")}`,
4849
};
4950
}, [usageData]);
5051

52+
// TODO - get this from team instead of account
5153
const rpcMetrics = useMemo(() => {
5254
if (!usageData) {
5355
return {};

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/UsageCard.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { cn } from "@/lib/utils";
21
import type { JSX } from "react";
32
import { toUSD } from "utils/number";
43

@@ -10,6 +9,7 @@ interface UsageCardProps {
109
progress?: number;
1110
description: string;
1211
children?: JSX.Element;
12+
totalUsage?: string;
1313
}
1414

1515
export const UsageCard: React.FC<UsageCardProps> = ({
@@ -20,10 +20,11 @@ export const UsageCard: React.FC<UsageCardProps> = ({
2020
progress,
2121
description,
2222
children,
23+
totalUsage,
2324
}) => {
2425
return (
2526
<div className="relative flex min-h-[190px] flex-col rounded-lg border border-border bg-muted/50 p-4 lg:p-6">
26-
<h3 className="mb-1 font-semibold text-xl">{name}</h3>
27+
<h3 className="mb-1 font-semibold text-xl tracking-tight">{name}</h3>
2728
<p className="text-muted-foreground"> {description}</p>
2829

2930
<div className="h-6" />
@@ -32,12 +33,13 @@ export const UsageCard: React.FC<UsageCardProps> = ({
3233
{title && <p className="text-foreground">{title}</p>}
3334

3435
{total !== undefined && (
35-
<p className="text-muted-foreground text-sm">
36+
<p className="text-muted-foreground">
3637
{typeof total === "number" ? toUSD(total) : total}
3738
</p>
3839
)}
3940

4041
{progress !== undefined && <Progress value={progress} />}
42+
{totalUsage && <p className="mt-2 text-foreground">{totalUsage}</p>}
4143

4244
{overage && (
4345
<p className="mt-2 text-muted-foreground text-sm">
@@ -58,14 +60,7 @@ function Progress(props: {
5860
return (
5961
<div className="rounded-full bg-muted">
6062
<div
61-
className={cn(
62-
"h-2 rounded-full",
63-
props.value > 90
64-
? "bg-red-600"
65-
: props.value > 50
66-
? "bg-yellow-600"
67-
: "bg-blue-600",
68-
)}
63+
className={"h-2 rounded-full bg-blue-600"}
6964
style={{
7065
width: `${Math.min(props.value, 100)}%`,
7166
}}

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default async function Page(props: {
4040

4141
return (
4242
<Usage
43+
// TODO - remove accountUsage when we have all the data available from team
4344
usage={accountUsage}
4445
subscriptions={subscriptions}
4546
account={account}

apps/dashboard/src/app/team/components/Analytics/BarChart.tsx

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,22 @@ import {
1414
YAxis,
1515
} from "recharts";
1616
import { toUSD } from "utils/number";
17+
import { EmptyChartState } from "../../../../components/analytics/empty-chart-state";
1718

1819
export function BarChart({
1920
chartConfig,
2021
data,
2122
activeKey,
2223
tooltipLabel,
2324
isCurrency = false,
25+
emptyChartContent,
2426
}: {
2527
chartConfig: ChartConfig;
2628
data: { [key in string]: number | string }[];
2729
activeKey: string;
2830
tooltipLabel?: string;
2931
isCurrency?: boolean;
32+
emptyChartContent?: React.ReactNode;
3033
}) {
3134
return (
3235
<ChartContainer
@@ -38,62 +41,66 @@ export function BarChart({
3841
}}
3942
className="aspect-auto h-[250px] w-full pt-6"
4043
>
41-
<RechartsBarChart
42-
accessibilityLayer
43-
data={data}
44-
margin={{
45-
left: 12,
46-
right: 12,
47-
}}
48-
>
49-
<CartesianGrid vertical={false} />
50-
<XAxis
51-
dataKey="date"
52-
tickLine={false}
53-
axisLine={false}
54-
tickMargin={8}
55-
minTickGap={32}
56-
tickFormatter={(value: string) => {
57-
const date = new Date(value);
58-
return date.toLocaleDateString("en-US", {
59-
month: "short",
60-
day: "numeric",
61-
});
44+
{data.length === 0 ? (
45+
<EmptyChartState> {emptyChartContent} </EmptyChartState>
46+
) : (
47+
<RechartsBarChart
48+
accessibilityLayer
49+
data={data}
50+
margin={{
51+
left: 12,
52+
right: 12,
6253
}}
63-
/>
64-
<YAxis
65-
width={48}
66-
dataKey={activeKey}
67-
tickLine={false}
68-
axisLine={false}
69-
tickFormatter={(value: number) => formatTickerNumber(value)}
70-
/>
71-
<ChartTooltip
72-
content={
73-
<ChartTooltipContent
74-
className="w-[200px]"
75-
nameKey={activeKey}
76-
labelFormatter={(value) => {
77-
return new Date(value).toLocaleDateString("en-US", {
78-
month: "short",
79-
day: "numeric",
80-
year: "numeric",
81-
});
82-
}}
83-
valueFormatter={(v: unknown) =>
84-
isCurrency
85-
? toUSD(v as number)
86-
: formatTickerNumber(v as number)
87-
}
88-
/>
89-
}
90-
/>
91-
<Bar
92-
dataKey={activeKey}
93-
radius={4}
94-
fill={chartConfig[activeKey]?.color ?? "hsl(var(--chart-1))"}
95-
/>
96-
</RechartsBarChart>
54+
>
55+
<CartesianGrid vertical={false} />
56+
<XAxis
57+
dataKey="date"
58+
tickLine={false}
59+
axisLine={false}
60+
tickMargin={8}
61+
minTickGap={32}
62+
tickFormatter={(value: string) => {
63+
const date = new Date(value);
64+
return date.toLocaleDateString("en-US", {
65+
month: "short",
66+
day: "numeric",
67+
});
68+
}}
69+
/>
70+
<YAxis
71+
width={48}
72+
dataKey={activeKey}
73+
tickLine={false}
74+
axisLine={false}
75+
tickFormatter={(value: number) => formatTickerNumber(value)}
76+
/>
77+
<ChartTooltip
78+
content={
79+
<ChartTooltipContent
80+
className="w-[200px]"
81+
nameKey={activeKey}
82+
labelFormatter={(value) => {
83+
return new Date(value).toLocaleDateString("en-US", {
84+
month: "short",
85+
day: "numeric",
86+
year: "numeric",
87+
});
88+
}}
89+
valueFormatter={(v: unknown) =>
90+
isCurrency
91+
? toUSD(v as number)
92+
: formatTickerNumber(v as number)
93+
}
94+
/>
95+
}
96+
/>
97+
<Bar
98+
dataKey={activeKey}
99+
radius={4}
100+
fill={chartConfig[activeKey]?.color ?? "hsl(var(--chart-1))"}
101+
/>
102+
</RechartsBarChart>
103+
)}
97104
</ChartContainer>
98105
);
99106
}

0 commit comments

Comments
 (0)