Skip to content

Commit b3a537b

Browse files
committed
refactor(dashboard): simplify sidebar and settings hierarchy
Collapse Billing and Organizations into a single Settings category with three scoped sections (Workspace, Billing, Account). Sidebar top-level goes from 6 to 4 categories. Page merges: - Fold /billing/cost-breakdown into /billing overview; consumption chart and usage breakdown table now render below the usage stats. Components and utils moved out of the cost-breakdown subdir. - Merge /organizations/invitations into /organizations/members as a tabbed page (?tab=invitations). Invitation files moved into members/. - Fold /organizations/settings/websites into /organizations/settings via a new WorkspaceWebsitesSection embedded in General settings. - Delete /billing/cost-breakdown, /organizations/invitations, and /organizations/settings/websites folders entirely. Dropdown additions: - Org selector dropdown now has Workspace settings + Billing quick actions with a plan badge. - Profile dropdown now has a Billing entry. Fixes: - Tabs component: internal activeValue state now syncs with the controlled value prop via useEffect. Without this, URL-driven tab changes left the indicator underline on the wrong tab (the "inverted" active state users saw on Members/Invitations). - Invitations pill tabs are now in a clean sticky toolbar instead of an ugly double-bordered wrapper. - Fix broken Invitation type import to pull from organizationAtoms. Skeletons: - Rewrite all settings page skeletons to match their real layouts: MembersSkeleton, InvitationsSkeleton, MembersPageSkeleton, ApiKeysSkeleton, GeneralSettingsSkeleton, DangerZoneSkeleton, OrganizationsListSkeleton. - All share a SettingsShell wrapper and use RightSidebar.Skeleton for the sidebar column. - Delete orphan list-skeleton.tsx.
1 parent c6f1e43 commit b3a537b

31 files changed

+710
-874
lines changed

apps/dashboard/app/(main)/billing/components/billing-header.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@ import { PageHeader } from "../../websites/_components/page-header";
66

77
const PAGE_TITLES: Record<string, { title: string; description: string }> = {
88
"/billing": {
9-
title: "Usage & Metrics",
10-
description: "Monitor your usage and billing metrics",
9+
title: "Billing Overview",
10+
description: "Current plan, usage, and payment method for this workspace",
1111
},
1212
"/billing/plans": {
13-
title: "Plans & Pricing",
14-
description: "Manage your subscription and billing plan",
13+
title: "Plans",
14+
description: "Compare plans and change your subscription",
1515
},
1616
"/billing/history": {
17-
title: "Payment History",
18-
description: "View your billing history and invoices",
17+
title: "Invoices",
18+
description: "Past invoices and payment history",
1919
},
2020
};
2121

2222
const DEFAULT_TITLE = {
23-
title: "Billing & Subscription",
24-
description: "Manage your subscription, usage, and billing preferences",
23+
title: "Billing",
24+
description: "Manage this workspace's subscription, usage, and invoices",
2525
};
2626

