Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions client/src/Hooks/useNotificationForm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useMemo } from "react";
import { notificationSchema } from "@/Validation/notifications";
import { notificationSchema, editNotificationSchema } from "@/Validation/notifications";
import type { NotificationFormData } from "@/Validation/notifications";
import type { Notification } from "@/Types/Notification";

interface UseNotificationFormOptions {
data?: Notification | null;
isEditMode?: boolean;
}

function buildDefaults(data: Notification | null): NotificationFormData {
Expand Down Expand Up @@ -44,6 +45,10 @@ function buildDefaults(data: Notification | null): NotificationFormData {
type: "webhook",
notificationName: data.notificationName || "",
address: data.address || "",
authType: data.authType || "none",
authUsername: data.authUsername || "",
authPassword: data.authPassword || "",
authToken: data.authToken || "",
};
}
if (data?.type === "pager_duty") {
Expand Down Expand Up @@ -86,9 +91,15 @@ function buildDefaults(data: Notification | null): NotificationFormData {
};
}

export const useNotificationForm = ({ data = null }: UseNotificationFormOptions = {}) => {
export const useNotificationForm = ({
data = null,
isEditMode = false,
}: UseNotificationFormOptions = {}) => {
return useMemo(() => {
const defaults = buildDefaults(data);
return { schema: notificationSchema, defaults };
}, [data]);
return {
schema: isEditMode ? editNotificationSchema : notificationSchema,
defaults,
};
}, [data, isEditMode]);
};
164 changes: 149 additions & 15 deletions client/src/Pages/Notifications/create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ import { useNavigate } from "react-router-dom";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useGet, usePost, usePatch } from "@/Hooks/UseApi";
import { useToast } from "@/Hooks/UseToast";
import { useNotificationForm } from "@/Hooks/useNotificationForm";
import type { NotificationFormData } from "@/Validation/notifications";
import { notificationSchema } from "@/Validation/notifications";
import type { Notification } from "@/Types/Notification";
import type { ZodIssue } from "zod";
import { useTranslation } from "react-i18next";
import { NotificationChannels } from "@/Types/Notification";
import { NotificationChannels, WebhookAuthTypes } from "@/Types/Notification";

