import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import Link from "next/link"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod/v4"; import { api } from "@/src/utils/api"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/src/components/ui/card"; import { Button } from "@/src/components/ui/button"; import { Input } from "@/src/components/ui/input"; import { Switch } from "@/src/components/ui/switch"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/src/components/ui/form"; import { toast } from "sonner"; import { Bell, Plus, X } from "lucide-react"; const usageAlertsSchema = z.object({ enabled: z.boolean(), threshold: z.number().int().positive(), notifications: z.object({ email: z.boolean(), recipients: z.array(z.string().email()), }), }); type UsageAlertsFormData = z.infer; export function UsageAlerts({ orgId }: { orgId: string }) { const [newRecipient, setNewRecipient] = useState(""); const [isAddingRecipient, setIsAddingRecipient] = useState(false); const { data: usageAlerts, isLoading, refetch, } = api.cloudBilling.getUsageAlerts.useQuery( { orgId }, { enabled: Boolean(orgId) }, ); const upsertUsageAlerts = api.cloudBilling.upsertUsageAlerts.useMutation({ onSuccess: () => { toast.success("Usage alerts updated", { description: "Your usage alert settings have been saved successfully.", }); refetch(); }, onError: (error) => { toast.error("Failed to update usage alerts", { description: error.message, }); }, }); const form = useForm({ resolver: zodResolver(usageAlertsSchema), defaultValues: { enabled: false, threshold: 100000, notifications: { email: true, recipients: [], }, }, }); useEffect(() => { // Overwrite from with existing usage alerts if available if (usageAlerts) { form.reset({ enabled: usageAlerts.enabled, threshold: usageAlerts.threshold, notifications: { email: usageAlerts.notifications.email, recipients: usageAlerts.notifications.recipients || [], }, }); } }, [usageAlerts, form]); const onSubmit = (data: UsageAlertsFormData) => { upsertUsageAlerts.mutate({ orgId, usageAlerts: data, }); }; const addRecipient = () => { if (!newRecipient.trim()) return; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(newRecipient)) { toast.error("Invalid email address", { description: "Please enter a valid email address.", }); return; } const currentRecipients = form.getValues("notifications.recipients"); if (currentRecipients.includes(newRecipient)) { toast.error("Email already added", { description: "This email address is already in the recipient list.", }); return; } form.setValue("notifications.recipients", [ ...currentRecipients, newRecipient, ]); setNewRecipient(""); setIsAddingRecipient(false); }; const removeRecipient = (emailToRemove: string) => { const currentRecipients = form.getValues("notifications.recipients"); form.setValue( "notifications.recipients", currentRecipients.filter((email) => email !== emailToRemove), ); }; const recipients = form.watch("notifications.recipients"); const isEnabled = form.watch("enabled"); if (isLoading) { return ( Usage Alerts Loading usage alert settings... ); } return (
Usage Alerts Get notified when your usage exceeds a specified threshold to avoid billing surprises. The alert triggers at most once per billing cycle and will only consider "future" usage from the time of creation or last update.
( )} />
{isEnabled && ( <> ( Threshold Amount
field.onChange(Number(e.target.value)) } className="flex-1" /> Events
You'll receive an alert when your usage exceeds this number of events in your billing period. Go to our{" "} pricing calculator {" "} to translate events into cost.
)} />

Email Recipients

Organization admins will automatically receive alerts. Add additional recipients below.

{recipients.map((email, index) => (
{email}
))} {isAddingRecipient ? (
setNewRecipient(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addRecipient(); } }} className="flex-1" />
) : ( )}
)}
); }