diff --git a/client/src/Components/inputs/forms/FormDatePickerField.tsx b/client/src/Components/inputs/forms/FormDatePickerField.tsx new file mode 100644 index 000000000..c9b012c14 --- /dev/null +++ b/client/src/Components/inputs/forms/FormDatePickerField.tsx @@ -0,0 +1,43 @@ +import { + Controller, + useFormContext, + type FieldPath, + type FieldValues, +} from "react-hook-form"; +import dayjs from "dayjs"; +import { DatePicker } from "@/Components/inputs"; + +interface FormDatePickerFieldProps { + name: FieldPath; + fieldLabel?: string; + format?: string; + onValueChange?: (value: string) => void; // e.g. () => trigger("startDate") +} + +export const FormDatePickerField = ({ + name, + fieldLabel, + format = "YYYY-MM-DD", + onValueChange, +}: FormDatePickerFieldProps) => { + const { control } = useFormContext(); + return ( + ( + { + const next = date ? date.format(format) : ""; + field.onChange(next); + onValueChange?.(next); + }} + error={!!fieldState.error} + helperText={fieldState.error?.message} + /> + )} + /> + ); +}; diff --git a/client/src/Hooks/useNotificationForm.ts b/client/src/Hooks/useNotificationForm.ts index 0051ddafd..b4b3fad7d 100644 --- a/client/src/Hooks/useNotificationForm.ts +++ b/client/src/Hooks/useNotificationForm.ts @@ -8,10 +8,23 @@ interface UseNotificationFormOptions { } function buildDefaults(data: Notification | null): NotificationFormData { + // Every channel field defaults to "" (not undefined) so untouched required + // fields fail zod with their custom messages instead of a type error. + const base = { + notificationName: data?.notificationName || "", + address: "", + accessToken: "", + accountSid: "", + phone: "", + twilioPhoneNumber: "", + homeserverUrl: "", + roomId: "", + topic: "", + }; if (data?.type === "matrix") { return { + ...base, type: "matrix", - notificationName: data.notificationName || "", homeserverUrl: data.homeserverUrl || "", roomId: data.roomId || "", accessToken: data.accessToken || "", @@ -19,51 +32,51 @@ function buildDefaults(data: Notification | null): NotificationFormData { } if (data?.type === "telegram") { return { + ...base, type: "telegram", - notificationName: data.notificationName || "", address: data.address || "", accessToken: data.accessToken || "", }; } if (data?.type === "slack") { return { + ...base, type: "slack", - notificationName: data.notificationName || "", address: data.address || "", }; } if (data?.type === "discord") { return { + ...base, type: "discord", - notificationName: data.notificationName || "", address: data.address || "", }; } if (data?.type === "webhook") { return { + ...base, type: "webhook", - notificationName: data.notificationName || "", address: data.address || "", }; } if (data?.type === "pager_duty") { return { + ...base, type: "pager_duty", - notificationName: data.notificationName || "", address: data.address || "", }; } if (data?.type === "teams") { return { + ...base, type: "teams", - notificationName: data.notificationName || "", address: data.address || "", }; } if (data?.type === "twilio") { return { + ...base, type: "twilio", - notificationName: data.notificationName || "", accountSid: data.accountSid || "", accessToken: data.accessToken || "", phone: data.phone || "", @@ -72,24 +85,24 @@ function buildDefaults(data: Notification | null): NotificationFormData { } if (data?.type === "pushover") { return { + ...base, type: "pushover", - notificationName: data.notificationName || "", address: data.address || "", accessToken: data.accessToken || "", }; } if (data?.type === "ntfy") { return { + ...base, type: "ntfy", - notificationName: data.notificationName || "", address: data.address || "", topic: data.topic || "", }; } // Default: email (covers both data === null and data.type === "email") return { + ...base, type: "email", - notificationName: data?.notificationName || "", address: data?.address || "", }; } diff --git a/client/src/Pages/Notifications/create/index.tsx b/client/src/Pages/Notifications/create/index.tsx index 84d264616..639391060 100644 --- a/client/src/Pages/Notifications/create/index.tsx +++ b/client/src/Pages/Notifications/create/index.tsx @@ -1,14 +1,12 @@ import { BasePage, ConfigBox } from "@/Components/design-elements"; -import { TextField, Select, Button } from "@/Components/inputs"; -import MenuItem from "@mui/material/MenuItem"; -import Typography from "@mui/material/Typography"; +import { Button } from "@/Components/inputs"; import Stack from "@mui/material/Stack"; import { useTheme } from "@mui/material/styles"; import { useEffect, useMemo } from "react"; import { useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom"; -import { Controller, useForm } from "react-hook-form"; +import { FormProvider, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useGet, usePost, usePatch } from "@/Hooks/UseApi"; import { useNotificationForm } from "@/Hooks/useNotificationForm"; @@ -16,6 +14,8 @@ import type { NotificationFormData } from "@/Validation/notifications"; import type { Notification } from "@/Types/Notification"; import { useTranslation } from "react-i18next"; import { NotificationChannels } from "@/Types/Notification"; +import { FormTextField } from "@/Components/inputs/forms/FormTextField"; +import { FormSelectField } from "@/Components/inputs/forms/FormSelectField"; const NotificationsCreatePage = () => { const { t } = useTranslation(); @@ -93,384 +93,195 @@ const NotificationsCreatePage = () => { }; return ( - - ( - - )} - /> - } - /> - ( - - )} - /> - } - /> - {watchedType !== "matrix" && - watchedType !== "telegram" && - watchedType !== "pushover" && - watchedType !== "twilio" && - watchedType !== "ntfy" && ( + + + + } + /> + ({ + value: channel, + label: t(`pages.notifications.form.type.value.${channel}`), + }))} + /> + } + /> + {watchedType !== "matrix" && + watchedType !== "telegram" && + watchedType !== "pushover" && + watchedType !== "twilio" && + watchedType !== "ntfy" && ( + + } + /> + )} + {watchedType === "ntfy" && ( ( - - )} - /> + + + + } /> )} - {watchedType === "ntfy" && ( - - ( - - )} - /> - ( - - )} - /> - - } - /> - )} - {watchedType === "telegram" && ( - - ( - - )} - /> - ( - - )} - /> - - } - /> - )} - {watchedType === "pushover" && ( - - ( - - )} - /> - ( - - )} - /> - - } - /> - )} - {watchedType === "twilio" && ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - } - /> - )} - {watchedType === "matrix" && ( - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - } - /> - )} - - - - - + + + + + ); }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index dcbaf00f5..142ffc8c7 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1164,7 +1164,20 @@ "type": { "description": "Select the type of notification channel to create.", "optionType": "Type", - "title": "Channel type" + "title": "Channel type", + "value": { + "email": "Email", + "slack": "Slack", + "discord": "Discord", + "webhook": "Webhook", + "pager_duty": "PagerDuty", + "matrix": "Matrix", + "teams": "Teams", + "telegram": "Telegram", + "pushover": "Pushover", + "twilio": "Twilio", + "ntfy": "ntfy" + } }, "telegram": { "title": "Telegram configuration",