feat: add "View usage" button to analytics limit banner and rename Usage page (#1660)

This commit is contained in:
Konsti Wohlwend 2026-06-23 16:20:34 -07:00 committed by GitHub
parent a6c46db72c
commit ed48ab3bd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 23 additions and 11 deletions

View File

@ -15,6 +15,7 @@ import {
WarningCircleIcon
} from "@phosphor-icons/react";
import { Alert, AlertDescription, Button } from "@/components/ui";
import { Link } from "@/components/link";
import { useDashboardInternalUser } from "@/lib/dashboard-user";
import { PLAN_LIMITS, resolvePlanId } from "@hexclave/shared/dist/plans";
import { runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises";
@ -340,7 +341,7 @@ export function AnalyticsEventLimitBanner() {
return null;
}
return <AnalyticsEventLimitBannerInner team={ownerTeam} />;
return <AnalyticsEventLimitBannerInner team={ownerTeam} projectId={project.id} />;
}
/**
@ -395,7 +396,7 @@ function SessionReplayLimitBannerInner({ team }: { team: { useItem: (itemId: str
);
}
function AnalyticsEventLimitBannerInner({ team }: { team: { useItem: (itemId: string) => { quantity: number }, useProducts: () => Array<{ id: string | null, type?: string }>, createCheckoutUrl: (options: { productId: string, returnUrl: string }) => Promise<string> } }) {
function AnalyticsEventLimitBannerInner({ team, projectId }: { team: { useItem: (itemId: string) => { quantity: number }, useProducts: () => Array<{ id: string | null, type?: string }>, createCheckoutUrl: (options: { productId: string, returnUrl: string }) => Promise<string> }, projectId: string }) {
const eventsItem = team.useItem("analytics_events");
const products = team.useProducts();
const planId = resolvePlanId(products);
@ -433,15 +434,26 @@ function AnalyticsEventLimitBannerInner({ team }: { team: { useItem: (itemId: st
}
{canUpgrade && !isExhausted && " Consider upgrading your plan."}
</span>
{canUpgrade && (
<div className="flex items-center gap-2 shrink-0">
<Button
size="sm"
variant={isExhausted ? "destructive" : "outline"}
onClick={handleUpgrade}
variant="outline"
asChild
>
Upgrade plan
<Link href={`/projects/${projectId}/project-settings/usage`}>
View usage
</Link>
</Button>
)}
{canUpgrade && (
<Button
size="sm"
variant={isExhausted ? "destructive" : "outline"}
onClick={handleUpgrade}
>
Upgrade plan
</Button>
)}
</div>
</AlertDescription>
</Alert>
);

View File

@ -106,8 +106,8 @@ describe("Usage settings page", () => {
it("renders the plan, usage rows, and overage state", () => {
render(<PageClient />);
// The page title and usage card share this label.
expect(screen.getAllByText("Usage").length).toBeGreaterThan(0);
// The page title
expect(screen.getAllByText("Billing & Usage").length).toBeGreaterThan(0);
expect(screen.getAllByText("Free").length).toBeGreaterThan(0);
expect(screen.getAllByText("Owner").length).toBeGreaterThan(0);
expect(screen.getAllByText("Dashboard admins").length).toBeGreaterThan(0);

View File

@ -362,7 +362,7 @@ export default function PageClient() {
return (
<PageLayout
title="Usage"
title="Billing & Usage"
description={`Usage for ${planUsage.ownerTeamDisplayName} across all projects owned by this team.`}
width={1050}
>

View File

@ -139,7 +139,7 @@ const projectSettingsItem: AppSection = {
match: (fullUrl: URL) => /^\/projects\/[^\/]+\/project-settings\/?$/.test(fullUrl.pathname),
},
{
name: "Usage",
name: "Billing & Usage",
href: "/project-settings/usage",
match: (fullUrl: URL) => /^\/projects\/[^\/]+\/project-settings\/usage(\/.*)?$/.test(fullUrl.pathname),
},