fix: segments modals (#2070)

This commit is contained in:
Anshuman Pandey
2024-02-14 15:11:57 +05:30
committed by GitHub
parent 3090151c50
commit e6fe027e57
6 changed files with 118 additions and 80 deletions

View File

@@ -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();
}}>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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();
}}>

View File

@@ -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;
};

View File

@@ -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}