mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-03 03:14:34 -05:00
add functionality to make events editable after creating them (#245)
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||||
import { Button } from "@formbricks/ui";
|
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||||
import { ErrorComponent } from "@formbricks/ui";
|
import { ErrorComponent } from "@formbricks/ui";
|
||||||
import { Input } from "@formbricks/ui";
|
import { Input } from "@formbricks/ui";
|
||||||
import { Label } from "@formbricks/ui";
|
import { Label } from "@formbricks/ui";
|
||||||
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
||||||
import { useEventClass, useEventClasses } from "@/lib/eventClasses/eventClasses";
|
import { useEventClass, useEventClasses } from "@/lib/eventClasses/eventClasses";
|
||||||
import { useEventClassMutation } from "@/lib/eventClasses/mutateEventClasses";
|
import { useEventClassMutation } from "@/lib/eventClasses/mutateEventClasses";
|
||||||
import { useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import type { NoCodeConfig, Event } from "@formbricks/types/events";
|
||||||
|
import { testURLmatch } from "./testURLmatch";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
interface EventSettingsTabProps {
|
interface EventSettingsTabProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
@@ -17,8 +22,12 @@ interface EventSettingsTabProps {
|
|||||||
export default function EventSettingsTab({ environmentId, eventClassId, setOpen }: EventSettingsTabProps) {
|
export default function EventSettingsTab({ environmentId, eventClassId, setOpen }: EventSettingsTabProps) {
|
||||||
const { eventClass, isLoadingEventClass, isErrorEventClass } = useEventClass(environmentId, eventClassId);
|
const { eventClass, isLoadingEventClass, isErrorEventClass } = useEventClass(environmentId, eventClassId);
|
||||||
|
|
||||||
const { register, handleSubmit } = useForm({
|
const { register, handleSubmit, control, watch } = useForm({
|
||||||
defaultValues: { name: eventClass.name, description: eventClass.description },
|
defaultValues: {
|
||||||
|
name: eventClass.name,
|
||||||
|
description: eventClass.description,
|
||||||
|
noCodeConfig: eventClass.noCodeConfig,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const { triggerEventClassMutate, isMutatingEventClass } = useEventClassMutation(
|
const { triggerEventClassMutate, isMutatingEventClass } = useEventClassMutation(
|
||||||
environmentId,
|
environmentId,
|
||||||
@@ -28,11 +37,41 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
|
|||||||
const { mutateEventClasses } = useEventClasses(environmentId);
|
const { mutateEventClasses } = useEventClasses(environmentId);
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
await triggerEventClassMutate(data);
|
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig);
|
||||||
|
|
||||||
|
const updatedData: Event = {
|
||||||
|
...data,
|
||||||
|
noCodeConfig: filteredNoCodeConfig,
|
||||||
|
type: "noCode",
|
||||||
|
} as Event;
|
||||||
|
|
||||||
|
await triggerEventClassMutate(updatedData);
|
||||||
mutateEventClasses();
|
mutateEventClasses();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterNoCodeConfig = (noCodeConfig: NoCodeConfig): NoCodeConfig => {
|
||||||
|
const { type } = noCodeConfig;
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
[type]: noCodeConfig[type],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const [testUrl, setTestUrl] = useState("");
|
||||||
|
const [isMatch, setIsMatch] = useState("");
|
||||||
|
|
||||||
|
const handleMatchClick = () => {
|
||||||
|
const match = testURLmatch(
|
||||||
|
testUrl,
|
||||||
|
watch("noCodeConfig.[pageUrl].value"),
|
||||||
|
watch("noCodeConfig.[pageUrl].rule")
|
||||||
|
);
|
||||||
|
setIsMatch(match);
|
||||||
|
if (match === "yes") toast.success("Your survey would be shown on this URL.");
|
||||||
|
if (match === "no") toast.error("Your survey would not be shown.");
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoadingEventClass) return <LoadingSpinner />;
|
if (isLoadingEventClass) return <LoadingSpinner />;
|
||||||
if (isErrorEventClass) return <ErrorComponent />;
|
if (isErrorEventClass) return <ErrorComponent />;
|
||||||
|
|
||||||
@@ -61,37 +100,152 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-6">
|
<div className="">
|
||||||
<Label>Action Type</Label>
|
<Label>Action Type</Label>
|
||||||
{eventClass.type === "code" ? (
|
{eventClass.type === "code" ? (
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-slate-600">
|
||||||
This is a code action. Please make changes in your code base.
|
This is a code action. Please make changes in your code base.
|
||||||
</p>
|
</p>
|
||||||
) : eventClass.type === "noCode" ? (
|
) : eventClass.type === "noCode" ? (
|
||||||
<div>
|
<div className="flex justify-between rounded-lg">
|
||||||
<Label className="mb-3 mt-1 block font-normal text-slate-500">
|
<div className="w-full space-y-4">
|
||||||
You cannot change the action type. Please add a new action instead.
|
<Controller
|
||||||
</Label>
|
name="noCodeConfig.type"
|
||||||
<RadioGroup defaultValue={eventClass.noCodeConfig.type} className="flex">
|
defaultValue={"pageUrl"}
|
||||||
<div className="flex items-center space-x-2 rounded-lg bg-slate-50 p-3">
|
control={control}
|
||||||
<RadioGroupItem disabled value="pageUrl" id="pageUrl" className="bg-slate-50" />
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<Label htmlFor="pageUrl" className="flex items-center text-slate-400">
|
<RadioGroup className="flex" onValueChange={onChange} onBlur={onBlur} value={value}>
|
||||||
Page URL
|
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3">
|
||||||
</Label>
|
<RadioGroupItem value="pageUrl" id="pageUrl" className="bg-slate-50" />
|
||||||
</div>
|
<Label htmlFor="pageUrl" className="flex cursor-pointer items-center">
|
||||||
<div className="flex items-center space-x-2 rounded-lg bg-slate-50 p-3">
|
Page URL
|
||||||
<RadioGroupItem disabled value="innerHtml" id="innerHtml" className="bg-slate-50" />
|
</Label>
|
||||||
<Label htmlFor="innerHtml" className="flex items-center text-slate-400">
|
</div>
|
||||||
Inner Text
|
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3">
|
||||||
</Label>
|
<RadioGroupItem value="innerHtml" id="innerHtml" className="bg-slate-50" />
|
||||||
</div>
|
<Label htmlFor="innerHtml" className="flex cursor-pointer items-center">
|
||||||
<div className="flex items-center space-x-2 rounded-lg bg-slate-50 p-3">
|
Inner Text
|
||||||
<RadioGroupItem disabled value="cssSelector" id="cssSelector" className="bg-slate-50" />
|
</Label>
|
||||||
<Label htmlFor="cssSelector" className="flex items-center text-slate-400">
|
</div>
|
||||||
CSS Selector
|
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3">
|
||||||
</Label>
|
<RadioGroupItem value="cssSelector" id="cssSelector" className="bg-slate-50" />
|
||||||
</div>
|
<Label htmlFor="cssSelector" className="flex cursor-pointer items-center">
|
||||||
</RadioGroup>
|
CSS Selector
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{(watch("noCodeConfig.type") === "pageUrl" || !watch("noCodeConfig.type")) && (
|
||||||
|
<>
|
||||||
|
<div className="grid w-full grid-cols-3 gap-x-8">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<Label>URL</Label>
|
||||||
|
<Controller
|
||||||
|
name="noCodeConfig.pageUrl.rule"
|
||||||
|
defaultValue={"exactMatch"}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
/* onValueChange={(e) => {
|
||||||
|
setMatchType(e as MatchType);
|
||||||
|
setIsMatch("default");
|
||||||
|
}} */
|
||||||
|
{...field}>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<SelectValue placeholder="Select match type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="exactMatch">Exactly matches</SelectItem>
|
||||||
|
<SelectItem value="contains">Contains</SelectItem>
|
||||||
|
<SelectItem value="startsWith">Starts with</SelectItem>
|
||||||
|
<SelectItem value="endsWith">Ends with</SelectItem>
|
||||||
|
<SelectItem value="notMatch">Does not exactly match</SelectItem>
|
||||||
|
<SelectItem value="notContains">Does not contain</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-2 flex w-full items-end">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. https://app.formbricks.com/dashboard"
|
||||||
|
{...register("noCodeConfig.[pageUrl].value", { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label className="mb-2 block">Test Your URL</Label>
|
||||||
|
<div className=" rounded bg-slate-50 p-4">
|
||||||
|
<Label className="font-normal text-slate-500">
|
||||||
|
Enter a URL to see if it matches your event URL
|
||||||
|
</Label>
|
||||||
|
<div className="mt-1 flex">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={testUrl}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTestUrl(e.target.value);
|
||||||
|
setIsMatch("default");
|
||||||
|
}}
|
||||||
|
className={clsx(
|
||||||
|
isMatch === "yes"
|
||||||
|
? "border-green-500 bg-green-50"
|
||||||
|
: isMatch === "no"
|
||||||
|
? "border-red-200 bg-red-50"
|
||||||
|
: isMatch === "default"
|
||||||
|
? "border-slate-200 bg-white"
|
||||||
|
: null
|
||||||
|
)}
|
||||||
|
placeholder="Paste the URL you want the event to trigger on"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
className="ml-2 whitespace-nowrap"
|
||||||
|
onClick={() => {
|
||||||
|
handleMatchClick();
|
||||||
|
}}>
|
||||||
|
Test Match
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{watch("noCodeConfig.type") === "innerHtml" && (
|
||||||
|
<div className="grid w-full grid-cols-3 gap-x-8">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<Label>Inner Text</Label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3 flex w-full items-end">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. 'Install App'"
|
||||||
|
{...register("noCodeConfig.innerHtml.value", { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{watch("noCodeConfig.type") === "cssSelector" && (
|
||||||
|
<div className="grid w-full grid-cols-3 gap-x-8">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<Label>CSS Tag</Label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3 flex w-full items-end">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. #install-button"
|
||||||
|
{...register("noCodeConfig.cssSelector.value", { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : eventClass.type === "automatic" ? (
|
) : eventClass.type === "automatic" ? (
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-slate-600">
|
||||||
@@ -99,7 +253,7 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
|
|||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between border-t border-slate-200 pt-6">
|
<div className="flex justify-between border-t border-slate-200 py-6">
|
||||||
<div>
|
<div>
|
||||||
<Button variant="secondary" href="https://formbricks.com/docs" target="_blank">
|
<Button variant="secondary" href="https://formbricks.com/docs" target="_blank">
|
||||||
Read Docs
|
Read Docs
|
||||||
|
|||||||
Reference in New Issue
Block a user