From e8a286bd4e94ed430b80af5f117e3093f25fe943 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Mon, 7 Aug 2023 16:10:34 +0530 Subject: [PATCH 01/15] feat: webhooks UI --- .../custom-webhook/AddWebhookModal.tsx | 253 +++++++++++++++++ .../custom-webhook/WebhookActivityTab.tsx | 70 +++++ .../custom-webhook/WebhookDetailModal.tsx | 47 ++++ .../custom-webhook/WebhookRowData.tsx | 74 +++++ .../custom-webhook/WebhookSettingsTab.tsx | 257 ++++++++++++++++++ .../custom-webhook/WebhookTable.tsx | 83 ++++++ .../custom-webhook/WebhookTableHeading.tsx | 13 + .../integrations/custom-webhook/page.tsx | 26 ++ .../custom-webhook/testEndpoint.tsx | 26 ++ .../[environmentId]/integrations/page.tsx | 9 +- apps/web/components/shared/GoBackButton.tsx | 2 + packages/lib/services/webhook.ts | 27 ++ packages/types/v1/webhooks.ts | 1 + 13 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTableHeading.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/page.tsx create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx new file mode 100644 index 0000000000..2f8ada2987 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx @@ -0,0 +1,253 @@ +"use client"; + +import Modal from "@/components/shared/Modal"; +import { + Button, + Checkbox, + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, + Input, + Label, +} from "@formbricks/ui"; +import { CursorArrowRaysIcon } from "@heroicons/react/24/solid"; +import clsx from "clsx"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; +import { useRouter } from "next/navigation"; +import { TSurvey } from "@formbricks/types/v1/surveys"; +import { ChevronDown } from "lucide-react"; +import { TWebhookInput } from "@formbricks/types/v1/webhooks"; +import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; +import { createWebhook } from "@formbricks/lib/services/webhook"; +import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint"; + +interface AddWebhookModalProps { + environmentId: string; + open: boolean; + surveys: TSurvey[]; + setOpen: (v: boolean) => void; +} + +export default function AddWebhookModal({ environmentId, surveys, open, setOpen }: AddWebhookModalProps) { + const router = useRouter(); + const { handleSubmit, reset } = useForm(); + + const submitWebhook = async (): Promise => { + if (testEndpointInput === undefined || testEndpointInput === "") { + toast.error("Please enter a URL"); + return; + } + + if (selectedTriggers.length === 0) { + toast.error("Please select at least one trigger"); + return; + } + + const updatedData: TWebhookInput = { + url: testEndpointInput, + triggers: selectedTriggers, + surveyIds: selectedSurveys, + }; + try { + await createWebhook(environmentId, updatedData); + router.refresh(); + reset(); + setOpen(false); + toast.success("Webhook added successfully."); + } catch (e) { + toast.error(e.message); + return; + } + }; + const renderSelectedSurveysText = () => { + if (selectedSurveys.length === 0) { + return

Select Surveys for this webhook

