Skip to content

Commit 68d5109

Browse files
committed
cleanup org hook, cost breakdown page
1 parent 9283999 commit 68d5109

File tree

7 files changed

+401
-643
lines changed

7 files changed

+401
-643
lines changed

apps/dashboard/app/(main)/billing/cost-breakdown/components/consumption-chart.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ const EVENT_TYPE_COLORS = {
2929
outgoing_link: METRIC_COLORS.bounce_rate.primary, // amber
3030
} as const;
3131

32-
interface ConsumptionChartProps {
32+
type ConsumptionChartProps = {
3333
usageData?: UsageResponse;
3434
isLoading: boolean;
3535
onDateRangeChange: (startDate: string, endDate: string) => void;
3636
overageInfo: OverageInfo | null;
37-
}
37+
};
3838

3939
export function ConsumptionChart({
4040
usageData,
@@ -118,14 +118,14 @@ export function ConsumptionChart({
118118
if (isLoading) {
119119
return (
120120
<div className="flex h-full flex-col border-b">
121-
<div className="border-b bg-muted/20 px-6 py-4">
121+
<div className="border-b px-5 py-4">
122122
<div className="flex items-center justify-between">
123123
<Skeleton className="h-6 w-48" />
124124
<Skeleton className="h-8 w-32" />
125125
</div>
126126
</div>
127-
<div className="flex-1 px-6 py-6">
128-
<Skeleton className="h-full" />
127+
<div className="p-5">
128+
<Skeleton className="h-[350px] w-full" />
129129
</div>
130130
</div>
131131
);
@@ -134,20 +134,20 @@ export function ConsumptionChart({
134134
if (!usageData || chartData.length === 0) {
135135
return (
136136
<div className="flex h-full flex-col border-b">
137-
<div className="border-b bg-muted/20 px-6 py-4">
137+
<div className="border-b px-5 py-4">
138138
<div className="flex items-center gap-2">
139139
<ChartBarIcon className="h-5 w-5" weight="duotone" />
140-
<h2 className="font-semibold text-lg">Consumption Breakdown</h2>
140+
<h2 className="font-semibold">Consumption Breakdown</h2>
141141
</div>
142142
</div>
143-
<div className="flex flex-1 items-center justify-center px-6 py-6">
143+
<div className="flex h-[350px] items-center justify-center p-5">
144144
<div className="text-center">
145145
<CalendarIcon
146146
className="mx-auto mb-4 h-12 w-12 text-muted-foreground"
147147
weight="duotone"
148148
/>
149-
<h3 className="font-semibold text-lg">No Data Available</h3>
150-
<p className="text-muted-foreground">
149+
<h3 className="font-semibold">No Data Available</h3>
150+
<p className="text-muted-foreground text-sm">
151151
No usage data found for the selected period
152152
</p>
153153
</div>
@@ -168,11 +168,11 @@ export function ConsumptionChart({
168168

169169
return (
170170
<div className="flex h-full flex-col border-b">
171-
<div className="border-b bg-muted/20 px-6 py-4">
171+
<div className="border-b px-5 py-4">
172172
<div className="flex items-center justify-between">
173173
<div className="flex items-center gap-2">
174174
<ChartBarIcon className="h-5 w-5" weight="duotone" />
175-
<h2 className="font-semibold text-lg">Consumption Breakdown</h2>
175+
<h2 className="font-semibold">Consumption Breakdown</h2>
176176
</div>
177177
<div className="flex items-center gap-2">
178178
<DateRangePicker
@@ -213,8 +213,8 @@ export function ConsumptionChart({
213213
</div>
214214
</div>
215215
</div>
216-
<div className="flex-1 px-6 py-6">
217-
<div className="h-full">
216+
<div className="flex-1 p-5">
217+
<div className="h-[350px]">
218218
<ResponsiveContainer height="100%" width="100%">
219219
<BarChart
220220
data={chartData}
@@ -318,9 +318,11 @@ export function ConsumptionChart({
318318
<div className="font-bold text-foreground text-sm group-hover:text-primary">
319319
{eventCount.toLocaleString()}
320320
</div>
321-
<div className="text-muted-foreground text-xs">
322-
${overageCost.toFixed(6)}
323-
</div>
321+
{overageCost > 0 && (
322+
<div className="text-muted-foreground text-xs">
323+
${overageCost.toFixed(2)}
324+
</div>
325+
)}
324326
</div>
325327
</div>
326328
);

apps/dashboard/app/(main)/billing/cost-breakdown/components/usage-breakdown-table.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ const EVENT_TYPE_CONFIG = {
5353
},
5454
} as const;
5555

56-
interface UsageBreakdownTableProps {
56+
type UsageBreakdownTableProps = {
5757
usageData?: UsageResponse;
5858
isLoading: boolean;
5959
overageInfo: OverageInfo | null;
60-
}
60+
};
6161

6262
export function UsageBreakdownTable({
6363
usageData,
@@ -88,10 +88,7 @@ export function UsageBreakdownTable({
8888
);
8989
}
9090

91-
if (
92-
!(usageData && usageData.eventTypeBreakdown) ||
93-
usageData.eventTypeBreakdown.length === 0
94-
) {
91+
if (!usageData?.eventTypeBreakdown?.length) {
9592
return (
9693
<div className="flex h-full items-center justify-center">
9794
<div className="text-center">
@@ -167,8 +164,8 @@ export function UsageBreakdownTable({
167164
<div className="text-muted-foreground text-sm">events</div>
168165
</TableCell>
169166
<TableCell>
170-
<div className="font-medium">
171-
${overageCost.toPrecision(3)}
167+
<div className="font-medium text-muted-foreground">
168+
{overageCost > 0 ? `$${overageCost.toFixed(2)}` : "—"}
172169
</div>
173170
</TableCell>
174171
</TableRow>
Lines changed: 58 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,99 @@
11
"use client";
22

3-
import { ChartLineUpIcon, FlaskIcon } from "@phosphor-icons/react";
43
import { useQuery } from "@tanstack/react-query";
54
import { Suspense, useMemo, useState } from "react";
6-
import { Badge } from "@/components/ui/badge";
5+
import { useOrganizationsContext } from "@/components/providers/organizations-provider";
76
import { Skeleton } from "@/components/ui/skeleton";
8-
import { useOrganizations } from "@/hooks/use-organizations";
97
import { orpc } from "@/lib/orpc";
108
import { ConsumptionChart } from "./components/consumption-chart";
119
import { UsageBreakdownTable } from "./components/usage-breakdown-table";
10+
import type { OverageInfo } from "./utils/billing-utils";
1211

13-
const getDefaultDateRange = () => {
14-
const endDate = new Date().toISOString().split("T")[0];
15-
const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
16-
.toISOString()
17-
.split("T")[0];
18-
return { startDate, endDate };
19-
};
12+
function getDefaultDateRange() {
13+
const end = new Date();
14+
const start = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
15+
return {
16+
startDate: start.toISOString().split("T")[0],
17+
endDate: end.toISOString().split("T")[0],
18+
};
19+
}
20+
21+
function calculateOverageInfo(
22+
totalEvents: number,
23+
rawIncludedUsage: number,
24+
unlimited: boolean
25+
): OverageInfo {
26+
const includedUsage =
27+
rawIncludedUsage > 0 && rawIncludedUsage < 1_000_000_000
28+
? rawIncludedUsage
29+
: 0;
30+
31+
if (unlimited || totalEvents <= includedUsage) {
32+
return { hasOverage: false, overageEvents: 0, includedEvents: totalEvents };
33+
}
34+
return {
35+
hasOverage: true,
36+
overageEvents: totalEvents - includedUsage,
37+
includedEvents: includedUsage,
38+
};
39+
}
2040

2141
export default function CostBreakdownPage() {
22-
const [dateRange, setDateRange] = useState(() => getDefaultDateRange());
23-
const { activeOrganization, isLoading: isLoadingOrganizations } =
24-
useOrganizations();
42+
const [dateRange, setDateRange] = useState(getDefaultDateRange);
43+
const { activeOrganization, isLoading: isOrgLoading } =
44+
useOrganizationsContext();
2545

26-
const usageQueryInput = useMemo(
27-
() => ({
28-
startDate: dateRange.startDate,
29-
endDate: dateRange.endDate,
30-
organizationId: activeOrganization?.id || null,
46+
const { data: usageData, isLoading: isUsageLoading } = useQuery({
47+
...orpc.billing.getUsage.queryOptions({
48+
input: {
49+
startDate: dateRange.startDate,
50+
endDate: dateRange.endDate,
51+
organizationId: activeOrganization?.id || null,
52+
},
3153
}),
32-
[dateRange, activeOrganization?.id]
33-
);
34-
35-
const { data: usageData, isLoading: isLoadingUsage } = useQuery({
36-
...orpc.billing.getUsage.queryOptions({ input: usageQueryInput }),
37-
enabled: !isLoadingOrganizations,
54+
enabled: !isOrgLoading,
3855
});
3956

40-
const { data: organizationUsage } = useQuery({
57+
const { data: orgUsage } = useQuery({
4158
...orpc.organizations.getUsage.queryOptions(),
4259
});
4360

44-
const isLoading = isLoadingUsage;
45-
46-
const handleDateRangeChange = (startDate: string, endDate: string) => {
47-
setDateRange({ startDate, endDate });
48-
};
49-
5061
const overageInfo = useMemo(() => {
51-
if (!(organizationUsage && usageData)) {
62+
if (!(orgUsage && usageData)) {
5263
return null;
5364
}
54-
55-
const includedUsage = organizationUsage.includedUsage || 0;
56-
const totalEvents = usageData.totalEvents;
57-
58-
if (organizationUsage.unlimited || totalEvents <= includedUsage) {
59-
return {
60-
hasOverage: false,
61-
overageEvents: 0,
62-
includedEvents: totalEvents,
63-
};
64-
}
65-
66-
const overageEvents = totalEvents - includedUsage;
67-
return { hasOverage: true, overageEvents, includedEvents: includedUsage };
68-
}, [organizationUsage, usageData]);
65+
return calculateOverageInfo(
66+
usageData.totalEvents,
67+
orgUsage.includedUsage || 0,
68+
orgUsage.unlimited
69+
);
70+
}, [orgUsage, usageData]);
6971

7072
return (
71-
<div className="flex h-full flex-col">
72-
<div className="border-b bg-linear-to-r from-background to-muted/20 px-6 py-6">
73-
<div className="flex items-center gap-4">
74-
<div className="rounded-xl border border-primary/20 bg-primary/10 p-3">
75-
<ChartLineUpIcon className="h-6 w-6 text-primary" />
76-
</div>
77-
<div>
78-
<div className="flex items-center gap-3">
79-
<h1 className="font-bold text-2xl tracking-tight">
80-
Cost Breakdown
81-
</h1>
82-
<Badge
83-
className="bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300"
84-
variant="secondary"
85-
>
86-
<FlaskIcon className="mr-1" size={12} weight="duotone" />
87-
Experimental
88-
</Badge>
89-
</div>
90-
<p className="text-muted-foreground text-sm">
91-
Detailed analytics usage breakdown and consumption patterns
92-
</p>
93-
</div>
94-
</div>
95-
</div>
96-
73+
<main className="min-h-0 flex-1 overflow-y-auto">
9774
<div className="flex min-h-0 flex-1 flex-col">
98-
<div className="flex-[3]">
75+
<div className="flex-3">
9976
<Suspense fallback={<Skeleton className="h-full w-full" />}>
10077
<ConsumptionChart
101-
isLoading={isLoading}
102-
onDateRangeChange={handleDateRangeChange}
78+
isLoading={isUsageLoading}
79+
onDateRangeChange={(start, end) =>
80+
setDateRange({ startDate: start, endDate: end })
81+
}
10382
overageInfo={overageInfo}
10483
usageData={usageData}
10584
/>
10685
</Suspense>
10786
</div>
108-
<div className="flex-[2]">
87+
<div className="flex-2">
10988
<Suspense fallback={<Skeleton className="h-full w-full" />}>
11089
<UsageBreakdownTable
111-
isLoading={isLoading}
90+
isLoading={isUsageLoading}
11291
overageInfo={overageInfo}
11392
usageData={usageData}
11493
/>
11594
</Suspense>
11695
</div>
11796
</div>
118-
</div>
97+
</main>
11998
);
12099
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
export const EVENT_COST = 0.000_035;
22

3-
export interface OverageInfo {
3+
export type OverageInfo = {
44
hasOverage: boolean;
55
overageEvents: number;
66
includedEvents: number;
7-
}
7+
};
88

99
export function calculateOverageCost(
1010
eventCount: number,
1111
totalEvents: number,
1212
overageInfo: OverageInfo | null
1313
): number {
14-
if (!overageInfo?.hasOverage || totalEvents === 0) {
14+
if (
15+
!overageInfo?.hasOverage ||
16+
totalEvents <= 0 ||
17+
eventCount <= 0 ||
18+
overageInfo.overageEvents <= 0
19+
) {
1520
return 0;
1621
}
1722

18-
const eventTypeRatio = eventCount / totalEvents;
19-
const overageForThisType = overageInfo.overageEvents * eventTypeRatio;
20-
return Math.max(0, overageForThisType) * EVENT_COST;
23+
const ratio = eventCount / totalEvents;
24+
return overageInfo.overageEvents * ratio * EVENT_COST;
2125
}

apps/dashboard/app/(main)/billing/plans/page.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ export default function PlansPage() {
88
const selectedPlan = searchParams.get("plan");
99

1010
return (
11-
<main className="flex-1 overflow-y-auto p-4 sm:p-6">
12-
<PricingTable selectedPlan={selectedPlan} />
11+
<main className="min-h-0 flex-1 overflow-y-auto">
12+
<div className="border-b px-5 py-4">
13+
<h2 className="font-semibold">Plans</h2>
14+
<p className="text-muted-foreground text-sm">
15+
Choose the plan that fits your needs
16+
</p>
17+
</div>
18+
<div className="p-5">
19+
<PricingTable selectedPlan={selectedPlan} />
20+
</div>
1321
</main>
1422
);
1523
}

0 commit comments

Comments
 (0)