diff --git a/client/src/Pages/StatusPage/Create/index.tsx b/client/src/Pages/StatusPage/Create/index.tsx
index 5ef5095ea..c9bc04838 100644
--- a/client/src/Pages/StatusPage/Create/index.tsx
+++ b/client/src/Pages/StatusPage/Create/index.tsx
@@ -1,4 +1,4 @@
-import { BasePage, ConfigBox } from "@/Components/design-elements";
+import { BasePage, ConfigBox, StepProgress } from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import { logger } from "@/Utils/logger";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
@@ -26,6 +26,7 @@ import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useStatusPageForm } from "@/Hooks/useStatusPageForm";
import type { StatusPageFormData } from "@/Validation/statusPage";
+import { statusPageStepCount, statusPageStepFieldsFor } from "@/Validation/statusPage";
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
import type { Monitor } from "@/Types/Monitor";
import type { MonitorDisplayType, StatusPageResponse } from "@/Types/StatusPage";
@@ -86,13 +87,27 @@ const CreateStatusPage = () => {
defaultValues: defaults,
});
- const { control, reset, handleSubmit } = form;
+ const { control, reset, handleSubmit, trigger } = form;
// Reset form when defaults change (from fetched data)
useEffect(() => {
reset(defaults);
}, [defaults, reset]);
+ // Multi-step wizard, create only. The configure (edit) flow keeps the flat
+ // form, where showStep() is always true.
+ const [currentStep, setCurrentStep] = useState(0);
+ const showStep = (step: number) => !isCreate || currentStep === step;
+ const totalSteps = statusPageStepCount();
+
+ const handleNext = async () => {
+ const isValid = await trigger(statusPageStepFieldsFor(currentStep));
+ if (isValid) {
+ setCurrentStep((step) => step + 1);
+ }
+ };
+ const handleBack = () => setCurrentStep((step) => step - 1);
+
const watchedMonitorIds: string[] = form.watch("monitors") ?? [];
const computedTypes: MonitorDisplayType[] = useMemo(() => {
const selectedMonitors = (watchedMonitorIds ?? [])
@@ -200,315 +215,334 @@ const CreateStatusPage = () => {
onSubmit={handleSubmit(onSubmit, onError)}
>
{!isCreate && }
-
- (
- field.onChange(e.target.checked)}
- />
- )}
- />
-
- {t("pages.statusPages.form.access.option.published.name")}
-
-
- }
- />
-
- (
-
- )}
- />
- (
-
- )}
- />
-
- }
- />
- {
- const selectedMonitors = field.value
- .map((id: string) => monitors.find((m) => m.id === id))
- .filter((m): m is Monitor => m !== undefined);
-
- const handleDragEnd = (result: DropResult) => {
- if (!result.destination) return;
- const reordered = Array.from(field.value);
- const [removed] = reordered.splice(result.source.index, 1);
- reordered.splice(result.destination.index, 0, removed);
- field.onChange(reordered);
- };
-
- return (
-
- option.name}
- value={selectedMonitors}
- onChange={(_, newValue) => {
- field.onChange(newValue.map((m: Monitor) => m.id));
- }}
- fieldLabel={t(
- "pages.statusPages.form.monitors.option.monitors.label"
- )}
- renderInput={(params) => (
-
- )}
- />
- {selectedMonitors.length > 0 && (
-
-
- {(provided) => (
-
- {selectedMonitors.map((monitor, index) => (
-
- {(provided) => (
-
-
- {`${monitor.name} (${getMonitorTypeLabel(monitor.type, t)})`}
- {
- field.onChange(
- field.value.filter(
- (id: string) => id !== monitor.id
- )
- );
- }}
- aria-label="Remove monitor"
- >
-
-
-
- )}
-
- ))}
- {provided.placeholder}
-
- )}
-
-
- )}
-
- );
- }}
- />
- }
- />
- (
- option.name}
- value={
- timezones.find((tz: TimezoneOption) => tz._id === field.value) ?? null
- }
- onChange={(_, newValue: TimezoneOption | null) => {
- field.onChange(newValue?._id ?? "");
- }}
- fieldLabel={t("pages.statusPages.form.timezone.option.timezone.label")}
- renderInput={(params) => (
-
+ )}
+ {showStep(0) && (
+
+ (
+ field.onChange(e.target.checked)}
/>
)}
/>
- )}
- />
- }
- />
-
-
+
+ {t("pages.statusPages.form.access.option.published.name")}
+
+
+ }
+ />
+ )}
+ {showStep(0) && (
+
(
- {
- if (file) {
- field.onChange({
- data: file.src,
- contentType: file.file.type,
- });
- } else {
- field.onChange(null);
- }
- }}
+ render={({ field, fieldState }) => (
+
+ )}
+ />
+ (
+
)}
/>
+ }
+ />
+ )}
+ {showStep(0) && (
+ (
-
- )}
- />
-
- }
- />
- (
- (
-
- )}
- />
- )}
- />
- }
- />
-
- (
- {
+ const selectedMonitors = field.value
+ .map((id: string) => monitors.find((m) => m.id === id))
+ .filter((m): m is Monitor => m !== undefined);
+
+ const handleDragEnd = (result: DropResult) => {
+ if (!result.destination) return;
+ const reordered = Array.from(field.value);
+ const [removed] = reordered.splice(result.source.index, 1);
+ reordered.splice(result.destination.index, 0, removed);
+ field.onChange(reordered);
+ };
+
+ return (
+
+ option.name}
+ value={selectedMonitors}
+ onChange={(_, newValue) => {
+ field.onChange(newValue.map((m: Monitor) => m.id));
+ }}
+ fieldLabel={t(
+ "pages.statusPages.form.monitors.option.monitors.label"
+ )}
+ renderInput={(params) => (
+
+ )}
/>
- }
- label={t("pages.statusPages.form.features.option.showCharts.label")}
- />
- )}
+ {selectedMonitors.length > 0 && (
+
+
+ {(provided) => (
+
+ {selectedMonitors.map((monitor, index) => (
+
+ {(provided) => (
+
+
+ {`${monitor.name} (${getMonitorTypeLabel(monitor.type, t)})`}
+ {
+ field.onChange(
+ field.value.filter(
+ (id: string) => id !== monitor.id
+ )
+ );
+ }}
+ aria-label="Remove monitor"
+ >
+
+
+
+ )}
+
+ ))}
+ {provided.placeholder}
+
+ )}
+
+
+ )}
+
+ );
+ }}
/>
+ }
+ />
+ )}
+ {showStep(0) && (
+ (
-
+ option.name}
+ value={
+ timezones.find((tz: TimezoneOption) => tz._id === field.value) ?? null
}
- label={t(
- "pages.statusPages.form.features.option.showInfrastructure.label"
+ onChange={(_, newValue: TimezoneOption | null) => {
+ field.onChange(newValue?._id ?? "");
+ }}
+ fieldLabel={t("pages.statusPages.form.timezone.option.timezone.label")}
+ renderInput={(params) => (
+
)}
/>
)}
/>
- {/*
+ )}
+ {showStep(1) && (
+
+
+ (
+ {
+ if (file) {
+ field.onChange({
+ data: file.src,
+ contentType: file.file.type,
+ });
+ } else {
+ field.onChange(null);
+ }
+ }}
+ />
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+ }
+ />
+ )}
+ {showStep(1) && (
+ (
+ (
+
+ )}
+ />
+ )}
+ />
+ }
+ />
+ )}
+ {showStep(1) && (
+
+ (
+
+ }
+ label={t("pages.statusPages.form.features.option.showCharts.label")}
+ />
+ )}
+ />
+ (
+
+ }
+ label={t(
+ "pages.statusPages.form.features.option.showInfrastructure.label"
+ )}
+ />
+ )}
+ />
+ {/* (
@@ -542,21 +576,46 @@ const CreateStatusPage = () => {
/>
)}
/> */}
-
- }
- />
+
+ }
+ />
+ )}
-
+ {isCreate && (
+
+ )}
+ {isCreate && currentStep < totalSteps - 1 ? (
+
+ ) : (
+
+ )}