From cddaefd9420507318d71f56355ff5a6648dcd951 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 19 Jan 2025 13:55:40 +0000 Subject: feat: Change webhooks to be configurable by users --- apps/web/components/settings/WebhookSettings.tsx | 514 +++++++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 apps/web/components/settings/WebhookSettings.tsx (limited to 'apps/web/components/settings/WebhookSettings.tsx') diff --git a/apps/web/components/settings/WebhookSettings.tsx b/apps/web/components/settings/WebhookSettings.tsx new file mode 100644 index 00000000..4f7f72dc --- /dev/null +++ b/apps/web/components/settings/WebhookSettings.tsx @@ -0,0 +1,514 @@ +"use client"; + +import React from "react"; +import { ActionButton } from "@/components/ui/action-button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { FullPageSpinner } from "@/components/ui/full-page-spinner"; +import { Input } from "@/components/ui/input"; +import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; +import { api } from "@/lib/trpc"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Edit, KeyRound, Plus, Save, Trash2, X } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { + zNewWebhookSchema, + zUpdateWebhookSchema, + ZWebhook, +} from "@hoarder/shared/types/webhooks"; + +import ActionConfirmingDialog from "../ui/action-confirming-dialog"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "../ui/table"; +import { WebhookEventSelector } from "./WebhookEventSelector"; + +export function WebhooksEditorDialog() { + const { t } = useTranslation(); + const [open, setOpen] = React.useState(false); + const apiUtils = api.useUtils(); + + const form = useForm>({ + resolver: zodResolver(zNewWebhookSchema), + defaultValues: { + url: "", + events: [], + token: "", + }, + }); + + React.useEffect(() => { + if (open) { + form.reset(); + } + }, [open]); + + const { mutateAsync: createWebhook, isPending: isCreating } = + api.webhooks.create.useMutation({ + onSuccess: () => { + toast({ + description: "Webhook has been created!", + }); + apiUtils.webhooks.list.invalidate(); + setOpen(false); + }, + }); + + return ( + + + + + + + {t("settings.webhooks.create_webhook")} + +
+ { + await createWebhook(value); + form.resetField("url"); + form.resetField("events"); + })} + > + { + return ( + + URL + + + + + + ); + }} + /> + ( + + {t("settings.webhooks.auth_token")} + + + + + + )} + /> + ( + + Events + + + + )} + /> + + + + + + + { + await createWebhook(value); + })} + loading={isCreating} + variant="default" + className="items-center" + > + + Add + + +
+
+ ); +} + +export function EditWebhookDialog({ webhook }: { webhook: ZWebhook }) { + const { t } = useTranslation(); + const apiUtils = api.useUtils(); + const [open, setOpen] = React.useState(false); + React.useEffect(() => { + if (open) { + form.reset({ + webhookId: webhook.id, + url: webhook.url, + events: webhook.events, + }); + } + }, [open]); + const { mutateAsync: updateWebhook, isPending: isUpdating } = + api.webhooks.update.useMutation({ + onSuccess: () => { + toast({ + description: "Webhook has been updated!", + }); + setOpen(false); + apiUtils.webhooks.list.invalidate(); + }, + }); + const updateSchema = zUpdateWebhookSchema.required({ + events: true, + url: true, + }); + const form = useForm>({ + resolver: zodResolver(updateSchema), + defaultValues: { + webhookId: webhook.id, + url: webhook.url, + events: webhook.events, + }, + }); + return ( + + + + + + + {t("settings.webhooks.edit_webhook")} + + +
+ { + await updateWebhook(value); + })} + > + { + return ( + + + + + + + ); + }} + /> + { + return ( + + {t("common.url")} + + + + + + ); + }} + /> + ( + + {t("settings.webhooks.events.title")} + + + + )} + /> + + + + + + + { + await updateWebhook(value); + })} + type="submit" + className="items-center" + > + + {t("actions.save")} + + +
+
+ ); +} + +export function EditTokenDialog({ webhook }: { webhook: ZWebhook }) { + const { t } = useTranslation(); + const apiUtils = api.useUtils(); + const [open, setOpen] = React.useState(false); + React.useEffect(() => { + if (open) { + form.reset({ + webhookId: webhook.id, + token: "", + }); + } + }, [open]); + + const updateSchema = zUpdateWebhookSchema + .pick({ + webhookId: true, + token: true, + }) + .required({ + token: true, + }); + + const form = useForm>({ + resolver: zodResolver(updateSchema), + defaultValues: { + webhookId: webhook.id, + token: "", + }, + }); + + const { mutateAsync: updateWebhook, isPending: isUpdating } = + api.webhooks.update.useMutation({ + onSuccess: () => { + toast({ + description: "Webhook token has been updated!", + }); + setOpen(false); + apiUtils.webhooks.list.invalidate(); + }, + }); + + return ( + + + + + + + {t("settings.webhooks.edit_auth_token")} + +
+ { + await updateWebhook(value); + })} + > + ( + + + + + + )} + /> + ( + + {t("settings.webhooks.auth_token")} + + + + + + )} + /> + + + + + + + { + await updateWebhook({ + webhookId: value.webhookId, + token: null, + }); + })} + className="items-center" + > + + {t("actions.delete")} + + { + await updateWebhook(value); + })} + type="submit" + className="items-center" + > + + {t("actions.save")} + + +
+
+ ); +} + +export function WebhookRow({ webhook }: { webhook: ZWebhook }) { + const { t } = useTranslation(); + const apiUtils = api.useUtils(); + const { mutate: deleteWebhook, isPending: isDeleting } = + api.webhooks.delete.useMutation({ + onSuccess: () => { + toast({ + description: "Webhook has been deleted!", + }); + apiUtils.webhooks.list.invalidate(); + }, + }); + + return ( + + {webhook.url} + {webhook.events.join(", ")} + {webhook.hasToken ? "*******" : } + + + + ( + deleteWebhook({ webhookId: webhook.id })} + className="items-center" + type="button" + > + + {t("actions.delete")} + + )} + > + + + + + ); +} + +export default function WebhookSettings() { + const { t } = useTranslation(); + const { data: webhooks, isLoading } = api.webhooks.list.useQuery(); + return ( +
+
+
+ + {t("settings.webhooks.webhooks")} + + +
+

+ {t("settings.webhooks.description")} +

+ {isLoading && } + {webhooks && webhooks.webhooks.length == 0 && ( +

+ You don't have any webhooks configured yet. +

+ )} + {webhooks && webhooks.webhooks.length > 0 && ( + + + + {t("common.url")} + {t("settings.webhooks.events.title")} + {t("settings.webhooks.auth_token")} + {t("common.actions")} + + + + {webhooks.webhooks.map((webhook) => ( + + ))} + +
+ )} +
+
+ ); +} -- cgit v1.2.3-70-g09d2