Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion packages/core/src/billing/usageDisplay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,27 @@ describe("formatResetTime", () => {
expected: "Resets shortly",
},
])("$name", ({ resetAt, expected }) => {
const result = formatResetTime(resetAt, NOW);
const result = formatResetTime(resetAt, { now: NOW });
if (expected instanceof RegExp) {
expect(result).toMatch(expected);
} else {
expect(result).toBe(expected);
}
});

it("swaps the leading phrase when a custom label is given", () => {
const opts = { now: NOW, label: "Billing period ends" };
expect(formatResetTime(isoAt(30 * 60 * 1000), opts)).toBe(
"Billing period ends in 30m",
);
expect(formatResetTime(isoAt(4 * 3600 * 1000), opts)).toBe(
"Billing period ends in 4h",
);
expect(formatResetTime(isoAt(-60_000), opts)).toBe(
"Billing period ends shortly",
);
expect(formatResetTime(isoAt(30 * 86400 * 1000), opts)).toMatch(
/^Billing period ends [A-Za-z]+ \d+ at /,
);
});
});
12 changes: 6 additions & 6 deletions packages/core/src/billing/usageDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ export function isUsageExceeded(usage: UsageOutput): boolean {

export function formatResetTime(
resetAtIso: string,
now: number = Date.now(),
{ now = Date.now(), label = "Resets" }: { now?: number; label?: string } = {},
): string {
const parsed = Date.parse(resetAtIso);
const ms = Number.isNaN(parsed) ? 0 : Math.max(0, parsed - now);

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

const totalHours = ms / 3_600_000;
if (totalHours < 24) {
Expand All @@ -106,8 +106,8 @@ export function formatResetTime(
minutes = 0;
}
return minutes === 0
? `Resets in ${hours}h`
: `Resets in ${hours}h ${minutes}m`;
? `${label} in ${hours}h`
: `${label} in ${hours}h ${minutes}m`;
}

const target = new Date(now + ms);
Expand All @@ -120,5 +120,5 @@ export function formatResetTime(
minute: "2-digit",
timeZoneName: "short",
});
return `Resets ${date} at ${time}`;
return `${label} ${date} at ${time}`;
}
7 changes: 4 additions & 3 deletions packages/ui/src/features/billing/UsageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ export function UsageButton() {
meter.kind === "dollars"
? `${formatUsdAmount(meter.usedUsd)} of ${formatUsdAmount(meter.limitUsd)} used`
: `${percent}% used`;
const resetLabel = formatResetTime(
meter.kind === "dollars" ? meter.resetAt : meter.bucket.reset_at,
);
const resetLabel =
meter.kind === "dollars"
? formatResetTime(meter.resetAt, { label: "Billing period ends" })
: formatResetTime(meter.bucket.reset_at);
const breakdownLabel =
meter.kind === "dollars" && meter.breakdown
? formatUsageBreakdown(meter.breakdown)
Expand Down
12 changes: 4 additions & 8 deletions packages/ui/src/features/settings/sections/PlanUsageSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ import { getBillingUrl } from "@posthog/ui/utils/urls";
import { Badge, Button, Callout, Flex, Spinner, Text } from "@radix-ui/themes";
import { useEffect } from "react";

/**
* Plan & usage under usage-based billing: no seats or plans to manage in-app —
* payment methods, spend limits, and org-level usage live on the PostHog
* billing page. The free-tier meters show the per-user allowance for
* confirmed free-tier orgs; subscribed orgs have no per-user caps to meter.
*/
export function PlanUsageSettings() {
const billingEnabled = useFeatureFlag(BILLING_FLAG);
const spendAnalysisEnabled = useSpendAnalysisEnabled();
Expand Down Expand Up @@ -163,7 +157,9 @@ export function PlanUsageSettings() {
</Flex>

<Flex direction="column" gap="3">
<Text className="font-medium text-(--gray-9) text-sm">Usage</Text>
<Text className="font-medium text-(--gray-9) text-sm">
Organization usage
</Text>
{usageLoading ? (
<Flex
align="center"
Expand All @@ -178,7 +174,7 @@ export function PlanUsageSettings() {
label={freeTier ? "Monthly free usage" : "Usage this period"}
percent={meter.percent}
valueLabel={`${formatUsdAmount(meter.usedUsd)} of ${formatUsdAmount(meter.limitUsd)}${freeTier ? " included" : ""}`}
detail={`${meter.exceeded ? "Limit exceeded. " : ""}${formatResetTime(meter.resetAt)}`}
detail={`${meter.exceeded ? "Limit exceeded. " : ""}${formatResetTime(meter.resetAt, { label: "Billing period ends" })}`}
breakdown={
meter.breakdown
? { ...meter.breakdown, usedUsd: meter.usedUsd }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function SpendAnalysisSection() {
<Flex direction="column" gap="3">
<Flex align="center" justify="between">
<Text className="font-medium text-(--gray-9) text-sm">
Spend analysis
Personal spend analysis
</Text>
<Flex align="center" gap="4">
<WindowSelector value={spendWindow} onChange={setSpendWindow} />
Expand Down
Loading