"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { type Region, monitorPeriodicity, } from "@openstatus/db/src/schema/constants"; import { formatRegionCode, groupByContinent, regionDict, } from "@openstatus/regions"; import { Button } from "@openstatus/ui/components/ui/button"; import { Checkbox } from "@openstatus/ui/components/ui/checkbox"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@openstatus/ui/components/ui/form"; import { Slider } from "@openstatus/ui/components/ui/slider"; import { cn } from "@openstatus/ui/lib/utils"; import { useQuery } from "@tanstack/react-query"; import { isTRPCClientError } from "@trpc/client"; import { CircleX, Globe, Info } from "lucide-react"; import { useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { IconCloudProviderTooltip } from "@/components/common/icon-cloud-provider"; import { Link } from "@/components/common/link"; import { Note, NoteButton } from "@/components/common/note"; import { UpgradeDialog } from "@/components/dialogs/upgrade"; import { FormCard, FormCardContent, FormCardDescription, FormCardFooter, FormCardFooterInfo, FormCardHeader, FormCardSeparator, FormCardTitle, } from "@/components/forms/form-card"; import { useTRPC } from "@/lib/trpc/client"; const DEFAULT_PERIODICITY = "10m"; const DEFAULT_REGIONS = ["ams", "fra", "iad", "syd", "jnb", "gru"]; const PERIODICITY = monitorPeriodicity.filter((p) => p !== "other"); const DEFAULT_PRIVATE_LOCATIONS = [] satisfies { id: number; name: string }[]; const schema = z.object({ regions: z.array(z.string()), periodicity: z.enum(monitorPeriodicity), privateLocations: z.array(z.number()), }); type FormValues = z.infer; export function FormSchedulingRegions({ defaultValues, onSubmit, privateLocations, ...props }: Omit, "onSubmit"> & { defaultValues?: FormValues; onSubmit: (values: FormValues) => Promise; privateLocations: { id: number; name: string }[]; }) { const trpc = useTRPC(); const [openDialog, setOpenDialog] = useState(false); const { data: workspace } = useQuery(trpc.workspace.get.queryOptions()); const form = useForm({ resolver: zodResolver(schema), defaultValues: defaultValues ?? { regions: DEFAULT_REGIONS, periodicity: DEFAULT_PERIODICITY, privateLocations: DEFAULT_PRIVATE_LOCATIONS, }, }); const [isPending, startTransition] = useTransition(); const watchPeriodicity = form.watch("periodicity"); const watchRegions = form.watch("regions"); const watchPrivateLocations = form.watch("privateLocations"); function submitAction(values: FormValues) { if (isPending) return; startTransition(async () => { try { const promise = onSubmit(values); toast.promise(promise, { loading: "Saving...", success: () => "Saved", error: (error) => { if (isTRPCClientError(error)) { return error.message; } console.error(error); return "Failed to save"; }, }); await promise; } catch (error) { console.error(error); } }); } if (!workspace) return null; const allowedRegions = workspace.limits.regions.filter( (r) => !regionDict[r as keyof typeof regionDict].deprecated, ); const maxRegions = workspace.limits["max-regions"]; const periodicity = workspace.limits.periodicity; const isMaxed = watchRegions.length >= maxRegions; return (
Scheduling & Regions Configure the scheduling and regions for your monitor. ( Periodicity
{ field.onChange(PERIODICITY[value[0]]); }} className={cn( !periodicity.includes(watchPeriodicity) && "[&_[data-slot=slider-range]]:bg-destructive", )} />
)} /> {!periodicity.includes(watchPeriodicity) ? ( The periodicity you are selecting is not allowed for your plan. setOpenDialog(true)}> Upgrade your plan ) : null}
To minimize false positives, we recommend monitoring your endpoint in at least 3 regions. (
{Object.entries(groupByContinent).map( ([continent, r]) => { const selected = r .filter((r) => allowedRegions.includes(r.code)) .reduce((prev, curr) => { return ( prev + (watchRegions.includes(curr.code) ? 1 : 0) ); }, 0); const isAllSelected = selected === r.filter((r) => allowedRegions.includes(r.code)) .length; const disabled = r.length + watchRegions.length - selected > maxRegions; return (
{continent}{" "} ({selected}/{r.length})
{r.map((region) => { return ( { const checked = field.value?.includes( region.code, ); const disabled = checked ? false : !allowedRegions.includes( region.code, ) || isMaxed; const deprecated = region.deprecated; return ( { if (checked) { field.onChange([ ...field.value, region.code, ]); } else { field.onChange( field.value?.filter( (r) => r !== region.code, ), ); } }} /> {formatRegionCode(region.code)}{" "} {region.flag} {region.location} ); }} /> ); })}
); }, )}
)} />
{privateLocations.length === 0 ? ( Monitor your endpoints from private locations. Learn more ) : ( (
Private Locations{" "} ({watchPrivateLocations.length}/ {privateLocations.length})
{privateLocations.map((item) => ( { return ( { return checked ? field.onChange([ ...field.value, item.id, ]) : field.onChange( field.value?.filter( (value) => value !== item.id, ), ); }} /> {item.name} ); }} /> ))}
)} /> )}
Your plan allows you to run{" "} {maxRegions}{" "} out of{" "} {allowedRegions.length} {" "} regions. Learn more about{" "} Regions {" "} and{" "} Periodicity .
); }