import { useMemo, useState } from "react"; import { useEffect } from "react"; import { useParams, useLocation, useNavigate } from "react-router"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTheme } from "@mui/material"; import Stack from "@mui/material/Stack"; import RadioGroup from "@mui/material/RadioGroup"; import FormControl from "@mui/material/FormControl"; import { Trans, useTranslation } from "react-i18next"; import MenuItem from "@mui/material/MenuItem"; import Typography from "@mui/material/Typography"; import Link from "@mui/material/Link"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; import { Trash2 } from "lucide-react"; import { HeaderDeleteControls } from "@/Components/v2/monitors"; import { BasePage, ConfigBox } from "@/Components/v2/design-elements"; import { RadioWithDescription, Button, TextField, Select, Autocomplete, SwitchComponent as Switch, SliderWithLabel, Dialog, } from "@/Components/v2/inputs"; import { useGet, usePost, usePatch, useDelete } from "@/Hooks/UseApi"; import { useMonitorForm } from "@/Hooks/useMonitorForm"; import type { Monitor, MonitorType, GamesMap } from "@/Types/Monitor"; import type { Notification } from "@/Types/Notification"; import type { MonitorFormData } from "@/Validation/monitor"; interface GeneralSettingsConfig { urlLabel: string; urlPlaceholder: string; namePlaceholder: string; showUrl: boolean; showPort: boolean; showGameSelect: boolean; showSecret: boolean; } const getGeneralSettingsConfig = ( type: MonitorType, t: (key: string) => string ): GeneralSettingsConfig => { const configs: Record = { http: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: false, showGameSelect: false, showSecret: false, }, ping: { urlLabel: t("pages.createMonitor.form.general.option.host.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.host.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: false, showGameSelect: false, showSecret: false, }, docker: { urlLabel: t("pages.createMonitor.form.general.option.container.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.container.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: false, showGameSelect: false, showSecret: false, }, port: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: true, showGameSelect: false, showSecret: false, }, game: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: true, showGameSelect: true, showSecret: false, }, pagespeed: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: false, showGameSelect: false, showSecret: false, }, hardware: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"), namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"), showUrl: true, showPort: false, showGameSelect: false, showSecret: true, }, }; return configs[type] || configs.http; }; const CreateMonitorPage = () => { const theme = useTheme(); const { t } = useTranslation(); const { monitorId } = useParams(); const location = useLocation(); const navigate = useNavigate(); const isEditMode = Boolean(monitorId); // Extract page type from URL path (e.g., /pagespeed/create -> pagespeed) const pageType = useMemo(() => { const pathSegments = location.pathname.split("/").filter(Boolean); const firstSegment = pathSegments[0]; if (firstSegment === "pagespeed") return "pagespeed"; if (firstSegment === "infrastructure") return "hardware"; return "uptime"; }, [location.pathname]); const showTypeSelector = pageType === "uptime" && !isEditMode; const defaultType: MonitorType = pageType === "pagespeed" ? "pagespeed" : pageType === "hardware" ? "hardware" : "http"; const { data: existingMonitor, refetch: refetchMonitor } = useGet( isEditMode ? `/monitors/${monitorId}` : null ); const { data: notifications } = useGet("/notifications/team"); const { data: games } = useGet("/monitors/games"); const { schema, defaults } = useMonitorForm({ data: existingMonitor ?? null, defaultType, }); const form = useForm({ resolver: zodResolver(schema), defaultValues: defaults, }); const { control, watch, handleSubmit, clearErrors } = form; useEffect(() => { form.reset(defaults); }, [defaults, form]); const watchedType = watch("type") as MonitorType; const watchedUseAdvancedMatching = watch("useAdvancedMatching") as boolean; useEffect(() => { clearErrors(); }, [watchedType, clearErrors]); const generalSettingsConfig = useMemo( () => getGeneralSettingsConfig(watchedType, t), [watchedType, t] ); const { post, loading: isCreating } = usePost(); const { patch, loading: isUpdating } = usePatch(); const isSubmitting = isCreating || isUpdating; // Delete functionality const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const { deleteFn, loading: isDeleting } = useDelete(); const handleDeleteClick = () => { setIsDeleteDialogOpen(true); }; const handleDeleteConfirm = async () => { if (!monitorId) return; await deleteFn(`/monitors/${monitorId}`); setIsDeleteDialogOpen(false); // Navigate based on page type if (pageType === "pagespeed") { navigate("/pagespeed"); } else if (pageType === "hardware") { navigate("/infrastructure"); } else { navigate("/uptime"); } }; const handleDeleteCancel = () => { setIsDeleteDialogOpen(false); }; const onSubmit = async (data: MonitorFormData) => { let result; if (isEditMode && monitorId) { result = await patch(`/monitors/${monitorId}`, data); } else { result = await post("/monitors", data); } if (result?.success) { if (pageType === "pagespeed") { navigate("/pagespeed"); } else if (pageType === "hardware") { navigate("/infrastructure"); } else { navigate("/uptime"); } } }; const onError = (errors: unknown) => { console.log(errors); }; return ( {/* Monitor Type Selection - only shown for uptime monitors */} {showTypeSelector && ( ( )} /> } /> )} {/* URL/Host/Container field - not shown for hardware */} {generalSettingsConfig.showUrl && ( ( )} /> )} {/* Port field - only for port and game types */} {generalSettingsConfig.showPort && ( ( { const val = e.target.value; field.onChange(val === "" ? 0 : Number(val)); }} type="number" fieldLabel={t("pages.createMonitor.form.general.option.port.label")} placeholder={t( "pages.createMonitor.form.general.option.port.placeholder" )} fullWidth error={!!fieldState.error} helperText={fieldState.error?.message ?? ""} /> )} /> )} {/* Game select - only for game type */} {generalSettingsConfig.showGameSelect && ( ( )} /> )} {/* Secret field - only for hardware type */} {generalSettingsConfig.showSecret && ( ( )} /> )} ( )} /> } /> ( )} /> } /> ( )} /> ( )} /> } /> { // Map notifications to have 'name' property for v2 Autocomplete const notificationOptions = (notifications ?? []).map((n) => ({ ...n, name: n.notificationName, })); const selectedNotifications = notificationOptions.filter((n) => (field.value ?? []).includes(n.id) ); return ( option.name} onChange={(_: unknown, newValue: typeof notificationOptions) => { field.onChange(newValue.map((n) => n.id)); }} isOptionEqualToValue={(option, value) => option.id === value.id} /> {selectedNotifications.length > 0 && ( {selectedNotifications.map((notification, index) => ( {notification.notificationName} { field.onChange( (field.value ?? []).filter( (id: string) => id !== notification.id ) ); }} aria-label="Remove notification" > {index < selectedNotifications.length - 1 && } ))} )} ); }} /> } /> {watchedType === "http" && ( ( field.onChange(e.target.checked)} /> {t("pages.createMonitor.form.ignoreTls.option.tls.label")} )} /> } /> )} {watchedType === "http" && ( ( field.onChange(e.target.checked)} /> {t( "pages.createMonitor.form.advanced.option.advancedMatching.label" )} )} /> {watchedUseAdvancedMatching && ( ( )} /> ( )} /> ( )} /> ), }} /> )} } /> )} ); }; export default CreateMonitorPage;