Skip to content

Commit 5eb5adb

Browse files
committed
fix(billing): clarify org usage vs personal spend on Plan & usage
The Plan & usage page showed an org-level usage meter directly above a user-level spend analysis with no cue that the two have different scopes, so the spend numbers could read as the whole organization's. - Label the usage meter "Organization usage" with a caption noting it's shared across the whole organization (and "Your usage" for the per-user free-tier valve fallback). - Rename "Spend analysis" to "Your spend analysis" with a caption noting it covers only the signed-in user's usage. - Reword the org billing-period countdown from "Resets …" to "Billing period ends …" via an optional label on formatResetTime; kept "Resets" for the per-user free-tier valve windows, which really do reset. Generated-By: PostHog Code Task-Id: d69678da-415c-450b-aafe-3d834d0bc557
1 parent 0b69cb9 commit 5eb5adb

5 files changed

Lines changed: 48 additions & 16 deletions

File tree

packages/core/src/billing/usageDisplay.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,27 @@ describe("formatResetTime", () => {
264264
expected: "Resets shortly",
265265
},
266266
])("$name", ({ resetAt, expected }) => {
267-
const result = formatResetTime(resetAt, NOW);
267+
const result = formatResetTime(resetAt, { now: NOW });
268268
if (expected instanceof RegExp) {
269269
expect(result).toMatch(expected);
270270
} else {
271271
expect(result).toBe(expected);
272272
}
273273
});
274+
275+
it("swaps the leading phrase when a custom label is given", () => {
276+
const opts = { now: NOW, label: "Billing period ends" };
277+
expect(formatResetTime(isoAt(30 * 60 * 1000), opts)).toBe(
278+
"Billing period ends in 30m",
279+
);
280+
expect(formatResetTime(isoAt(4 * 3600 * 1000), opts)).toBe(
281+
"Billing period ends in 4h",
282+
);
283+
expect(formatResetTime(isoAt(-60_000), opts)).toBe(
284+
"Billing period ends shortly",
285+
);
286+
expect(formatResetTime(isoAt(30 * 86400 * 1000), opts)).toMatch(
287+
/^Billing period ends [A-Za-z]+ \d+ at /,
288+
);
289+
});
274290
});

packages/core/src/billing/usageDisplay.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ export function isUsageExceeded(usage: UsageOutput): boolean {
8888

8989
export function formatResetTime(
9090
resetAtIso: string,
91-
now: number = Date.now(),
91+
{ now = Date.now(), label = "Resets" }: { now?: number; label?: string } = {},
9292
): string {
9393
const parsed = Date.parse(resetAtIso);
9494
const ms = Number.isNaN(parsed) ? 0 : Math.max(0, parsed - now);
9595

9696
const totalMinutes = Math.ceil(ms / 60_000);
97-
if (totalMinutes <= 0) return "Resets shortly";
98-
if (totalMinutes < 60) return `Resets in ${totalMinutes}m`;
97+
if (totalMinutes <= 0) return `${label} shortly`;
98+
if (totalMinutes < 60) return `${label} in ${totalMinutes}m`;
9999

100100
const totalHours = ms / 3_600_000;
101101
if (totalHours < 24) {
@@ -106,8 +106,8 @@ export function formatResetTime(
106106
minutes = 0;
107107
}
108108
return minutes === 0
109-
? `Resets in ${hours}h`
110-
: `Resets in ${hours}h ${minutes}m`;
109+
? `${label} in ${hours}h`
110+
: `${label} in ${hours}h ${minutes}m`;
111111
}
112112

113113
const target = new Date(now + ms);
@@ -120,5 +120,5 @@ export function formatResetTime(
120120
minute: "2-digit",
121121
timeZoneName: "short",
122122
});
123-
return `Resets ${date} at ${time}`;
123+
return `${label} ${date} at ${time}`;
124124
}

packages/ui/src/features/billing/UsageButton.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ export function UsageButton() {
6868
meter.kind === "dollars"
6969
? `${formatUsdAmount(meter.usedUsd)} of ${formatUsdAmount(meter.limitUsd)} used`
7070
: `${percent}% used`;
71-
const resetLabel = formatResetTime(
72-
meter.kind === "dollars" ? meter.resetAt : meter.bucket.reset_at,
73-
);
71+
const resetLabel =
72+
meter.kind === "dollars"
73+
? formatResetTime(meter.resetAt, { label: "Billing period ends" })
74+
: formatResetTime(meter.bucket.reset_at);
7475
const breakdownLabel =
7576
meter.kind === "dollars" && meter.breakdown
7677
? formatUsageBreakdown(meter.breakdown)

packages/ui/src/features/settings/sections/PlanUsageSettings.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export function PlanUsageSettings() {
6666
const subscribed = usage?.code_usage_subscribed === true;
6767
const orgLimitReached = usage?.ai_credits?.exhausted === true;
6868
const meter = codeUsageMeter(usage);
69+
const usageIsPerUser = meter.kind === "bucket";
6970

7071
const openBilling = () => {
7172
if (billingUrl) window.open(billingUrl, "_blank");
@@ -163,7 +164,16 @@ export function PlanUsageSettings() {
163164
</Flex>
164165

165166
<Flex direction="column" gap="3">
166-
<Text className="font-medium text-(--gray-9) text-sm">Usage</Text>
167+
<Flex direction="column" gap="1">
168+
<Text className="font-medium text-(--gray-9) text-sm">
169+
{usageIsPerUser ? "Your usage" : "Organization usage"}
170+
</Text>
171+
<Text className="text-(--gray-11) text-[13px]">
172+
{usageIsPerUser
173+
? "Your personal free-tier allowance for this period."
174+
: "Usage-based billing shared across your whole organization."}
175+
</Text>
176+
</Flex>
167177
{usageLoading ? (
168178
<Flex
169179
align="center"
@@ -178,7 +188,7 @@ export function PlanUsageSettings() {
178188
label={freeTier ? "Monthly free usage" : "Usage this period"}
179189
percent={meter.percent}
180190
valueLabel={`${formatUsdAmount(meter.usedUsd)} of ${formatUsdAmount(meter.limitUsd)}${freeTier ? " included" : ""}`}
181-
detail={`${meter.exceeded ? "Limit exceeded. " : ""}${formatResetTime(meter.resetAt)}`}
191+
detail={`${meter.exceeded ? "Limit exceeded. " : ""}${formatResetTime(meter.resetAt, { label: "Billing period ends" })}`}
182192
breakdown={
183193
meter.breakdown
184194
? { ...meter.breakdown, usedUsd: meter.usedUsd }

packages/ui/src/features/usage/components/SpendAnalysisSection.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ export function SpendAnalysisSection() {
3636

3737
return (
3838
<Flex direction="column" gap="3">
39-
<Flex align="center" justify="between">
40-
<Text className="font-medium text-(--gray-9) text-sm">
41-
Spend analysis
42-
</Text>
39+
<Flex align="start" justify="between" gap="4">
40+
<Flex direction="column" gap="1">
41+
<Text className="font-medium text-(--gray-9) text-sm">
42+
Your spend analysis
43+
</Text>
44+
<Text className="text-(--gray-11) text-[13px]">
45+
Just your own usage, not your whole organization's.
46+
</Text>
47+
</Flex>
4348
<Flex align="center" gap="4">
4449
<WindowSelector value={spendWindow} onChange={setSpendWindow} />
4550
<Button

0 commit comments

Comments
 (0)