mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
fix: segments modals (#2070)
This commit is contained in:
@@ -3,13 +3,12 @@
|
||||
import { UserGroupIcon } from "@heroicons/react/20/solid";
|
||||
import { FilterIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { createSegmentAction } from "@formbricks/ee/advancedTargeting/lib/actions";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TBaseFilter, TSegment } from "@formbricks/types/segment";
|
||||
import { TBaseFilter, TSegment, ZSegmentFilters } from "@formbricks/types/segment";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Input } from "@formbricks/ui/Input";
|
||||
import { Modal } from "@formbricks/ui/Modal";
|
||||
@@ -45,11 +44,8 @@ const BasicCreateSegmentModal = ({
|
||||
const [segment, setSegment] = useState<TSegment>(initialSegmentState);
|
||||
const [isCreatingSegment, setIsCreatingSegment] = useState(false);
|
||||
|
||||
const [titleError, setTitleError] = useState("");
|
||||
|
||||
const handleResetState = () => {
|
||||
setSegment(initialSegmentState);
|
||||
setTitleError("");
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
@@ -69,7 +65,7 @@ const BasicCreateSegmentModal = ({
|
||||
|
||||
const handleCreateSegment = async () => {
|
||||
if (!segment.title) {
|
||||
setTitleError("Title is required");
|
||||
toast.error("Title is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,7 +83,13 @@ const BasicCreateSegmentModal = ({
|
||||
setIsCreatingSegment(false);
|
||||
toast.success("Segment created successfully!");
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
// parse the segment filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
toast.error("Invalid filters. Please check the filters and try again.");
|
||||
} else {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
setIsCreatingSegment(false);
|
||||
return;
|
||||
}
|
||||
@@ -97,6 +99,22 @@ const BasicCreateSegmentModal = ({
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
const isSaveDisabled = useMemo(() => {
|
||||
// check if title is empty
|
||||
|
||||
if (!segment.title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// parse the filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [segment]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex justify-end">
|
||||
@@ -143,14 +161,8 @@ const BasicCreateSegmentModal = ({
|
||||
title: e.target.value,
|
||||
}));
|
||||
}}
|
||||
className={cn(titleError && "border border-red-500 focus:border-red-500")}
|
||||
className="w-auto"
|
||||
/>
|
||||
|
||||
{titleError && (
|
||||
<p className="absolute right-1 bg-white text-xs text-red-500" style={{ top: "-8px" }}>
|
||||
{titleError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,6 +176,7 @@ const BasicCreateSegmentModal = ({
|
||||
description: e.target.value,
|
||||
}));
|
||||
}}
|
||||
className="w-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -231,6 +244,7 @@ const BasicCreateSegmentModal = ({
|
||||
variant="darkCTA"
|
||||
type="submit"
|
||||
loading={isCreatingSegment}
|
||||
disabled={isSaveDisabled}
|
||||
onClick={() => {
|
||||
handleCreateSegment();
|
||||
}}>
|
||||
|
||||
@@ -4,12 +4,11 @@ import {
|
||||
deleteBasicSegmentAction,
|
||||
updateBasicSegmentAction,
|
||||
} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { FilterIcon, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { isAdvancedSegment } from "@formbricks/lib/segment/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TBaseFilter, TSegment, TSegmentWithSurveyNames, ZSegmentFilters } from "@formbricks/types/segment";
|
||||
@@ -43,17 +42,12 @@ const BasicSegmentSettings = ({
|
||||
const [isUpdatingSegment, setIsUpdatingSegment] = useState(false);
|
||||
const [isDeletingSegment, setIsDeletingSegment] = useState(false);
|
||||
|
||||
const [titleError, setTitleError] = useState("");
|
||||
|
||||
const [isSaveDisabled, setIsSaveDisabled] = useState(false);
|
||||
const [isDeleteSegmentModalOpen, setIsDeleteSegmentModalOpen] = useState(false);
|
||||
|
||||
const handleResetState = () => {
|
||||
setSegment(initialSegment);
|
||||
setOpen(false);
|
||||
|
||||
setTitleError("");
|
||||
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
@@ -73,7 +67,7 @@ const BasicSegmentSettings = ({
|
||||
|
||||
const handleUpdateSegment = async () => {
|
||||
if (!segment.title) {
|
||||
setTitleError("Title is required");
|
||||
toast.error("Title is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,7 +83,13 @@ const BasicSegmentSettings = ({
|
||||
setIsUpdatingSegment(false);
|
||||
toast.success("Segment updated successfully!");
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
// parse the segment filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
toast.error("Invalid filters. Please check the filters and try again.");
|
||||
} else {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
setIsUpdatingSegment(false);
|
||||
return;
|
||||
}
|
||||
@@ -108,20 +108,26 @@ const BasicSegmentSettings = ({
|
||||
toast.success("Segment deleted successfully!");
|
||||
handleResetState();
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
|
||||
setIsDeletingSegment(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isSaveDisabled = useMemo(() => {
|
||||
// check if title is empty
|
||||
|
||||
if (!segment.title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// parse the filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
setIsSaveDisabled(true);
|
||||
} else {
|
||||
setIsSaveDisabled(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [segment]);
|
||||
|
||||
if (isAdvancedSegment(segment.filters)) {
|
||||
@@ -149,17 +155,9 @@ const BasicSegmentSettings = ({
|
||||
...prev,
|
||||
title: e.target.value,
|
||||
}));
|
||||
|
||||
if (e.target.value) {
|
||||
setTitleError("");
|
||||
}
|
||||
}}
|
||||
className={cn("w-auto", titleError && "border border-red-500 focus:border-red-500")}
|
||||
className="w-auto"
|
||||
/>
|
||||
|
||||
{titleError && (
|
||||
<p className="absolute -bottom-1.5 right-2 bg-white text-xs text-red-500">{titleError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -175,7 +173,7 @@ const BasicSegmentSettings = ({
|
||||
description: e.target.value,
|
||||
}));
|
||||
}}
|
||||
className={cn("w-auto")}
|
||||
className="w-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,6 +181,13 @@ const BasicSegmentSettings = ({
|
||||
|
||||
<label className="my-4 text-sm font-medium text-slate-900">Targeting</label>
|
||||
<div className="filter-scrollbar flex max-h-96 w-full flex-col gap-4 overflow-auto rounded-lg border border-slate-200 bg-slate-50 p-4">
|
||||
{segment?.filters?.length === 0 && (
|
||||
<div className="-mb-2 flex items-center gap-1">
|
||||
<FilterIcon className="h-5 w-5 text-slate-700" />
|
||||
<h3 className="text-sm font-medium text-slate-700">Add your first filter to get started</h3>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<BasicSegmentEditor
|
||||
environmentId={environmentId}
|
||||
segment={segment}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { UserGroupIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
import SegmentSettingsTab from "@formbricks/ee/advancedTargeting/components/SegmentSettings";
|
||||
import SegmentSettings from "@formbricks/ee/advancedTargeting/components/SegmentSettings";
|
||||
import { TActionClass } from "@formbricks/types/actionClasses";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TSegment, TSegmentWithSurveyNames } from "@formbricks/types/segment";
|
||||
@@ -37,7 +37,7 @@ export default function EditSegmentModal({
|
||||
const SettingsTab = () => {
|
||||
if (isAdvancedTargetingAllowed) {
|
||||
return (
|
||||
<SegmentSettingsTab
|
||||
<SegmentSettings
|
||||
actionClasses={actionClasses}
|
||||
attributeClasses={attributeClasses}
|
||||
environmentId={environmentId}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
import { UserGroupIcon } from "@heroicons/react/20/solid";
|
||||
import { FilterIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TActionClass } from "@formbricks/types/actionClasses";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TBaseFilter, TSegment } from "@formbricks/types/segment";
|
||||
import { TBaseFilter, TSegment, ZSegmentFilters } from "@formbricks/types/segment";
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Input } from "@formbricks/ui/Input";
|
||||
import { Modal } from "@formbricks/ui/Modal";
|
||||
@@ -48,11 +47,8 @@ const CreateSegmentModal = ({
|
||||
const [segment, setSegment] = useState<TSegment>(initialSegmentState);
|
||||
const [isCreatingSegment, setIsCreatingSegment] = useState(false);
|
||||
|
||||
const [titleError, setTitleError] = useState("");
|
||||
|
||||
const handleResetState = () => {
|
||||
setSegment(initialSegmentState);
|
||||
setTitleError("");
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
@@ -72,7 +68,7 @@ const CreateSegmentModal = ({
|
||||
|
||||
const handleCreateSegment = async () => {
|
||||
if (!segment.title) {
|
||||
setTitleError("Title is required");
|
||||
toast.error("Title is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,7 +86,13 @@ const CreateSegmentModal = ({
|
||||
setIsCreatingSegment(false);
|
||||
toast.success("Segment created successfully!");
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
// parse the segment filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
toast.error("Invalid filters. Please check the filters and try again.");
|
||||
} else {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
setIsCreatingSegment(false);
|
||||
return;
|
||||
}
|
||||
@@ -100,6 +102,22 @@ const CreateSegmentModal = ({
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
const isSaveDisabled = useMemo(() => {
|
||||
// check if title is empty
|
||||
|
||||
if (!segment.title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// parse the filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [segment]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex justify-end">
|
||||
@@ -147,14 +165,8 @@ const CreateSegmentModal = ({
|
||||
title: e.target.value,
|
||||
}));
|
||||
}}
|
||||
className={cn(titleError && "border border-red-500 focus:border-red-500")}
|
||||
className="w-auto"
|
||||
/>
|
||||
|
||||
{titleError && (
|
||||
<p className="absolute right-1 bg-white text-xs text-red-500" style={{ top: "-8px" }}>
|
||||
{titleError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -225,6 +237,7 @@ const CreateSegmentModal = ({
|
||||
variant="darkCTA"
|
||||
type="submit"
|
||||
loading={isCreatingSegment}
|
||||
disabled={isSaveDisabled}
|
||||
onClick={() => {
|
||||
handleCreateSegment();
|
||||
}}>
|
||||
|
||||
@@ -32,7 +32,7 @@ type TSegmentEditorProps = {
|
||||
segments: TSegment[];
|
||||
actionClasses: TActionClass[];
|
||||
attributeClasses: TAttributeClass[];
|
||||
setSegment: React.Dispatch<React.SetStateAction<TSegment | null>>;
|
||||
setSegment: React.Dispatch<React.SetStateAction<TSegment>>;
|
||||
viewOnly?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { FilterIcon, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
@@ -42,16 +42,12 @@ const SegmentSettings = ({
|
||||
const [isUpdatingSegment, setIsUpdatingSegment] = useState(false);
|
||||
const [isDeletingSegment, setIsDeletingSegment] = useState(false);
|
||||
|
||||
const [titleError, setTitleError] = useState("");
|
||||
|
||||
const [isSaveDisabled, setIsSaveDisabled] = useState(false);
|
||||
const [isDeleteSegmentModalOpen, setIsDeleteSegmentModalOpen] = useState(false);
|
||||
|
||||
const handleResetState = () => {
|
||||
setSegment(initialSegment);
|
||||
setOpen(false);
|
||||
|
||||
setTitleError("");
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
@@ -71,7 +67,7 @@ const SegmentSettings = ({
|
||||
|
||||
const handleUpdateSegment = async () => {
|
||||
if (!segment.title) {
|
||||
setTitleError("Title is required");
|
||||
toast.error("Title is required");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,7 +83,12 @@ const SegmentSettings = ({
|
||||
setIsUpdatingSegment(false);
|
||||
toast.success("Segment updated successfully!");
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
toast.error("Invalid filters. Please check the filters and try again.");
|
||||
} else {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
setIsUpdatingSegment(false);
|
||||
return;
|
||||
}
|
||||
@@ -106,20 +107,26 @@ const SegmentSettings = ({
|
||||
toast.success("Segment deleted successfully!");
|
||||
handleResetState();
|
||||
} catch (err: any) {
|
||||
toast.error(`${err.message}`);
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
|
||||
setIsDeletingSegment(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isSaveDisabled = useMemo(() => {
|
||||
// check if title is empty
|
||||
|
||||
if (!segment.title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// parse the filters to check if they are valid
|
||||
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
|
||||
if (!parsedFilters.success) {
|
||||
setIsSaveDisabled(true);
|
||||
} else {
|
||||
setIsSaveDisabled(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [segment]);
|
||||
|
||||
return (
|
||||
@@ -139,17 +146,9 @@ const SegmentSettings = ({
|
||||
...prev,
|
||||
title: e.target.value,
|
||||
}));
|
||||
|
||||
if (e.target.value) {
|
||||
setTitleError("");
|
||||
}
|
||||
}}
|
||||
className={cn("w-auto", titleError && "border border-red-500 focus:border-red-500")}
|
||||
className="w-auto"
|
||||
/>
|
||||
|
||||
{titleError && (
|
||||
<p className="absolute -bottom-1.5 right-2 bg-white text-xs text-red-500">{titleError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -173,6 +172,13 @@ const SegmentSettings = ({
|
||||
|
||||
<label className="my-4 text-sm font-medium text-slate-900">Targeting</label>
|
||||
<div className="filter-scrollbar flex max-h-96 w-full flex-col gap-4 overflow-auto rounded-lg border border-slate-200 bg-slate-50 p-4">
|
||||
{segment?.filters?.length === 0 && (
|
||||
<div className="-mb-2 flex items-center gap-1">
|
||||
<FilterIcon className="h-5 w-5 text-slate-700" />
|
||||
<h3 className="text-sm font-medium text-slate-700">Add your first filter to get started</h3>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SegmentEditor
|
||||
environmentId={environmentId}
|
||||
segment={segment}
|
||||
|
||||
Reference in New Issue
Block a user