2727
export function BillingHeader() {

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

File renamed without changes.

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

File renamed without changes.

apps/dashboard/app/(main)/billing/components/usage-row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ function BilledOverageRow({
288288
)}
289289
<Link
290290
className="font-medium text-primary text-xs hover:underline"
291-
href="/billing/cost-breakdown"
291+
href="#breakdown"
292292
>
293293
View breakdown
294294
</Link>

apps/dashboard/app/(main)/billing/cost-breakdown/page.tsx

Lines changed: 0 additions & 91 deletions
This file was deleted.

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

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,69 @@
11
"use client";
22

3-
import { ArrowSquareOutIcon } from "@phosphor-icons/react";
4-
import { CalendarIcon } from "@phosphor-icons/react";
5-
import { CrownIcon } from "@phosphor-icons/react";
6-
import { PlusIcon } from "@phosphor-icons/react";
7-
import { PuzzlePieceIcon } from "@phosphor-icons/react";
8-
import { TrendUpIcon } from "@phosphor-icons/react";
9-
import { XIcon } from "@phosphor-icons/react";
10-
import { useCustomer } from "autumn-js/react";
11-
import Link from "next/link";
12-
import { useMemo } from "react";
133
import { EmptyState } from "@/components/empty-state";
144
import { useBillingContext } from "@/components/providers/billing-provider";
155
import { Badge } from "@/components/ui/badge";
166
import { Button } from "@/components/ui/button";
7+
import { Skeleton } from "@/components/ui/skeleton";
178
import dayjs from "@/lib/dayjs";
9+
import { orpc } from "@/lib/orpc";
10+
import type { UsageResponse } from "@databuddy/shared/types/billing";
11+
import {
12+
ArrowSquareOutIcon,
13+
CalendarIcon,
14+
CrownIcon,
15+
PlusIcon,
16+
PuzzlePieceIcon,
17+
TrendUpIcon,
18+
XIcon,
19+
} from "@phosphor-icons/react";
20+
import { useQuery } from "@tanstack/react-query";
21+
import { useCustomer } from "autumn-js/react";
22+
import Link from "next/link";
23+
import { Suspense, useMemo, useState } from "react";
1824
import { CancelSubscriptionDialog } from "./components/cancel-subscription-dialog";
25+
import { ConsumptionChart } from "./components/consumption-chart";
1926
import { CreditCardDisplay } from "./components/credit-card-display";
2027
import { ErrorState } from "./components/empty-states";
2128
import { OverviewSkeleton } from "./components/overview-skeleton";
29+
import { UsageBreakdownTable } from "./components/usage-breakdown-table";
2230
import { UsageRow } from "./components/usage-row";
2331
import { useBilling, useBillingData } from "./hooks/use-billing";
32+
import type { OverageInfo } from "./utils/billing-utils";
33+
34+
interface OrgUsageData {
35+
balance?: number | null;
36+
includedUsage?: number | null;
37+
unlimited: boolean;
38+
}
39+
40+
function getDefaultDateRange() {
41+
const end = new Date();
42+
const start = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
43+
return {
44+
startDate: start.toISOString().split("T")[0],
45+
endDate: end.toISOString().split("T")[0],
46+
};
47+
}
48+
49+
function calculateOverageInfo(
50+
balance: number,
51+
includedUsage: number,
52+
unlimited: boolean
53+
): OverageInfo {
54+
if (unlimited || balance >= 0) {
55+
return {
56+
hasOverage: false,
57+
overageEvents: 0,
58+
includedEvents: includedUsage,
59+
};
60+
}
61+
return {
62+
hasOverage: true,
63+
overageEvents: Math.abs(balance),
64+
includedEvents: includedUsage,
65+
};
66+
}
2467

2568
function isSSOPlan(plan: { id: string; name: string }): boolean {
2669
const id = plan.id.toLowerCase();
@@ -59,6 +102,33 @@ export default function BillingPage() {
59102
const { plans, usage, customer, isLoading, error, refetch } =
60103
useBillingData();
61104
const { attach } = useCustomer();
105+
const [dateRange, setDateRange] = useState(getDefaultDateRange);
106+
107+
const { data: breakdownUsageRaw, isLoading: isBreakdownLoading } = useQuery({
108+
...orpc.billing.getUsage.queryOptions({
109+
input: {
110+
startDate: dateRange.startDate,
111+
endDate: dateRange.endDate,
112+
},
113+
}),
114+
});
115+
const breakdownUsageData = breakdownUsageRaw as UsageResponse | undefined;
116+
117+
const { data: orgUsageRaw } = useQuery({
118+
...orpc.organizations.getUsage.queryOptions(),
119+
});
120+
const orgUsage = orgUsageRaw as OrgUsageData | undefined;
121+
122+
const overageInfo = useMemo(() => {
123+
if (!orgUsage) {
124+
return null;
125+
}
126+
return calculateOverageInfo(
127+
orgUsage.balance ?? 0,
128+
orgUsage.includedUsage ?? 0,
129+
orgUsage.unlimited
130+
);
131+
}, [orgUsage]);
62132
const {
63133
onCancelClick,
64134
onCancelConfirm,
@@ -140,7 +210,7 @@ export default function BillingPage() {
140210
planName={cancelTarget?.name ?? ""}
141211
/>
142212

143-
{/* Main Content - Usage Stats */}
213+
{/* Main Content - Usage Stats + Breakdown */}
144214
<div className="shrink-0 lg:h-full lg:min-h-0 lg:overflow-y-auto">
145215
{usageStats.length === 0 ? (
146216
<EmptyState
@@ -151,15 +221,34 @@ export default function BillingPage() {
151221
variant="minimal"
152222
/>
153223
) : (
154-
<div className="divide-y">
155-
{usageStats.map((feature) => (
156-
<UsageRow
157-
feature={feature}
158-
isMaxPlan={isMaxPlan}
159-
key={feature.id}
224+
<>
225+
<div className="divide-y">
226+
{usageStats.map((feature) => (
227+
<UsageRow
228+
feature={feature}
229+
isMaxPlan={isMaxPlan}
230+
key={feature.id}
231+
/>
232+
))}
233+
</div>
234+
<Suspense fallback={<Skeleton className="h-64 w-full" />}>
235+
<ConsumptionChart
236+
isLoading={isBreakdownLoading}
237+
onDateRangeChange={(start, end) =>
238+
setDateRange({ startDate: start, endDate: end })
239+
}
240+
overageInfo={overageInfo}
241+
usageData={breakdownUsageData}
160242
/>
161-
))}
162-
</div>
243+
</Suspense>
244+
<Suspense fallback={<Skeleton className="h-64 w-full" />}>
245+
<UsageBreakdownTable
246+
isLoading={isBreakdownLoading}
247+
overageInfo={overageInfo}
248+
usageData={breakdownUsageData}
249+
/>
250+
</Suspense>
251+
</>
163252
)}
164253
</div>
165254

apps/dashboard/app/(main)/billing/cost-breakdown/utils/billing-utils.ts renamed to apps/dashboard/app/(main)/billing/utils/billing-utils.ts

File renamed without changes.

apps/dashboard/app/(main)/organizations/components/general-settings.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Input } from "@/components/ui/input";
1212
import { Label } from "@/components/ui/label";
1313
import { type Organization, useOrganizations } from "@/hooks/use-organizations";
1414
import { OrganizationAvatarEditor } from "./organization-avatar-editor";
15+
import { WorkspaceWebsitesSection } from "./workspace-websites-section";
1516

1617
export function GeneralSettings({
1718
organization,
@@ -161,6 +162,8 @@ export function GeneralSettings({
161162
</div>
162163
</div>
163164
</section>
165+
166+
<WorkspaceWebsitesSection organization={organization} />
164167
</div>
165168

166169
{/* Save Footer */}

apps/dashboard/app/(main)/organizations/components/list-skeleton.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)