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" />
This commit is contained in:
@@ -6,7 +6,7 @@ import { cn } from "~/utils/cn";
|
||||
const variantClasses = {
|
||||
basic:
|
||||
"bg-background-bright border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
|
||||
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50"
|
||||
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
|
||||
};
|
||||
|
||||
type Variant = keyof typeof variantClasses;
|
||||
@@ -111,11 +111,13 @@ export function InfoIconTooltip({
|
||||
buttonClassName,
|
||||
contentClassName,
|
||||
variant = "basic",
|
||||
disableHoverableContent = false,
|
||||
}: {
|
||||
content: React.ReactNode;
|
||||
buttonClassName?: string;
|
||||
contentClassName?: string;
|
||||
variant?: Variant;
|
||||
disableHoverableContent?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<SimpleTooltip
|
||||
@@ -125,6 +127,7 @@ export function InfoIconTooltip({
|
||||
content={content}
|
||||
variant={variant}
|
||||
className={contentClassName}
|
||||
disableHoverableContent={disableHoverableContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+37
-1
@@ -24,12 +24,13 @@ import { Label } from "~/components/primitives/Label";
|
||||
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { InfoIconTooltip } from "~/components/primitives/Tooltip";
|
||||
import { prisma } from "~/db.server";
|
||||
import { featuresForRequest } from "~/features.server";
|
||||
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import { getBillingAlerts, setBillingAlert } from "~/services/platform.v3.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { formatCurrency } from "~/utils/numberFormatter";
|
||||
import { formatCurrency, formatNumber } from "~/utils/numberFormatter";
|
||||
import {
|
||||
docsPath,
|
||||
OrganizationParamsSchema,
|
||||
@@ -183,6 +184,8 @@ export default function Page() {
|
||||
|
||||
const checkboxLevels = [0.75, 0.9, 1.0, 2.0, 5.0];
|
||||
|
||||
const spikeAlertLevels = [10.0, 20.0, 50.0, 100.0];
|
||||
|
||||
useEffect(() => {
|
||||
if (alerts.emails.length > 0) {
|
||||
requestIntent(form.ref.current ?? undefined, list.append(emails.name));
|
||||
@@ -272,6 +275,39 @@ export default function Page() {
|
||||
))}
|
||||
<FormError id={alertLevels.errorId}>{alertLevels.error}</FormError>
|
||||
</InputGroup>
|
||||
<InputGroup fullWidth>
|
||||
<div className="flex items-center gap-1">
|
||||
<Label>Spike alerts</Label>
|
||||
<InfoIconTooltip
|
||||
content={
|
||||
"Catch runaway usage from bugs or errors. We recommend keeping these enabled as a safety net."
|
||||
}
|
||||
disableHoverableContent
|
||||
/>
|
||||
</div>
|
||||
{spikeAlertLevels.map((level) => (
|
||||
<CheckboxWithLabel
|
||||
name={alertLevels.name}
|
||||
id={`level_${level}`}
|
||||
key={level}
|
||||
value={level.toString()}
|
||||
variant="simple/small"
|
||||
label={
|
||||
<span>
|
||||
{formatNumber(level * 100)}%{" "}
|
||||
<span className="text-text-dimmed">
|
||||
({formatCurrency(Number(dollarAmount) * level, false)})
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
defaultChecked={
|
||||
alerts.alertLevels.includes(level) ||
|
||||
!spikeAlertLevels.some((l) => alerts.alertLevels.includes(l))
|
||||
}
|
||||
className="pr-0"
|
||||
/>
|
||||
))}
|
||||
</InputGroup>
|
||||
<InputGroup fullWidth>
|
||||
<Label htmlFor={emails.id}>Email addresses</Label>
|
||||
{emailFields.map((email, index) => (
|
||||
|
||||
Reference in New Issue
Block a user