fix: make components out of survey and trigger checklists and minor ux factoring

This commit is contained in:
ShubhamPalriwala
2023-08-08 21:21:15 +05:30
parent dcc198b151
commit 15050525fd
5 changed files with 186 additions and 179 deletions
@@ -1,10 +1,13 @@
import SurveyCheckboxGroup from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/SurveyCheckboxGroup";
import TriggerCheckboxGroup from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/TriggerCheckboxGroup";
import { triggers } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/HardcodedTriggers";
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 { Button, Input, Label } from "@formbricks/ui";
import clsx from "clsx";
import { Webhook } from "lucide-react";
import { useRouter } from "next/navigation";
@@ -19,12 +22,6 @@ 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 {
@@ -42,12 +39,12 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
const [selectedAllSurveys, setSelectedAllSurveys] = useState(false);
const [creatingWebhook, setCreatingWebhook] = useState(false);
const handleTestEndpoint = async () => {
const handleTestEndpoint = async (sendSuccessToast: boolean) => {
try {
setHittingEndpoint(true);
await testEndpoint(testEndpointInput);
setHittingEndpoint(false);
toast.success("Yay! We are able to ping the webhook!");
if (sendSuccessToast) toast.success("Yay! We are able to ping the webhook!");
setEndpointAccessible(true);
return true;
} catch (err) {
@@ -64,7 +61,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
};
const handleSelectedSurveyChange = (surveyId: string) => {
setSelectedSurveys((prevSelectedSurveys) =>
setSelectedSurveys((prevSelectedSurveys: string[]) =>
prevSelectedSurveys.includes(surveyId)
? prevSelectedSurveys.filter((id) => id !== surveyId)
: [...prevSelectedSurveys, surveyId]
@@ -94,7 +91,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
throw new Error("Please select at least one survey");
}
const endpointHitSuccessfully = await handleTestEndpoint();
const endpointHitSuccessfully = await handleTestEndpoint(false);
if (!endpointHitSuccessfully) return;
const updatedData: TWebhookInput = {
@@ -106,7 +103,6 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
await createWebhook(environmentId, updatedData);
router.refresh();
reset();
setOpenWithStates(false);
toast.success("Webhook added successfully.");
} catch (e) {
@@ -185,7 +181,7 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
loading={hittingEndpoint}
className="ml-2 whitespace-nowrap"
onClick={() => {
handleTestEndpoint();
handleTestEndpoint(true);
}}>
Test Endpoint
</Button>
@@ -194,72 +190,22 @@ export default function AddWebhookModal({ environmentId, surveys, open, setOpen
<div>
<Label htmlFor="Triggers">Triggers</Label>
<div className="border-slate-20 mt-1 rounded-lg border">
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
{triggers.map((trigger) => (
<div key={trigger.value} className="my-1 flex items-center space-x-2">
<label htmlFor={trigger.value} className="flex cursor-pointer items-center">
<Checkbox
type="button"
id={trigger.value}
value={trigger.value}
className="bg-white"
checked={selectedTriggers.includes(trigger.value)}
onCheckedChange={() => {
handleCheckboxChange(trigger.value);
}}
/>
<span className="ml-2">{trigger.title}</span>
</label>
</div>
))}
</div>
</div>
<TriggerCheckboxGroup
triggers={triggers}
selectedTriggers={selectedTriggers}
onCheckboxChange={handleCheckboxChange}
/>
</div>
<div>
<Label htmlFor="Surveys">Surveys</Label>
<div className="mt-1 rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
<div className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id="allSurveys"
value=""
checked={selectedAllSurveys}
className="bg-white"
onCheckedChange={handleSelectAllSurveys}
/>
<label
htmlFor="allSurveys"
className={`flex cursor-pointer items-center ${
selectedAllSurveys ? "font-semibold" : ""
}`}>
All current and new surveys
</label>
</div>
{surveys.map((survey) => (
<div key={survey.id} className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id={survey.id}
value={survey.id}
className="bg-white"
checked={selectedSurveys.includes(survey.id) && !selectedAllSurveys}
disabled={selectedAllSurveys}
onCheckedChange={() => handleSelectedSurveyChange(survey.id)}
/>
<label
htmlFor={survey.id}
className={`flex cursor-pointer items-center ${
selectedAllSurveys ? "cursor-not-allowed opacity-50" : ""
}`}>
{survey.name}
</label>
</div>
))}
</div>
</div>
<SurveyCheckboxGroup
surveys={surveys}
selectedSurveys={selectedSurveys}
selectedAllSurveys={selectedAllSurveys}
onSelectAllSurveys={handleSelectAllSurveys}
onSelectedSurveyChange={handleSelectedSurveyChange}
/>
</div>
</div>
</div>
@@ -0,0 +1,7 @@
import { TPipelineTrigger } from "@formbricks/types/v1/pipelines";
export const triggers = [
{ title: "Response Created", value: "responseCreated" as TPipelineTrigger },
{ title: "Response Updated", value: "responseUpdated" as TPipelineTrigger },
{ title: "Response Finished", value: "responseFinished" as TPipelineTrigger },
];
@@ -0,0 +1,61 @@
import React from "react";
import { Checkbox } from "@formbricks/ui";
import { TSurvey } from "@formbricks/types/v1/surveys";
interface SurveyCheckboxGroupProps {
surveys: TSurvey[];
selectedSurveys: string[];
selectedAllSurveys: boolean;
onSelectAllSurveys: () => void;
onSelectedSurveyChange: (surveyId: string) => void;
}
export const SurveyCheckboxGroup: React.FC<SurveyCheckboxGroupProps> = ({
surveys,
selectedSurveys,
selectedAllSurveys,
onSelectAllSurveys,
onSelectedSurveyChange,
}) => {
return (
<div className="mt-1 rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-100 p-3 text-left text-sm text-slate-900">
<div className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id="allSurveys"
value=""
checked={selectedAllSurveys}
onCheckedChange={onSelectAllSurveys}
/>
<label
htmlFor="allSurveys"
className={`flex cursor-pointer items-center ${selectedAllSurveys ? "font-semibold" : ""}`}>
All current and new surveys
</label>
</div>
{surveys.map((survey) => (
<div key={survey.id} className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id={survey.id}
value={survey.id}
checked={selectedSurveys.includes(survey.id) && !selectedAllSurveys}
disabled={selectedAllSurveys}
onCheckedChange={() => onSelectedSurveyChange(survey.id)}
/>
<label
htmlFor={survey.id}
className={`flex cursor-pointer items-center ${
selectedAllSurveys ? "cursor-not-allowed opacity-50" : ""
}`}>
{survey.name}
</label>
</div>
))}
</div>
</div>
);
};
export default SurveyCheckboxGroup;
@@ -0,0 +1,40 @@
import React from "react";
import { Checkbox } from "@formbricks/ui";
import { TPipelineTrigger } from "@formbricks/types/v1/pipelines";
interface TriggerCheckboxGroupProps {
triggers: { title: string; value: TPipelineTrigger }[];
selectedTriggers: TPipelineTrigger[];
onCheckboxChange: (selectedValue: TPipelineTrigger) => void;
}
export const TriggerCheckboxGroup: React.FC<TriggerCheckboxGroupProps> = ({
triggers,
selectedTriggers,
onCheckboxChange,
}) => {
return (
<div className="mt-1 rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-100 p-3 text-left text-sm text-slate-900">
{triggers.map((trigger) => (
<div key={trigger.value} className="my-1 flex items-center space-x-2">
<label htmlFor={trigger.value} className="flex cursor-pointer items-center">
<Checkbox
type="button"
id={trigger.value}
value={trigger.value}
checked={selectedTriggers.includes(trigger.value)}
onCheckedChange={() => {
onCheckboxChange(trigger.value);
}}
/>
<span className="ml-2">{trigger.title}</span>
</label>
</div>
))}
</div>
</div>
);
};
export default TriggerCheckboxGroup;
@@ -1,7 +1,7 @@
"use client";
import DeleteDialog from "@/components/shared/DeleteDialog";
import { Button, Checkbox, Input, Label } from "@formbricks/ui";
import { Button, Input, Label } from "@formbricks/ui";
import { TrashIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useRouter } from "next/navigation";
@@ -13,6 +13,9 @@ import { deleteWebhook, updateWebhook } from "@formbricks/lib/services/webhook";
import { TPipelineTrigger } from "@formbricks/types/v1/pipelines";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { testEndpoint } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/testEndpoint";
import { triggers } from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/HardcodedTriggers";
import TriggerCheckboxGroup from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/TriggerCheckboxGroup";
import SurveyCheckboxGroup from "@/app/(app)/environments/[environmentId]/integrations/custom-webhook/SurveyCheckboxGroup";
interface ActionSettingsTabProps {
environmentId: string;
@@ -21,12 +24,6 @@ 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,
@@ -34,6 +31,15 @@ export default function WebhookSettingsTab({
setOpen,
}: ActionSettingsTabProps) {
const router = useRouter();
const { register, handleSubmit } = useForm({
defaultValues: {
name: webhook.name,
url: webhook.url,
triggers: webhook.triggers,
surveyIds: webhook.surveyIds,
},
});
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
const [isUpdatingWebhook, setIsUpdatingWebhook] = useState(false);
const [selectedTriggers, setSelectedTriggers] = useState<TPipelineTrigger[]>(webhook.triggers);
@@ -43,12 +49,12 @@ export default function WebhookSettingsTab({
const [hittingEndpoint, setHittingEndpoint] = useState<boolean>(false);
const [selectedAllSurveys, setSelectedAllSurveys] = useState(webhook.surveyIds.length === 0);
const handleTestEndpoint = async () => {
const handleTestEndpoint = async (sendSuccessToast: boolean) => {
try {
setHittingEndpoint(true);
await testEndpoint(testEndpointInput);
setHittingEndpoint(false);
toast.success("Yay! We are able to ping the webhook!");
if (sendSuccessToast) toast.success("Yay! We are able to ping the webhook!");
setEndpointAccessible(true);
return true;
} catch (err) {
@@ -64,43 +70,6 @@ export default function WebhookSettingsTab({
setSelectedSurveys([]);
};
const { register, handleSubmit } = useForm({
defaultValues: {
name: webhook.name,
url: webhook.url,
triggers: webhook.triggers,
surveyIds: webhook.surveyIds,
},
});
const onSubmit = async (data) => {
const endpointHitSuccessfully = await handleTestEndpoint();
if (!endpointHitSuccessfully) {
return;
}
if (selectedTriggers.length === 0) {
toast.error("Please select at least one trigger");
return;
}
if (!selectedAllSurveys && selectedSurveys.length === 0) {
toast.error("Please select at least one survey");
return;
}
const updatedData: TWebhookInput = {
name: data.name,
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)) {
@@ -121,6 +90,35 @@ export default function WebhookSettingsTab({
});
};
const onSubmit = async (data) => {
if (selectedTriggers.length === 0) {
toast.error("Please select at least one trigger");
return;
}
if (!selectedAllSurveys && selectedSurveys.length === 0) {
toast.error("Please select at least one survey");
return;
}
const endpointHitSuccessfully = await handleTestEndpoint(false);
if (!endpointHitSuccessfully) {
return;
}
const updatedData: TWebhookInput = {
name: data.name,
url: data.url as string,
triggers: selectedTriggers,
surveyIds: selectedSurveys,
};
setIsUpdatingWebhook(true);
await updateWebhook(environmentId, webhook.id, updatedData);
toast.success("Webhook updated successfully.");
router.refresh();
setIsUpdatingWebhook(false);
setOpen(false);
};
return (
<div>
<form className="space-y-4" onSubmit={handleSubmit(onSubmit)}>
@@ -166,7 +164,7 @@ export default function WebhookSettingsTab({
loading={hittingEndpoint}
className="ml-2 whitespace-nowrap"
onClick={() => {
handleTestEndpoint();
handleTestEndpoint(true);
}}>
Test Endpoint
</Button>
@@ -175,67 +173,22 @@ export default function WebhookSettingsTab({
<div>
<Label htmlFor="Triggers">Triggers</Label>
<div className="mt-1 rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-100 p-3 text-left text-sm text-slate-900">
{triggers.map((survey) => (
<div key={survey.value} className="my-1 flex items-center space-x-2">
<label htmlFor={survey.value} className="flex cursor-pointer items-center">
<Checkbox
type="button"
id={survey.value}
value={survey.value}
checked={selectedTriggers.includes(survey.value)}
onCheckedChange={() => {
handleCheckboxChange(survey.value);
}}
/>
<span className="ml-2">{survey.title}</span>
</label>
</div>
))}
</div>
</div>
<TriggerCheckboxGroup
triggers={triggers}
selectedTriggers={selectedTriggers}
onCheckboxChange={handleCheckboxChange}
/>
</div>
<div>
<Label htmlFor="Surveys">Surveys</Label>
<div className="mt-1 rounded-lg border border-slate-200">
<div className="grid content-center rounded-lg bg-slate-100 p-3 text-left text-sm text-slate-900">
<div className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id="allSurveys"
value=""
checked={selectedAllSurveys}
onCheckedChange={() => handleSelectAllSurveys()}
/>
<label
htmlFor="allSurveys"
className={`flex cursor-pointer items-center ${selectedAllSurveys ? "font-semibold" : ""}`}>
All current and new surveys
</label>
</div>
{surveys.map((survey) => (
<div key={survey.id} className="my-1 flex items-center space-x-2">
<Checkbox
type="button"
id={survey.id}
value={survey.id}
checked={selectedSurveys.includes(survey.id) && !selectedAllSurveys}
disabled={selectedAllSurveys}
onCheckedChange={() => handleSelectedSurveyChange(survey.id)}
/>
<label
htmlFor={survey.id}
className={`flex cursor-pointer items-center ${
selectedAllSurveys ? "cursor-not-allowed opacity-50" : ""
}`}>
{survey.name}
</label>
</div>
))}
</div>
</div>
<SurveyCheckboxGroup
surveys={surveys}
selectedSurveys={selectedSurveys}
selectedAllSurveys={selectedAllSurveys}
onSelectAllSurveys={handleSelectAllSurveys}
onSelectedSurveyChange={handleSelectedSurveyChange}
/>
</div>
<div className="flex justify-between border-t border-slate-200 py-6">