From 37dfc67e33cbcf9d0ce78b637e54ced0d13727f0 Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Wed, 27 May 2026 12:07:07 +0530 Subject: [PATCH] feat(client): split uptime monitor creation into a 3-step wizard --- client/src/Pages/CreateMonitor/index.tsx | 53 ++++++++++++------------ client/src/Validation/monitor.ts | 12 +++++- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index dcd0fd97f..6f0865a02 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -47,7 +47,7 @@ import { } from "@/Types/Monitor"; import type { Notification } from "@/Types/Notification"; import type { Tag } from "@/Types/Tag"; -import type { MonitorFormData } from "@/Validation/monitor"; +import { monitorFieldsByType, type MonitorFormData } from "@/Validation/monitor"; interface GeneralSettingsConfig { urlLabel: string; @@ -218,30 +218,31 @@ const getGeneralSettingsConfig = ( const hasAdvancedStep = (type: MonitorType): boolean => type === "http" || type === "grpc" || type === "websocket" || supportsGeoCheck(type); -// Fields validated before advancing past each wizard step (uptime create only). -const STEP_FIELDS: FieldPath[][] = [ - [ - "type", - "url", - "name", - "port", - "gameId", - "grpcServiceName", - "dnsServer", - "dnsRecordType", - ], - ["interval", "statusWindowSize", "statusWindowThreshold", "notifications", "tags"], - [ - "ignoreTlsErrors", - "useAdvancedMatching", - "matchMethod", - "expectedValue", - "jsonPath", - "geoCheckEnabled", - "geoCheckLocations", - "geoCheckInterval", - ], -]; +// The wizard step on which each field is validated. Fields not listed default +// to step 0, so a field added to the schema is validated by default rather than +// silently skipped. Step membership is the only thing hardcoded here; the field +// set itself is derived from the schema via `monitorFieldsByType`. +const FIELD_STEP: Record = { + interval: 1, + statusWindowSize: 1, + statusWindowThreshold: 1, + notifications: 1, + tags: 1, + ignoreTlsErrors: 2, + useAdvancedMatching: 2, + matchMethod: 2, + expectedValue: 2, + jsonPath: 2, + geoCheckEnabled: 2, + geoCheckLocations: 2, + geoCheckInterval: 2, +}; + +// Exactly the selected type's fields that belong to the given step. +const stepFieldsFor = (type: MonitorType, step: number): FieldPath[] => + (monitorFieldsByType[type] ?? []).filter( + (field) => (FIELD_STEP[field] ?? 0) === step + ) as FieldPath[]; const CreateMonitorPage = () => { const theme = useTheme(); @@ -304,7 +305,7 @@ const CreateMonitorPage = () => { const totalSteps = hasAdvancedStep(watchedType) ? 3 : 2; const handleNext = async () => { - const isValid = await trigger(STEP_FIELDS[currentStep]); + const isValid = await trigger(stepFieldsFor(watchedType, currentStep)); if (isValid) { setCurrentStep((step) => step + 1); } diff --git a/client/src/Validation/monitor.ts b/client/src/Validation/monitor.ts index 17be250b1..81b8fff55 100644 --- a/client/src/Validation/monitor.ts +++ b/client/src/Validation/monitor.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { GeoContinents } from "@/Types/GeoCheck"; -import { DnsRecordTypes, PageSpeedStrategies } from "@/Types/Monitor"; +import { DnsRecordTypes, PageSpeedStrategies, type MonitorType } from "@/Types/Monitor"; // URL schema with custom error message const urlSchema = z.url({ message: "Please enter a valid URL" }); @@ -164,6 +164,16 @@ export const monitorSchema = z.discriminatedUnion("type", [ export type MonitorFormData = z.infer; +// Field names present in each monitor type's schema, derived from the schema +// itself. Consumers (e.g. the create wizard's per-step validation) should read +// this rather than hardcoding field lists, so renames/additions stay in sync. +export const monitorFieldsByType = Object.fromEntries( + monitorSchema.options.map((option) => [ + option.shape.type.value, + Object.keys(option.shape), + ]) +) as Record; + // Type-specific schemas exported for individual use export { httpSchema,