"use client"; import { PipelineTriggers, Webhook } from "@prisma/client"; import clsx from "clsx"; import { Webhook as WebhookIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { TSurvey } from "@formbricks/types/surveys/types"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { SurveyCheckboxGroup } from "@/modules/integrations/webhooks/components/survey-checkbox-group"; import { TriggerCheckboxGroup } from "@/modules/integrations/webhooks/components/trigger-checkbox-group"; import { WebhookCreatedModal } from "@/modules/integrations/webhooks/components/webhook-created-modal"; import { isDiscordWebhook, validWebHookURL } from "@/modules/integrations/webhooks/lib/utils"; import { Button } from "@/modules/ui/components/button"; import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/modules/ui/components/dialog"; import { Input } from "@/modules/ui/components/input"; import { Label } from "@/modules/ui/components/label"; import { createWebhookAction, testEndpointAction } from "../actions"; import { TWebhookInput } from "../types/webhooks"; interface AddWebhookModalProps { environmentId: string; open: boolean; surveys: TSurvey[]; setOpen: (v: boolean) => void; } export const AddWebhookModal = ({ environmentId, surveys, open, setOpen }: AddWebhookModalProps) => { const router = useRouter(); const { handleSubmit, reset, register, formState: { isSubmitting }, } = useForm(); const { t } = useTranslation(); const [testEndpointInput, setTestEndpointInput] = useState(""); const [hittingEndpoint, setHittingEndpoint] = useState(false); const [endpointAccessible, setEndpointAccessible] = useState(); const [selectedTriggers, setSelectedTriggers] = useState([]); const [selectedSurveys, setSelectedSurveys] = useState([]); const [selectedAllSurveys, setSelectedAllSurveys] = useState(false); const [creatingWebhook, setCreatingWebhook] = useState(false); const [createdWebhook, setCreatedWebhook] = useState(null); const [webhookSecret, setWebhookSecret] = useState(); const handleTestEndpoint = async ( sendSuccessToast: boolean ): Promise<{ success: boolean; secret?: string }> => { try { const { valid, error } = validWebHookURL(testEndpointInput); if (!valid) { toast.error(error ?? t("common.something_went_wrong_please_try_again")); return { success: false }; } setHittingEndpoint(true); const testEndpointActionResult = await testEndpointAction({ url: testEndpointInput, secret: webhookSecret, }); if (!testEndpointActionResult?.data) { const errorMessage = getFormattedErrorMessage(testEndpointActionResult); throw new Error(errorMessage); } setHittingEndpoint(false); if (sendSuccessToast) toast.success(t("environments.integrations.webhooks.endpoint_pinged")); setEndpointAccessible(true); if (testEndpointActionResult.data.secret) { setWebhookSecret(testEndpointActionResult.data.secret); } return testEndpointActionResult.data; } catch (err) { setHittingEndpoint(false); toast.error( `${t("environments.integrations.webhooks.endpoint_pinged_error")} \n ${ err.message.length < 250 ? `${t("common.error")}: ${err.message}` : t("environments.integrations.webhooks.please_check_console") }`, { className: err.message.length < 250 ? "break-all" : "" } ); console.error(t("environments.integrations.webhooks.webhook_test_failed_due_to"), err.message); setEndpointAccessible(false); return { success: false }; } }; const handleSelectAllSurveys = () => { setSelectedAllSurveys(!selectedAllSurveys); setSelectedSurveys([]); }; const handleSelectedSurveyChange = (surveyId: string) => { setSelectedSurveys((prevSelectedSurveys: string[]) => prevSelectedSurveys.includes(surveyId) ? prevSelectedSurveys.filter((id) => id !== surveyId) : [...prevSelectedSurveys, surveyId] ); }; const handleCheckboxChange = (selectedValue: PipelineTriggers) => { setSelectedTriggers((prevValues) => prevValues.includes(selectedValue) ? prevValues.filter((value) => value !== selectedValue) : [...prevValues, selectedValue] ); }; const submitWebhook = async (data: TWebhookInput): Promise => { if (!isSubmitting) { try { setCreatingWebhook(true); if (!testEndpointInput || testEndpointInput === "") { throw new Error(t("environments.integrations.webhooks.please_enter_a_url")); } if (selectedTriggers.length === 0) { throw new Error(t("common.please_select_at_least_one_trigger")); } if (!selectedAllSurveys && selectedSurveys.length === 0) { throw new Error(t("common.please_select_at_least_one_survey")); } if (isDiscordWebhook(testEndpointInput)) { throw new Error(t("environments.integrations.webhooks.discord_webhook_not_supported")); } const testResult = await handleTestEndpoint(false); if (!testResult.success) return; const updatedData: TWebhookInput = { name: data.name, url: testEndpointInput, source: "user", triggers: selectedTriggers, surveyIds: selectedSurveys, }; const createWebhookActionResult = await createWebhookAction({ environmentId, webhookInput: updatedData, webhookSecret: testResult.secret, }); if (createWebhookActionResult?.data) { router.refresh(); setCreatedWebhook(createWebhookActionResult.data); toast.success(t("environments.integrations.webhooks.webhook_added_successfully")); } else { const errorMessage = getFormattedErrorMessage(createWebhookActionResult); toast.error(errorMessage); } } catch (e) { toast.error(e.message); } finally { setCreatingWebhook(false); } } }; const resetAndClose = () => { setOpen(false); reset(); setTestEndpointInput(""); setEndpointAccessible(undefined); setSelectedSurveys([]); setSelectedTriggers([]); setSelectedAllSurveys(false); setCreatedWebhook(null); setWebhookSecret(undefined); }; // Show success dialog with secret after webhook creation if (createdWebhook) { return ; } return ( {t("environments.integrations.webhooks.add_webhook")} {t("environments.integrations.webhooks.add_webhook_description")}
{ setTestEndpointInput(e.target.value); }} className={clsx( endpointAccessible === true ? "border-green-500 bg-green-50" : endpointAccessible === false ? "border-red-200 bg-red-50" : endpointAccessible === undefined ? "border-slate-200 bg-white" : null )} placeholder={t("environments.integrations.webhooks.webhook_url_placeholder")} />
); };