Skip to content

Commit 1733805

Browse files
authored
Chore(webapp): Adds additional billing alerts (#2829)
- Adds 4 additional alert thresholds to ensure customers are emailed if they have runaway usage. - Separated these into a new section called "Spike alerts" with a tooltip so it's clear what they are. - Tooltip message is: "Catch runaway usage from bugs or errors. We recommend keeping these enabled as a safety net." - A billing service PR now returns all orgs to populate the email list, rather than oldest 5. - Adds `defaultChecked` logic to honour existing orgs who have configured alerts in the DB. New orgs get all alerts checked on by default. <img width="1316" height="1560" alt="CleanShot 2026-01-05 at 09 43 56@2x" src="https://github.com/user-attachments/assets/ce749407-2b7f-4864-9c09-9333c5ac495a" />
1 parent c7a238c commit 1733805

File tree

2 files changed

+41
-2
lines changed
  • apps/webapp/app

2 files changed

+41
-2
lines changed

apps/webapp/app/components/primitives/Tooltip.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { cn } from "~/utils/cn";
66
const variantClasses = {
77
basic:
88
"bg-background-bright border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
9-
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50"
9+
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
1010
};
1111

1212
type Variant = keyof typeof variantClasses;
@@ -111,11 +111,13 @@ export function InfoIconTooltip({
111111
buttonClassName,
112112
contentClassName,
113113
variant = "basic",
114+
disableHoverableContent = false,
114115
}: {
115116
content: React.ReactNode;
116117
buttonClassName?: string;
117118
contentClassName?: string;
118119
variant?: Variant;
120+
disableHoverableContent?: boolean;
119121
}) {
120122
return (
121123
<SimpleTooltip
@@ -125,6 +127,7 @@ export function InfoIconTooltip({
125127
content={content}
126128
variant={variant}
127129
className={contentClassName}
130+
disableHoverableContent={disableHoverableContent}
128131
/>
129132
);
130133
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ import { Label } from "~/components/primitives/Label";
2424
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
2525
import { Paragraph } from "~/components/primitives/Paragraph";
2626
import { TextLink } from "~/components/primitives/TextLink";
27+
import { InfoIconTooltip } from "~/components/primitives/Tooltip";
2728
import { prisma } from "~/db.server";
2829
import { featuresForRequest } from "~/features.server";
2930
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
3031
import { getBillingAlerts, setBillingAlert } from "~/services/platform.v3.server";
3132
import { requireUserId } from "~/services/session.server";
32-
import { formatCurrency } from "~/utils/numberFormatter";
33+
import { formatCurrency, formatNumber } from "~/utils/numberFormatter";
3334
import {
3435
docsPath,
3536
OrganizationParamsSchema,
@@ -183,6 +184,8 @@ export default function Page() {
183184

184185
const checkboxLevels = [0.75, 0.9, 1.0, 2.0, 5.0];
185186

187+
const spikeAlertLevels = [10.0, 20.0, 50.0, 100.0];
188+
186189
useEffect(() => {
187190
if (alerts.emails.length > 0) {
188191
requestIntent(form.ref.current ?? undefined, list.append(emails.name));
@@ -272,6 +275,39 @@ export default function Page() {
272275
))}
273276
<FormError id={alertLevels.errorId}>{alertLevels.error}</FormError>
274277
</InputGroup>
278+
<InputGroup fullWidth>
279+
<div className="flex items-center gap-1">
280+
<Label>Spike alerts</Label>
281+
<InfoIconTooltip
282+
content={
283+
"Catch runaway usage from bugs or errors. We recommend keeping these enabled as a safety net."
284+
}
285+
disableHoverableContent
286+
/>
287+
</div>
288+
{spikeAlertLevels.map((level) => (
289+
<CheckboxWithLabel
290+
name={alertLevels.name}
291+
id={`level_${level}`}
292+
key={level}
293+
value={level.toString()}
294+
variant="simple/small"
295+
label={
296+
<span>
297+
{formatNumber(level * 100)}%{" "}
298+
<span className="text-text-dimmed">
299+
({formatCurrency(Number(dollarAmount) * level, false)})
300+
</span>
301+
</span>
302+
}
303+
defaultChecked={
304+
alerts.alertLevels.includes(level) ||
305+
!spikeAlertLevels.some((l) => alerts.alertLevels.includes(l))
306+
}
307+
className="pr-0"
308+
/>
309+
))}
310+
</InputGroup>
275311
<InputGroup fullWidth>
276312
<Label htmlFor={emails.id}>Email addresses</Label>
277313
{emailFields.map((email, index) => (

0 commit comments

Comments
 (0)