diff --git a/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicCreateSegmentModal.tsx b/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicCreateSegmentModal.tsx index c4e4cea22a..9f8c63041c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicCreateSegmentModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicCreateSegmentModal.tsx @@ -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(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 ( <>
@@ -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 && ( -

- {titleError} -

- )}
@@ -164,6 +176,7 @@ const BasicCreateSegmentModal = ({ description: e.target.value, })); }} + className="w-auto" /> @@ -231,6 +244,7 @@ const BasicCreateSegmentModal = ({ variant="darkCTA" type="submit" loading={isCreatingSegment} + disabled={isSaveDisabled} onClick={() => { handleCreateSegment(); }}> diff --git a/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicSegmentSettings.tsx b/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicSegmentSettings.tsx index efb0e55368..c9f5e169e7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicSegmentSettings.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicSegmentSettings.tsx @@ -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 && ( -

{titleError}

- )} @@ -175,7 +173,7 @@ const BasicSegmentSettings = ({ description: e.target.value, })); }} - className={cn("w-auto")} + className="w-auto" /> @@ -183,6 +181,13 @@ const BasicSegmentSettings = ({
+ {segment?.filters?.length === 0 && ( +
+ +

Add your first filter to get started

+
+ )} + { if (isAdvancedTargetingAllowed) { return ( - (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 ( <>
@@ -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 && ( -

- {titleError} -

- )}
@@ -225,6 +237,7 @@ const CreateSegmentModal = ({ variant="darkCTA" type="submit" loading={isCreatingSegment} + disabled={isSaveDisabled} onClick={() => { handleCreateSegment(); }}> diff --git a/packages/ee/advancedTargeting/components/SegmentEditor.tsx b/packages/ee/advancedTargeting/components/SegmentEditor.tsx index 2157c3f6c3..3e843c6e41 100644 --- a/packages/ee/advancedTargeting/components/SegmentEditor.tsx +++ b/packages/ee/advancedTargeting/components/SegmentEditor.tsx @@ -32,7 +32,7 @@ type TSegmentEditorProps = { segments: TSegment[]; actionClasses: TActionClass[]; attributeClasses: TAttributeClass[]; - setSegment: React.Dispatch>; + setSegment: React.Dispatch>; viewOnly?: boolean; }; diff --git a/packages/ee/advancedTargeting/components/SegmentSettings.tsx b/packages/ee/advancedTargeting/components/SegmentSettings.tsx index ec49282daf..3837679188 100644 --- a/packages/ee/advancedTargeting/components/SegmentSettings.tsx +++ b/packages/ee/advancedTargeting/components/SegmentSettings.tsx @@ -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 && ( -

{titleError}

- )} @@ -173,6 +172,13 @@ const SegmentSettings = ({
+ {segment?.filters?.length === 0 && ( +
+ +

Add your first filter to get started

+
+ )} +