Skip to main content

Broadcasting an Alert

Some events can't wait for someone to happen to glance at the right camera: a spreading fire, an evacuation boundary, a hazard other agencies need to know about now. The Alerts feature lets an authorized operator draw that event on the map and push it — as a marker every user can see, and as email/SMS/phone notifications to everyone whose subscription matches.

The form​

AlertMessage (in components/molecules/Notifications/) collects the alert: a location picked interactively on the map (latitude, longitude, and a radius up to 10 miles), an urgency level, a message, the recipients (specific agencies, specific users, or broadcast), and optionally a snapshot of the map for spatial context.

The snapshot is the operator's own ArcGIS map — every layer they have turned on, plus the alert pin and radius circle already drawn on it — not a separate render. Only the framing is controlled: captureAlertMapImage (util/map/) moves the live view so the alert origin is centred at a radius-derived 16:9 extent (half-height max(2.2 × radius, 1.5 mi), see alertMapGeometry.ts), waits for tiles, captures 1920×1080, and restores the previous viewpoint. Every recipient therefore sees the same framing of the real map regardless of how the operator had it panned or zoomed.

Validation is react-hook-form + a Zod schema, and the schema is the contract worth reading:

const AlertMessageSchema = z.object({
message: z.string(),
latitude: z.number(),
longitude: z.number(),
radiusInMiles: z.number().min(0).max(10),
recipientAgencyIds: z.array(z.number()).nullable(),
recipientEntraUserIds: z.array(z.string()).nullable(),
imageUrl: z.string().nullable(),
cameraId: z.string(),
urgencyLevel: z.number().min(0).max(4), // 0=None, 1=Low, 2=Medium, 4=High
mapImage: z.instanceof(Blob).nullable(),
});

Note urgencyLevel — those aren't sequential values. They're bitmask levels, and the subscription side depends on that; the next lesson explains the scheme.

The journey after submit​

Because the payload can carry a map-image Blob, this route breaks the usual JSON pattern: the form posts multipart FormData to /api/alerts/alert-create, where the BFF parses the multipart body, re-validates, and forwards fields plus image to the backend.

From there, the backend takes over — this is the hand-off boundary where frontend work ends:

  1. A marker is created on the ESRI map, visible to users with map access (clicking it goes through the AGOL click pipeline)
  2. The alert's location, radius, urgency, and timing are matched against user subscriptions
  3. Notifications go out on each recipient's chosen channels

Frontend feedback is standard: inline Zod errors before submit, success/error surfaced in the UI after.

Wrapping up​

One form, one Zod contract, one multipart hop through the BFF — then the backend fans it out to the map and to every matching subscriber. Which subscribers match is the other half of the story: Notification Settings.