const NotificationsCreatePage = () => {
const { t } = useTranslation();
const theme = useTheme();
const navigate = useNavigate();
const { toastError } = useToast();
const { notificationId } = useParams<{ notificationId: string }>();
const isEditMode = Boolean(notificationId);

Expand All @@ -32,20 +36,28 @@ const NotificationsCreatePage = () => {
const { patch, loading: isPatching } = usePatch<NotificationFormData, Notification>();
const { post: testPost, loading: isTesting } = usePost<NotificationFormData, void>();

const { schema, defaults } = useNotificationForm({ data: existingNotification });
const { schema, defaults } = useNotificationForm({
data: existingNotification,
isEditMode,
});

const form = useForm<NotificationFormData>({
resolver: zodResolver(schema),
defaultValues: defaults,
});

const { control, watch, reset, handleSubmit, clearErrors, trigger, getValues } = form;
const { control, watch, reset, handleSubmit, clearErrors, getValues } = form;

useEffect(() => {
reset(defaults);
}, [defaults, reset]);

const watchedType = watch("type");
const watchedAuthType = watch("authType");

const keepExistingHint = isEditMode
? t("pages.notifications.form.secrets.placeholderKeepExisting")
: undefined;

useEffect(() => {
clearErrors();
Expand Down Expand Up @@ -86,9 +98,24 @@ const NotificationsCreatePage = () => {
};

const handleTest = async () => {
const isValid = await trigger();
if (!isValid) return;
const data = getValues();
const result = notificationSchema.safeParse(data);
if (!result.success) {
toastError("Please provide all required credentials to test this notification.");
result.error.issues.forEach((err: ZodIssue, index: number) => {
if (err.path.length > 0) {
form.setError(
err.path[0] as keyof NotificationFormData,
{
type: "manual",
message: err.message,
},
{ shouldFocus: index === 0 }
);
}
});
return;
}
await testPost("/notifications/test", data);
};

Expand Down Expand Up @@ -174,6 +201,107 @@ const NotificationsCreatePage = () => {
}
/>
)}
{watchedType === "webhook" && (
<ConfigBox
title={t("pages.notifications.form.webhookAuth.title")}
subtitle={t("pages.notifications.form.webhookAuth.description")}
rightContent={
<Stack spacing={theme.spacing(8)}>
<Controller
name="authType"
control={control}
defaultValue={"authType" in defaults ? defaults.authType : "none"}
render={({ field }) => (
<Select
value={field.value}
fieldLabel={t("pages.notifications.form.webhookAuth.optionAuthType")}
onChange={field.onChange}
>
{WebhookAuthTypes.map((authType) => (
<MenuItem
key={authType}
value={authType}
>
<Typography>
{t(
`pages.notifications.form.webhookAuth.auth${authType.charAt(0).toUpperCase() + authType.slice(1)}`
)}
</Typography>
</MenuItem>
))}
</Select>
)}
/>
{watchedAuthType === "basic" && (
<>
<Controller
name="authUsername"
control={control}
defaultValue={"authUsername" in defaults ? defaults.authUsername : ""}
render={({ field, fieldState }) => (
<TextField
{...field}
type="text"
fieldLabel={t(
"pages.notifications.form.webhookAuth.optionUsername"
)}
placeholder={t(
"pages.notifications.form.webhookAuth.placeholderUsername"
)}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
/>
)}
/>
<Controller
name="authPassword"
control={control}
defaultValue={"authPassword" in defaults ? defaults.authPassword : ""}
render={({ field, fieldState }) => (
<TextField
{...field}
type="password"
fieldLabel={t(
"pages.notifications.form.webhookAuth.optionPassword"
)}
placeholder={
keepExistingHint ??
t("pages.notifications.form.webhookAuth.placeholderPassword")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
/>
)}
/>
</>
)}
{watchedAuthType === "bearer" && (
<Controller
name="authToken"
control={control}
defaultValue={"authToken" in defaults ? defaults.authToken : ""}
render={({ field, fieldState }) => (
<TextField
{...field}
type="password"
fieldLabel={t("pages.notifications.form.webhookAuth.optionToken")}
placeholder={
keepExistingHint ??
t("pages.notifications.form.webhookAuth.placeholderToken")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
/>
)}
/>
)}
</Stack>
}
/>
)}
{watchedType === "telegram" && (
<ConfigBox
title={t("pages.notifications.form.telegram.title")}
Expand All @@ -189,9 +317,10 @@ const NotificationsCreatePage = () => {
{...field}
type="text"
fieldLabel={t("pages.notifications.form.telegram.optionBotToken")}
placeholder={t(
"pages.notifications.form.telegram.placeholderBotToken"
)}
placeholder={
keepExistingHint ??
t("pages.notifications.form.telegram.placeholderBotToken")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
Expand Down Expand Up @@ -232,9 +361,10 @@ const NotificationsCreatePage = () => {
{...field}
type="text"
fieldLabel={t("pages.notifications.form.pushover.optionAppToken")}
placeholder={t(
"pages.notifications.form.pushover.placeholderAppToken"
)}
placeholder={
keepExistingHint ??
t("pages.notifications.form.pushover.placeholderAppToken")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
Expand Down Expand Up @@ -296,9 +426,10 @@ const NotificationsCreatePage = () => {
{...field}
type="text"
fieldLabel={t("pages.notifications.form.twilio.optionAuthToken")}
placeholder={t(
"pages.notifications.form.twilio.placeholderAuthToken"
)}
placeholder={
keepExistingHint ??
t("pages.notifications.form.twilio.placeholderAuthToken")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
Expand Down Expand Up @@ -394,7 +525,10 @@ const NotificationsCreatePage = () => {
fieldLabel={t(
"pages.notifications.form.accessToken.optionAccessToken"
)}
placeholder={t("pages.notifications.form.accessToken.placeholder")}
placeholder={
keepExistingHint ??
t("pages.notifications.form.accessToken.placeholder")
}
fullWidth
error={!!fieldState.error}
helperText={fieldState.error?.message ?? ""}
Expand Down
7 changes: 7 additions & 0 deletions client/src/Types/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export const NotificationChannels = [
] as const;
export type NotificationChannel = (typeof NotificationChannels)[number];

export const WebhookAuthTypes = ["none", "basic", "bearer"] as const;
export type WebhookAuthType = (typeof WebhookAuthTypes)[number];

export interface Notification {
id: string;
userId: string;
Expand All @@ -25,6 +28,10 @@ export interface Notification {
accessToken?: string;
accountSid?: string;
twilioPhoneNumber?: string;
authType?: WebhookAuthType;
authUsername?: string;
authPassword?: string;
authToken?: string;
createdAt: string;
updatedAt: string;
}
Loading
Loading