; + } else { + const selectedSurveyNames = selectedSurveys.map((surveyId) => { + const survey = surveys.find((survey) => survey.id === surveyId); + return survey ? survey.name : ""; + }); + return

{selectedSurveyNames.join(", ")}

; + } + }; + const [testEndpointInput, setTestEndpointInput] = useState(""); + const [endpointAccessible, setEndpointAccessible] = useState(); + const [hittingEndpoint, setHittingEndpoint] = useState(false); + + const [selectedSurveys, setSelectedSurveys] = useState([]); + const [selectedTriggers, setSelectedTriggers] = useState([]); + + const handleSelectedSurveyChange = (surveyId) => { + setSelectedSurveys((prevSelectedSurveys) => { + if (prevSelectedSurveys.includes(surveyId)) { + return prevSelectedSurveys.filter((id) => id !== surveyId); + } else { + return [...prevSelectedSurveys, surveyId]; + } + }); + }; + + const handleCheckboxChange = (selectedValue) => { + setSelectedTriggers((prevValues) => { + if (prevValues.includes(selectedValue)) { + return prevValues.filter((value) => value !== selectedValue); + } else { + return [...prevValues, selectedValue]; + } + }); + }; + + const handleTestEndpoint = async () => { + try { + setHittingEndpoint(true); + await testEndpoint(testEndpointInput); + setHittingEndpoint(false); + toast.success("Yay! We are able to ping the webhook!"); + setEndpointAccessible(true); + } catch (err) { + setHittingEndpoint(false); + toast.error("Oh no! We are unable to ping the webhook!"); + setEndpointAccessible(false); + } + }; + + return ( + +
+
+
+
+
+ +
+
+
Add Webhook
+
+ Send alerts to your own custom endpoints when an action is striggered in a survey. +
+
+
+
+
+
+
+
+
+ +
+ { + 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="Paste the URL you want the event to trigger on" + /> + +
+
+
+ +
+
+ { + handleCheckboxChange("responseCreated"); + }} + /> + +
+
+ { + handleCheckboxChange("responseUpdated"); + }} + /> + +
+
+ { + handleCheckboxChange("responseFinished"); + }} + /> + +
+
+
+ +
+ + + + +
+ {renderSelectedSurveysText()} + +
+
+ + {surveys.map((survey) => ( + e.preventDefault()} + onCheckedChange={() => handleSelectedSurveyChange(survey.id)}> + {survey.name} + + ))} + +
+
+
+
+
+
+ + +
+
+
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx new file mode 100644 index 0000000000..7f7713b830 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx @@ -0,0 +1,70 @@ +import { Label } from "@formbricks/ui"; +import { convertDateTimeStringShort } from "@formbricks/lib/time"; +import { TWebhook } from "@formbricks/types/v1/webhooks"; +import { TSurvey } from "@formbricks/types/v1/surveys"; + +interface ActivityTabProps { + webhook: TWebhook; + surveys: TSurvey[]; +} + +const convertTriggerIdToName = (triggerId: string): string => { + switch (triggerId) { + case "responseCreated": + return "Response Created"; + case "responseUpdated": + return "Response Updated"; + case "responseFinished": + return "Response Finished"; + default: + return triggerId; + } +}; + +export default function WebhookActivityTab({ webhook, surveys }: ActivityTabProps) { + return ( +
+
+
+ +

{webhook.url}

+
+ +
+ + {webhook.surveyIds.length === 0 &&

-

} + {webhook.surveyIds + .map((surveyId) => surveys.find((survey) => survey.id === surveyId)?.name) + .map((surveyName) => ( +

+ {surveyName} +

+ ))} +
+
+ + {webhook.triggers.length === 0 &&

-

} + {webhook.triggers.map((triggerId) => ( +

+ {convertTriggerIdToName(triggerId)} +

+ ))} +
+
+
+
+ +

+ {convertDateTimeStringShort(webhook.createdAt?.toString())} +

+
+
+ +

+ {convertDateTimeStringShort(webhook.updatedAt?.toString())} +

+
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx new file mode 100644 index 0000000000..995423caef --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx @@ -0,0 +1,47 @@ +import ModalWithTabs from "@/components/shared/ModalWithTabs"; +import { CodeBracketIcon } from "@heroicons/react/24/solid"; +import { TWebhook } from "@formbricks/types/v1/webhooks"; +import WebhookActivityTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab"; +import WebhookSettingsTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab"; +import { TSurvey } from "@formbricks/types/v1/surveys"; + +interface WebhookModalProps { + environmentId: string; + open: boolean; + setOpen: (v: boolean) => void; + webhook: TWebhook; + surveys: TSurvey[]; +} + +export default function WebhookModal({ environmentId, open, setOpen, webhook, surveys }: WebhookModalProps) { + const tabs = [ + { + title: "Activity", + children: , + }, + { + title: "Settings", + children: ( + + ), + }, + ]; + + return ( + <> + } + label={webhook.url} + description={""} + /> + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx new file mode 100644 index 0000000000..f3ef75279d --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx @@ -0,0 +1,74 @@ +import { timeSinceConditionally } from "@formbricks/lib/time"; +import { TSurvey } from "@formbricks/types/v1/surveys"; +import { TWebhook } from "@formbricks/types/v1/webhooks"; + +const renderSelectedSurveysText = (webhook: TWebhook, allSurveys: TSurvey[]) => { + if (webhook.surveyIds.length === 0) { + return

No Surveys

; + } else { + const selectedSurveyNames = webhook.surveyIds.map((surveyId) => { + const survey = allSurveys.find((survey) => survey.id === surveyId); + return survey ? survey.name : ""; + }); + return

{selectedSurveyNames.join(", ")}

; + } +}; + +const renderSelectedTriggersText = (webhook: TWebhook) => { + if (webhook.triggers.length === 0) { + return

No Triggers

; + } else { + let cleanedTriggers = webhook.triggers.map((trigger) => { + if (trigger === "responseCreated") { + return "Response Created"; + } else if (trigger === "responseUpdated") { + return "Response Updated"; + } else if (trigger === "responseFinished") { + return "Response Finished"; + } else { + return trigger; + } + }); + + return ( +

+ {cleanedTriggers + .sort((a, b) => { + const triggerOrder = { + "Response Created": 1, + "Response Updated": 2, + "Response Finished": 3, + }; + + return triggerOrder[a] - triggerOrder[b]; + }) + .join(", ")} +

+ ); + } +}; + +export default function WebhookRowData({ webhook, surveys }: { webhook: TWebhook; surveys: TSurvey[] }) { + return ( +
+
+
+
+
{webhook.url}
+
+
+
+
+
{renderSelectedSurveysText(webhook, surveys)}
+
+
+
{renderSelectedTriggersText(webhook)}
+
+ +
+ {timeSinceConditionally(webhook.createdAt.toString())} +
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx new file mode 100644 index 0000000000..fdc60266e4 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx @@ -0,0 +1,257 @@ +"use client"; + +import DeleteDialog from "@/components/shared/DeleteDialog"; +import { + Button, + Checkbox, + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, + Input, + Label, +} from "@formbricks/ui"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "react-hot-toast"; +import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks"; +import { deleteWebhook, updateWebhook } from "@formbricks/lib/services/webhook"; +import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; +import { ChevronDown } from "lucide-react"; +import { TSurvey } from "@formbricks/types/v1/surveys"; +import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint"; + +interface ActionSettingsTabProps { + environmentId: string; + webhook: TWebhook; + surveys: TSurvey[]; + setOpen: (v: boolean) => void; +} + +export default function WebhookSettingsTab({ + environmentId, + webhook, + surveys, + setOpen, +}: ActionSettingsTabProps) { + const router = useRouter(); + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const [isUpdatingWebhook, setIsUpdatingWebhook] = useState(false); + const [selectedTriggers, setSelectedTriggers] = useState(webhook.triggers); + const [selectedSurveys, setSelectedSurveys] = useState(webhook.surveyIds); + const [testEndpointInput, setTestEndpointInput] = useState(webhook.url); + const [endpointAccessible, setEndpointAccessible] = useState(); + const [hittingEndpoint, setHittingEndpoint] = useState(false); + + const handleTestEndpoint = async () => { + try { + setHittingEndpoint(true); + await testEndpoint(testEndpointInput); + setHittingEndpoint(false); + toast.success("Yay! We are able to ping the webhook!"); + setEndpointAccessible(true); + } catch (err) { + setHittingEndpoint(false); + toast.error("Oh no! We are unable to ping the webhook!"); + setEndpointAccessible(false); + } + }; + const renderSelectedSurveysText = () => { + if (selectedSurveys.length === 0) { + return

Select Surveys for this webhook

; + } else { + const selectedSurveyNames = selectedSurveys.map((surveyId) => { + const survey = surveys.find((survey) => survey.id === surveyId); + return survey ? survey.name : ""; + }); + return

{selectedSurveyNames.join(", ")}

; + } + }; + const { register, handleSubmit } = useForm({ + defaultValues: { + url: webhook.url, + triggers: webhook.triggers, + surveyIds: webhook.surveyIds, + }, + }); + + const onSubmit = async (data) => { + if (selectedTriggers.length === 0) { + toast.error("Please select at least one trigger"); + return; + } + const updatedData: TWebhookInput = { + url: data.url as string, + triggers: selectedTriggers, + surveyIds: selectedSurveys, + }; + setIsUpdatingWebhook(true); + await updateWebhook(environmentId, webhook.id, updatedData); + router.refresh(); + setIsUpdatingWebhook(false); + setOpen(false); + }; + + const handleSelectedSurveyChange = (surveyId) => { + setSelectedSurveys((prevSelectedSurveys) => { + if (prevSelectedSurveys.includes(surveyId)) { + return prevSelectedSurveys.filter((id) => id !== surveyId); + } else { + return [...prevSelectedSurveys, surveyId]; + } + }); + }; + + const handleCheckboxChange = (selectedValue) => { + setSelectedTriggers((prevValues) => { + if (prevValues.includes(selectedValue)) { + return prevValues.filter((value) => value !== selectedValue); + } else { + return [...prevValues, selectedValue]; + } + }); + }; + + return ( +
+
+
+ +
+ { + 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="Paste the URL you want the event to trigger on" + /> + +
+
+ +
+ +
+
+ { + handleCheckboxChange("responseCreated"); + }} + /> + +
+
+ { + handleCheckboxChange("responseUpdated"); + }} + /> + +
+
+ { + handleCheckboxChange("responseFinished"); + }} + /> + +
+
+
+ +
+ + + + +
+ {renderSelectedSurveysText()} + +
+
+ + {surveys.map((survey) => ( + handleSelectedSurveyChange(survey.id)}> + {survey.name} + + ))} + +
+
+ +
+
+ + + +
+ +
+ +
+
+
+ { + setOpen(false); + try { + await deleteWebhook(webhook.id); + router.refresh(); + toast.success("Webhook deleted successfully"); + } catch (error) { + toast.error("Something went wrong. Please try again."); + } + }} + /> +
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx new file mode 100644 index 0000000000..065ee1fa94 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { Button } from "@formbricks/ui"; +import { CursorArrowRaysIcon } from "@heroicons/react/24/solid"; +import { useState } from "react"; +import { TWebhook } from "@formbricks/types/v1/webhooks"; +import AddWebhookModal from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal"; +import { TSurvey } from "@formbricks/types/v1/surveys"; +import WebhookModal from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal"; + +export default function WebhookTable({ + environmentId, + webhooks, + surveys, + children: [TableHeading, webhookRows], +}: { + environmentId: string; + webhooks: TWebhook[]; + surveys: TSurvey[]; + children: [JSX.Element, JSX.Element[]]; +}) { + const [isWebhookDetailModalOpen, setWebhookDetailModalOpen] = useState(false); + const [isAddWebhookModalOpen, setAddWebhookModalOpen] = useState(false); + + const [activeWebhook, setActiveWebhook] = useState({ + environmentId, + id: "", + url: "", + triggers: [], + surveyIds: [], + createdAt: new Date(), + updatedAt: new Date(), + }); + + const handleOpenWebhookDetailModalClick = (e, webhook: TWebhook) => { + e.preventDefault(); + setActiveWebhook(webhook); + setWebhookDetailModalOpen(true); + }; + + return ( + <> +
+ +
+
+ {TableHeading} +
+ {webhooks.map((webhook, index) => ( + + ))} +
+
+ + + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTableHeading.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTableHeading.tsx new file mode 100644 index 0000000000..9c83d1d6fb --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTableHeading.tsx @@ -0,0 +1,13 @@ +export default function WebhookTableHeading() { + return ( + <> +
+ Edit +
URL
+
Surveys
+
Triggers
+
Updated
+
+ + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/page.tsx new file mode 100644 index 0000000000..6dfb480031 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/page.tsx @@ -0,0 +1,26 @@ +import WebhookRowData from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData"; +import WebhookTable from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable"; +import WebhookTableHeading from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTableHeading"; +import GoBackButton from "@/components/shared/GoBackButton"; +import { getSurveys } from "@formbricks/lib/services/survey"; +import { getWebhooks } from "@formbricks/lib/services/webhook"; + +export default async function CustomWebhookPage({ params }) { + const webhooks = (await getWebhooks(params.environmentId)).sort((a, b) => { + if (a.createdAt > b.createdAt) return -1; + if (a.createdAt < b.createdAt) return 1; + return 0; + }); + const surveys = await getSurveys(params.environmentId); + return ( + <> + + + + {webhooks.map((webhook) => ( + + ))} + + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint.tsx new file mode 100644 index 0000000000..a19dc9a03f --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint.tsx @@ -0,0 +1,26 @@ +"use server"; +import "server-only"; + +export const testEndpoint = async (url: string) => { + try { + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ + formbricks: "test endpoint", + }), + headers: { + "Content-Type": "application/json", + }, + }); + const statusCode = response.status; + + if (statusCode >= 200 && statusCode < 300) { + return true; + } else { + const errorMessage = await response.text(); + throw new Error(`Request failed with status code ${statusCode}: ${errorMessage}`); + } + } catch (error) { + throw new Error(`Error while fetching the URL: ${error.message}`); + } +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index a5e083b637..9e2e91e7ea 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -3,7 +3,7 @@ import Image from "next/image"; import JsLogo from "@/images/jslogo.png"; import ZapierLogo from "@/images/zapier-small.png"; -export default function IntegrationsPage() { +export default function IntegrationsPage({ params }) { return (

Integrations

@@ -22,6 +22,13 @@ export default function IntegrationsPage() { description="Integrate Formbricks with 5000+ apps via Zapier" icon={Zapier Logo} /> + } + />
); diff --git a/apps/web/components/shared/GoBackButton.tsx b/apps/web/components/shared/GoBackButton.tsx index 8869ac65ac..332683eec7 100644 --- a/apps/web/components/shared/GoBackButton.tsx +++ b/apps/web/components/shared/GoBackButton.tsx @@ -1,3 +1,5 @@ +'use client'; + import { BackIcon } from "@formbricks/ui"; import { useRouter } from "next/navigation"; diff --git a/packages/lib/services/webhook.ts b/packages/lib/services/webhook.ts index 7cc920e094..c278340191 100644 --- a/packages/lib/services/webhook.ts +++ b/packages/lib/services/webhook.ts @@ -1,3 +1,6 @@ +"use server"; +import "server-only"; + import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks"; import { prisma } from "@formbricks/database"; import { Prisma } from "@prisma/client"; @@ -52,6 +55,30 @@ export const createWebhook = async ( } }; +export const updateWebhook = async ( + environmentId: string, + webhookId: string, + webhookInput: Partial +): Promise => { + try { + const result = await prisma.webhook.update({ + where: { + id: webhookId, + }, + data: { + url: webhookInput.url, + triggers: webhookInput.triggers, + surveyIds: webhookInput.surveyIds || [], + }, + }); + return result; + } catch (error) { + throw new DatabaseError( + `Database error when updating webhook with ID ${webhookId} for environment ${environmentId}` + ); + } +}; + export const deleteWebhook = async (id: string): Promise => { try { return await prisma.webhook.delete({ diff --git a/packages/types/v1/webhooks.ts b/packages/types/v1/webhooks.ts index bead9b3628..9582d8822c 100644 --- a/packages/types/v1/webhooks.ts +++ b/packages/types/v1/webhooks.ts @@ -8,6 +8,7 @@ export const ZWebhook = z.object({ url: z.string().url(), environmentId: z.string().cuid2(), triggers: z.array(ZPipelineTrigger), + surveyIds: z.array(z.string().cuid2()), }); export type TWebhook = z.infer; From bb4052690ed39a7101516165968cc7f1931cf07a Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Mon, 7 Aug 2023 16:29:54 +0530 Subject: [PATCH 02/15] feat: link integrations page to webhook --- .../environments/[environmentId]/integrations/page.tsx | 3 +++ packages/ui/components/Card.tsx | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index 9e2e91e7ea..812c766e21 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -13,6 +13,7 @@ export default function IntegrationsPage({ params }) { docsHref="https://formbricks.com/docs/getting-started/nextjs-app" label="Javascript Widget" description="Integrate Formbricks into your Webapp" + newTab={true} icon={Javascript Logo} /> } /> } /> diff --git a/packages/ui/components/Card.tsx b/packages/ui/components/Card.tsx index ad771f5c6b..58770e88b5 100644 --- a/packages/ui/components/Card.tsx +++ b/packages/ui/components/Card.tsx @@ -6,23 +6,24 @@ interface CardProps { label: string; description: string; icon?: React.ReactNode; + newTab?: boolean; } export type { CardProps }; -export const Card: React.FC = ({ connectHref, docsHref, label, description, icon }) => ( +export const Card: React.FC = ({ connectHref, docsHref, label, description, icon, newTab }) => (
{icon &&
{icon}
}

{label}

{description}

{connectHref && ( - )} {docsHref && ( - )} From e68a7fe7630b0e2f276a9246e3ee13fa8ca92d3e Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Tue, 8 Aug 2023 09:21:03 +0530 Subject: [PATCH 03/15] fix: add webhook logo and card component flexibility --- .../[environmentId]/integrations/page.tsx | 16 ++++++++--- apps/web/images/webhook.png | Bin 0 -> 14952 bytes packages/ui/components/Card.tsx | 25 +++++++++++++----- 3 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 apps/web/images/webhook.png diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index 812c766e21..f3b4b3f6ce 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -2,6 +2,7 @@ import { Card } from "@formbricks/ui"; import Image from "next/image"; import JsLogo from "@/images/jslogo.png"; import ZapierLogo from "@/images/zapier-small.png"; +import webhook from "@/images/webhook.png"; export default function IntegrationsPage({ params }) { return ( @@ -11,26 +12,33 @@ export default function IntegrationsPage({ params }) {
} /> } /> } + icon={Javascript Logo} />
diff --git a/apps/web/images/webhook.png b/apps/web/images/webhook.png new file mode 100644 index 0000000000000000000000000000000000000000..3acc776924b6c4104496da0d10f45caa8315d365 GIT binary patch literal 14952 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 literal 0 HcmV?d00001 diff --git a/packages/ui/components/Card.tsx b/packages/ui/components/Card.tsx index 58770e88b5..84b05bb67e 100644 --- a/packages/ui/components/Card.tsx +++ b/packages/ui/components/Card.tsx @@ -1,30 +1,43 @@ import { Button } from "./Button"; interface CardProps { + connectText?: string; connectHref?: string; + connectNewTab?: boolean; + docsText?: string; docsHref?: string; + docsNewTab?: boolean; label: string; description: string; icon?: React.ReactNode; - newTab?: boolean; } export type { CardProps }; -export const Card: React.FC = ({ connectHref, docsHref, label, description, icon, newTab }) => ( +export const Card: React.FC = ({ + connectText, + connectHref, + connectNewTab, + docsText, + docsHref, + docsNewTab, + label, + description, + icon, +}) => (
{icon &&
{icon}
}

{label}

{description}

{connectHref && ( - )} {docsHref && ( - )}
From c92b2b00e05da2a51c5be47f6676292d402f7937 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Tue, 8 Aug 2023 09:38:15 +0530 Subject: [PATCH 04/15] fix: webhook table wrapping & add webhook modal text and dropdown id --- .../integrations/custom-webhook/AddWebhookModal.tsx | 7 ++++--- .../integrations/custom-webhook/WebhookRowData.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx index 2f8ada2987..098d1649dc 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx @@ -125,9 +125,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
Add Webhook
-
- Send alerts to your own custom endpoints when an action is striggered in a survey. -
+
Send survey response data to a custom endpoint
@@ -172,6 +170,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
{ @@ -182,6 +181,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
{ @@ -192,6 +192,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
{ diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx index f3ef75279d..ce7f1b968f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx @@ -58,7 +58,7 @@ export default function WebhookRowData({ webhook, surveys }: { webhook: TWebhook
-
+
{renderSelectedSurveysText(webhook, surveys)}
From 7631783e7d2c4ec14bd12c8e622e16d56fe43936 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Tue, 8 Aug 2023 09:46:08 +0530 Subject: [PATCH 05/15] fix: all webhook icons now use lucid --- .../integrations/custom-webhook/AddWebhookModal.tsx | 5 ++--- .../integrations/custom-webhook/WebhookDetailModal.tsx | 4 ++-- .../integrations/custom-webhook/WebhookTable.tsx | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx index 098d1649dc..d4c2a4997a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx @@ -11,14 +11,13 @@ import { Input, Label, } from "@formbricks/ui"; -import { CursorArrowRaysIcon } from "@heroicons/react/24/solid"; import clsx from "clsx"; import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; import { useRouter } from "next/navigation"; import { TSurvey } from "@formbricks/types/v1/surveys"; -import { ChevronDown } from "lucide-react"; +import { ChevronDown, Webhook } from "lucide-react"; import { TWebhookInput } from "@formbricks/types/v1/webhooks"; import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; import { createWebhook } from "@formbricks/lib/services/webhook"; @@ -121,7 +120,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
- +
Add Webhook
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx index 995423caef..3f4d739b71 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx @@ -1,9 +1,9 @@ import ModalWithTabs from "@/components/shared/ModalWithTabs"; -import { CodeBracketIcon } from "@heroicons/react/24/solid"; import { TWebhook } from "@formbricks/types/v1/webhooks"; import WebhookActivityTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab"; import WebhookSettingsTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab"; import { TSurvey } from "@formbricks/types/v1/surveys"; +import { Webhook } from "lucide-react"; interface WebhookModalProps { environmentId: string; @@ -38,7 +38,7 @@ export default function WebhookModal({ environmentId, open, setOpen, webhook, su open={open} setOpen={setOpen} tabs={tabs} - icon={} + icon={} label={webhook.url} description={""} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx index 065ee1fa94..f3beb18cc7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx @@ -1,12 +1,12 @@ "use client"; import { Button } from "@formbricks/ui"; -import { CursorArrowRaysIcon } from "@heroicons/react/24/solid"; import { useState } from "react"; import { TWebhook } from "@formbricks/types/v1/webhooks"; import AddWebhookModal from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal"; import { TSurvey } from "@formbricks/types/v1/surveys"; import WebhookModal from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal"; +import { Webhook } from "lucide-react"; export default function WebhookTable({ environmentId, @@ -46,7 +46,7 @@ export default function WebhookTable({ onClick={() => { setAddWebhookModalOpen(true); }}> - + Add Webhook
From d4a4b4ec41f90b86edf65c53a254bd8662e89e94 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Tue, 8 Aug 2023 15:55:48 +0530 Subject: [PATCH 06/15] fix: dropdown replaced with checkbox list and loader is implemented --- .../custom-webhook/AddWebhookModal.tsx | 169 +++++++++--------- .../custom-webhook/WebhookDetailModal.tsx | 6 +- ...ActivityTab.tsx => WebhookOverviewTab.tsx} | 28 +-- .../custom-webhook/WebhookRowData.tsx | 3 +- .../custom-webhook/WebhookSettingsTab.tsx | 143 +++++++-------- .../custom-webhook/WebhookTable.tsx | 60 +++++-- .../integrations/custom-webhook/loading.tsx | 58 ++++++ 7 files changed, 284 insertions(+), 183 deletions(-) rename apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/{WebhookActivityTab.tsx => WebhookOverviewTab.tsx} (74%) create mode 100644 apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/loading.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx index d4c2a4997a..d8b1c129de 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx @@ -1,23 +1,14 @@ "use client"; import Modal from "@/components/shared/Modal"; -import { - Button, - Checkbox, - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, - Input, - Label, -} from "@formbricks/ui"; +import { Button, Checkbox, Input, Label } from "@formbricks/ui"; import clsx from "clsx"; import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; import { useRouter } from "next/navigation"; import { TSurvey } from "@formbricks/types/v1/surveys"; -import { ChevronDown, Webhook } from "lucide-react"; +import { Webhook } from "lucide-react"; import { TWebhookInput } from "@formbricks/types/v1/webhooks"; import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; import { createWebhook } from "@formbricks/lib/services/webhook"; @@ -30,16 +21,27 @@ interface AddWebhookModalProps { setOpen: (v: boolean) => void; } +const triggers = [ + { title: "Response Created", value: "responseCreated" as TPipelineTrigger }, + { title: "Response Updated", value: "responseUpdated" as TPipelineTrigger }, + { title: "Response Finished", value: "responseFinished" as TPipelineTrigger }, +]; + export default function AddWebhookModal({ environmentId, surveys, open, setOpen }: AddWebhookModalProps) { const router = useRouter(); const { handleSubmit, reset } = useForm(); + 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 submitWebhook = async (): Promise => { if (testEndpointInput === undefined || testEndpointInput === "") { toast.error("Please enter a URL"); return; } - if (selectedTriggers.length === 0) { toast.error("Please select at least one trigger"); return; @@ -53,6 +55,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen try { await createWebhook(environmentId, updatedData); router.refresh(); + resetStates(); reset(); setOpen(false); toast.success("Webhook added successfully."); @@ -61,23 +64,19 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen return; } }; - const renderSelectedSurveysText = () => { - if (selectedSurveys.length === 0) { - return

Select Surveys for this webhook

; - } else { - const selectedSurveyNames = selectedSurveys.map((surveyId) => { - const survey = surveys.find((survey) => survey.id === surveyId); - return survey ? survey.name : ""; - }); - return

{selectedSurveyNames.join(", ")}

; - } - }; - const [testEndpointInput, setTestEndpointInput] = useState(""); - const [endpointAccessible, setEndpointAccessible] = useState(); - const [hittingEndpoint, setHittingEndpoint] = useState(false); - const [selectedSurveys, setSelectedSurveys] = useState([]); - const [selectedTriggers, setSelectedTriggers] = useState([]); + const resetStates = () => { + setTestEndpointInput(""); + setEndpointAccessible(undefined); + setSelectedSurveys([]); + setSelectedTriggers([]); + setSelectedAllSurveys(false); + }; + + const handleSelectAllSurveys = () => { + setSelectedAllSurveys(!selectedAllSurveys); + setSelectedSurveys([]); + }; const handleSelectedSurveyChange = (surveyId) => { setSelectedSurveys((prevSelectedSurveys) => { @@ -133,7 +132,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
- +
+
- -
-
- { - handleCheckboxChange("responseCreated"); - }} - /> - -
-
- { - handleCheckboxChange("responseUpdated"); - }} - /> - -
-
- { - handleCheckboxChange("responseFinished"); - }} - /> - + +
+
+ {triggers.map((survey) => ( +
+ +
+ ))}
- - - - -
- {renderSelectedSurveysText()} - + +
+
+
+ handleSelectAllSurveys()} + /> +
- - {surveys.map((survey) => ( - e.preventDefault()} - onCheckedChange={() => handleSelectedSurveyChange(survey.id)}> - {survey.name} - +
+ handleSelectedSurveyChange(survey.id)} + /> + +
))} -
- +
+
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx index 3f4d739b71..7dbe4f5602 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal.tsx @@ -1,6 +1,6 @@ import ModalWithTabs from "@/components/shared/ModalWithTabs"; import { TWebhook } from "@formbricks/types/v1/webhooks"; -import WebhookActivityTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab"; +import WebhookOverviewTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookOverviewTab"; import WebhookSettingsTab from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab"; import { TSurvey } from "@formbricks/types/v1/surveys"; import { Webhook } from "lucide-react"; @@ -16,8 +16,8 @@ interface WebhookModalProps { export default function WebhookModal({ environmentId, open, setOpen, webhook, surveys }: WebhookModalProps) { const tabs = [ { - title: "Activity", - children: , + title: "Overview", + children: , }, { title: "Settings", diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookOverviewTab.tsx similarity index 74% rename from apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx rename to apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookOverviewTab.tsx index 7f7713b830..c9b269e255 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookActivityTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookOverviewTab.tsx @@ -8,6 +8,17 @@ interface ActivityTabProps { surveys: TSurvey[]; } +const getSurveyNamesForWebhook = (webhook: TWebhook, allSurveys: TSurvey[]): string[] => { + if (webhook.surveyIds.length === 0) { + return allSurveys.map((survey) => survey.name); + } else { + return webhook.surveyIds.map((surveyId) => { + const survey = allSurveys.find((survey) => survey.id === surveyId); + return survey ? survey.name : ""; + }); + } +}; + const convertTriggerIdToName = (triggerId: string): string => { switch (triggerId) { case "responseCreated": @@ -21,7 +32,7 @@ const convertTriggerIdToName = (triggerId: string): string => { } }; -export default function WebhookActivityTab({ webhook, surveys }: ActivityTabProps) { +export default function WebhookOverviewTab({ webhook, surveys }: ActivityTabProps) { return (
@@ -32,18 +43,15 @@ export default function WebhookActivityTab({ webhook, surveys }: ActivityTabProp
- {webhook.surveyIds.length === 0 &&

-

} - {webhook.surveyIds - .map((surveyId) => surveys.find((survey) => survey.id === surveyId)?.name) - .map((surveyName) => ( -

- {surveyName} -

- ))} + + {getSurveyNamesForWebhook(webhook, surveys).map((surveyName, index) => ( +

+ {surveyName} +

+ ))}
- {webhook.triggers.length === 0 &&

-

} {webhook.triggers.map((triggerId) => (

{convertTriggerIdToName(triggerId)} diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx index ce7f1b968f..8e5a696039 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookRowData.tsx @@ -4,7 +4,8 @@ import { TWebhook } from "@formbricks/types/v1/webhooks"; const renderSelectedSurveysText = (webhook: TWebhook, allSurveys: TSurvey[]) => { if (webhook.surveyIds.length === 0) { - return

No Surveys

; + const allSurveyNames = allSurveys.map((survey) => survey.name); + return

{allSurveyNames.join(", ")}

; } else { const selectedSurveyNames = webhook.surveyIds.map((surveyId) => { const survey = allSurveys.find((survey) => survey.id === surveyId); diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx index fdc60266e4..9bbdb89b17 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookSettingsTab.tsx @@ -1,16 +1,7 @@ "use client"; import DeleteDialog from "@/components/shared/DeleteDialog"; -import { - Button, - Checkbox, - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, - Input, - Label, -} from "@formbricks/ui"; +import { Button, Checkbox, Input, Label } from "@formbricks/ui"; import { TrashIcon } from "@heroicons/react/24/outline"; import clsx from "clsx"; import { useRouter } from "next/navigation"; @@ -20,7 +11,6 @@ import { toast } from "react-hot-toast"; import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks"; import { deleteWebhook, updateWebhook } from "@formbricks/lib/services/webhook"; import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; -import { ChevronDown } from "lucide-react"; import { TSurvey } from "@formbricks/types/v1/surveys"; import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint"; @@ -31,6 +21,12 @@ interface ActionSettingsTabProps { setOpen: (v: boolean) => void; } +const triggers = [ + { title: "Response Created", value: "responseCreated" as TPipelineTrigger }, + { title: "Response Updated", value: "responseUpdated" as TPipelineTrigger }, + { title: "Response Finished", value: "responseFinished" as TPipelineTrigger }, +]; + export default function WebhookSettingsTab({ environmentId, webhook, @@ -45,6 +41,7 @@ export default function WebhookSettingsTab({ const [testEndpointInput, setTestEndpointInput] = useState(webhook.url); const [endpointAccessible, setEndpointAccessible] = useState(); const [hittingEndpoint, setHittingEndpoint] = useState(false); + const [selectedAllSurveys, setSelectedAllSurveys] = useState(webhook.surveyIds.length === 0); const handleTestEndpoint = async () => { try { @@ -59,17 +56,12 @@ export default function WebhookSettingsTab({ setEndpointAccessible(false); } }; - const renderSelectedSurveysText = () => { - if (selectedSurveys.length === 0) { - return

Select Surveys for this webhook

; - } else { - const selectedSurveyNames = selectedSurveys.map((surveyId) => { - const survey = surveys.find((survey) => survey.id === surveyId); - return survey ? survey.name : ""; - }); - return

{selectedSurveyNames.join(", ")}

; - } + + const handleSelectAllSurveys = () => { + setSelectedAllSurveys(!selectedAllSurveys); + setSelectedSurveys([]); }; + const { register, handleSubmit } = useForm({ defaultValues: { url: webhook.url, @@ -119,7 +111,7 @@ export default function WebhookSettingsTab({
- +
- -
-
- { - handleCheckboxChange("responseCreated"); - }} - /> - -
-
- { - handleCheckboxChange("responseUpdated"); - }} - /> - -
-
- { - handleCheckboxChange("responseFinished"); - }} - /> - + +
+
+ {triggers.map((survey) => ( +
+ +
+ ))}
- - - - -
- {renderSelectedSurveysText()} - + +
+
+
+ handleSelectAllSurveys()} + /> +
- - {surveys.map((survey) => ( - handleSelectedSurveyChange(survey.id)}> - {survey.name} - +
+ handleSelectedSurveyChange(survey.id)} + /> + +
))} -
- +
+
@@ -224,7 +222,10 @@ export default function WebhookSettingsTab({ Delete -
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx index f3beb18cc7..85dcb7cc08 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookTable.tsx @@ -7,6 +7,7 @@ import AddWebhookModal from "@/app/(app)/environments/[environmentId]/integratio import { TSurvey } from "@formbricks/types/v1/surveys"; import WebhookModal from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/WebhookDetailModal"; import { Webhook } from "lucide-react"; +import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller"; export default function WebhookTable({ environmentId, @@ -50,21 +51,52 @@ export default function WebhookTable({ Add Webhook
-
- {TableHeading} -
- {webhooks.map((webhook, index) => ( - - ))} + + {webhooks.length === 0 ? ( + + ) : ( +
+ {TableHeading} +
+
+
+
+
+
webhook.url
+
+
+
+
+
surveysbaazi
+
+
+
trigeysbaazi
+
+ +
+ kab bana +
+
+
+ {webhooks.map((webhook, index) => ( + + ))} +
-
+ )} + + +
+ +
+ +
+
+ Edit +
URL
+
Surveys
+
Triggers
+
Updated
+
+
+ {[...Array(3)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ + ); +} From 758fc9af4dbb6ce4a289944971ccd125a19e7d9a Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 8 Aug 2023 13:29:34 +0200 Subject: [PATCH 07/15] tweaks --- .../custom-webhook/AddWebhookModal.tsx | 26 +++++++++++-------- .../[environmentId]/integrations/page.tsx | 8 +++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx index d8b1c129de..bdcee750e7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/custom-webhook/AddWebhookModal.tsx @@ -1,18 +1,18 @@ "use client"; +import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint"; import Modal from "@/components/shared/Modal"; +import { createWebhook } from "@formbricks/lib/services/webhook"; +import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; +import { TSurvey } from "@formbricks/types/v1/surveys"; +import { TWebhookInput } from "@formbricks/types/v1/webhooks"; import { Button, Checkbox, Input, Label } from "@formbricks/ui"; import clsx from "clsx"; +import { Webhook } 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 { useRouter } from "next/navigation"; -import { TSurvey } from "@formbricks/types/v1/surveys"; -import { Webhook } from "lucide-react"; -import { TWebhookInput } from "@formbricks/types/v1/webhooks"; -import { TPipelineTrigger } from "@formbricks/types/v1/pipelines"; -import { createWebhook } from "@formbricks/lib/services/webhook"; -import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint"; interface AddWebhookModalProps { environmentId: string; @@ -135,7 +135,8 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
{ setTestEndpointInput(e.target.value); @@ -166,8 +167,8 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
-
-
+
+
{triggers.map((survey) => (