fix: survey editor validation (#2749)

Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
Anshuman Pandey
2024-07-09 20:03:09 +05:30
committed by GitHub
parent 7cf9885125
commit 647a05f469
216 changed files with 1119 additions and 751 deletions

View File

@@ -27,7 +27,7 @@ import { TActionClassInput } from "@formbricks/types/actionClasses";
import { AuthorizationError } from "@formbricks/types/errors";
import { TProduct } from "@formbricks/types/product";
import { TBaseFilters, TSegmentUpdateInput, ZSegmentFilters } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
export const surveyMutateAction = async (survey: TSurvey): Promise<TSurvey> => {
return await updateSurvey(survey);

View File

@@ -1,7 +1,7 @@
"use client";
import { TActionClass } from "@formbricks/types/actionClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ModalWithTabs } from "@formbricks/ui/ModalWithTabs";
import { CreateNewActionTab } from "./CreateNewActionTab";
import { SavedActionsTab } from "./SavedActionsTab";

View File

@@ -3,7 +3,7 @@
import { PlusIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -1,5 +1,5 @@
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { LogicEditor } from "./LogicEditor";
import { UpdateQuestionId } from "./UpdateQuestionId";

View File

@@ -5,7 +5,7 @@ import { CheckIcon } from "lucide-react";
import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
import { TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling } from "@formbricks/types/surveys";
import { TSurveyStyling } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/Badge";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/Form";
import { Slider } from "@formbricks/ui/Slider";

View File

@@ -3,7 +3,7 @@
import { useState } from "react";
import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -2,7 +2,7 @@ import { PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Checkbox } from "@formbricks/ui/Checkbox";
import { Input } from "@formbricks/ui/Input";

View File

@@ -7,7 +7,7 @@ import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { TProduct, TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling, TSurveyType } from "@formbricks/types/surveys";
import { TSurveyStyling, TSurveyType } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/Badge";
import { CardArrangementTabs } from "@formbricks/ui/CardArrangementTabs";
import { ColorPicker } from "@formbricks/ui/ColorPicker";

View File

@@ -3,7 +3,7 @@
import { useState } from "react";
import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -10,7 +10,7 @@ import {
TActionClassInputCode,
ZActionClassInput,
} from "@formbricks/types/actionClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/Form";
import { Input } from "@formbricks/ui/Input";

View File

@@ -1,7 +1,7 @@
import { PlusIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -5,7 +5,7 @@ import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -6,7 +6,7 @@ import { useState } from "react";
import { LocalizedEditor } from "@formbricks/ee/multi-language/components/localized-editor";
import { cn } from "@formbricks/lib/cn";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { FileInput } from "@formbricks/ui/FileInput";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -10,7 +10,7 @@ import { useGetBillingInfo } from "@formbricks/lib/organization/hooks/useGetBill
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TAllowedFileExtension, ZAllowedFileExtension } from "@formbricks/types/common";
import { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyFileUploadQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyFileUploadQuestion } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";

View File

@@ -8,7 +8,7 @@ import { cn } from "@formbricks/lib/cn";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { mixColor } from "@formbricks/lib/utils/colors";
import { TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling } from "@formbricks/types/surveys";
import { TSurveyStyling } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { ColorPicker } from "@formbricks/ui/ColorPicker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/Form";

View File

@@ -4,13 +4,13 @@ import * as Collapsible from "@radix-ui/react-collapsible";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { TSurvey, TSurveyHiddenFields } from "@formbricks/types/surveys";
import { TSurvey, TSurveyHiddenFields } from "@formbricks/types/surveys/types";
import { validateId } from "@formbricks/types/surveys/validation";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import { Tag } from "@formbricks/ui/Tag";
import { validateId } from "../lib/validation";
interface HiddenFieldsCardProps {
localSurvey: TSurvey;
@@ -119,14 +119,24 @@ export const HiddenFieldsCard = ({
e.preventDefault();
const existingQuestionIds = localSurvey.questions.map((question) => question.id);
const existingHiddenFieldIds = localSurvey.hiddenFields.fieldIds ?? [];
if (validateId("Hidden field", hiddenField, existingQuestionIds, existingHiddenFieldIds)) {
updateSurvey({
fieldIds: [...(localSurvey.hiddenFields?.fieldIds || []), hiddenField],
enabled: true,
});
toast.success("Hidden field added successfully");
setHiddenField("");
const validateIdError = validateId(
"Hidden field",
hiddenField,
existingQuestionIds,
existingHiddenFieldIds
);
if (validateIdError) {
toast.error(validateIdError);
return;
}
updateSurvey({
fieldIds: [...(localSurvey.hiddenFields?.fieldIds || []), hiddenField],
enabled: true,
});
toast.success("Hidden field added successfully");
setHiddenField("");
}}>
<Label htmlFor="headline">Hidden Field</Label>
<div className="mt-2 flex gap-2">

View File

@@ -8,7 +8,7 @@ import { cn } from "@formbricks/lib/cn";
import { TEnvironment } from "@formbricks/types/environment";
import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/Badge";
import { Label } from "@formbricks/ui/Label";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui/RadioGroup";

View File

@@ -18,7 +18,7 @@ import {
TSurveyLogicCondition,
TSurveyQuestion,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys";
} from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import {
DropdownMenu,

View File

@@ -1,10 +1,9 @@
"use client";
import { PlusIcon, TrashIcon } from "lucide-react";
import { toast } from "react-hot-toast";
import { createI18nString, extractLanguageCodes, getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys";
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
@@ -79,25 +78,6 @@ export const MatrixQuestionForm = ({
}
};
const checkForDuplicateLabels = () => {
const rowLabels = question.rows
.map((row) => getLocalizedValue(row, selectedLanguageCode))
.filter((label) => label.trim() !== "");
const columnLabels = question.columns
.map((column) => getLocalizedValue(column, selectedLanguageCode))
.filter((label) => label.trim() !== "");
const duplicateRowLabels = rowLabels.filter((label, index, array) => array.indexOf(label) !== index);
const duplicateColumnLabels = columnLabels.filter(
(label, index, array) => array.indexOf(label) !== index
);
if (duplicateRowLabels.length > 0 || duplicateColumnLabels.length > 0) {
toast.error("Duplicate row or column labels");
}
};
return (
<form>
<QuestionFormInput
@@ -164,7 +144,6 @@ export const MatrixQuestionForm = ({
updateMatrixLabel={updateMatrixLabel}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
onBlur={checkForDuplicateLabels}
isInvalid={
isInvalid && !isLabelValidForAllLanguages(question.rows[index], localSurvey.languages)
}
@@ -207,7 +186,6 @@ export const MatrixQuestionForm = ({
updateMatrixLabel={updateMatrixLabel}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
onBlur={checkForDuplicateLabels}
isInvalid={
isInvalid && !isLabelValidForAllLanguages(question.columns[index], localSurvey.languages)
}

View File

@@ -14,7 +14,7 @@ import {
TSurvey,
TSurveyMultipleChoiceQuestion,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys";
} from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
@@ -68,20 +68,6 @@ export const MultipleChoiceQuestionForm = ({
},
};
const findDuplicateLabel = () => {
for (let i = 0; i < question.choices.length; i++) {
for (let j = i + 1; j < question.choices.length; j++) {
if (
getLocalizedValue(question.choices[i].label, selectedLanguageCode).trim() ===
getLocalizedValue(question.choices[j].label, selectedLanguageCode).trim()
) {
return getLocalizedValue(question.choices[i].label, selectedLanguageCode).trim(); // Return the duplicate label
}
}
}
return null;
};
const updateChoice = (choiceIdx: number, updatedAttributes: { label: TI18nString }) => {
const newLabel = updatedAttributes.label.en;
const oldLabel = question.choices[choiceIdx].label;
@@ -267,13 +253,11 @@ export const MultipleChoiceQuestionForm = ({
updateChoice={updateChoice}
deleteChoice={deleteChoice}
addChoice={addChoice}
setisInvalidValue={setisInvalidValue}
isInvalid={isInvalid}
localSurvey={localSurvey}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}
surveyLanguages={surveyLanguages}
findDuplicateLabel={findDuplicateLabel}
question={question}
updateQuestion={updateQuestion}
surveyLanguageCodes={surveyLanguageCodes}

View File

@@ -3,7 +3,7 @@
import { PlusIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -7,7 +7,7 @@ import {
TSurvey,
TSurveyOpenTextQuestion,
TSurveyOpenTextQuestionInputType,
} from "@formbricks/types/surveys";
} from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";

View File

@@ -3,7 +3,7 @@ import { PlusIcon } from "lucide-react";
import { cn } from "@formbricks/lib/cn";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { FileInput } from "@formbricks/ui/FileInput";
import { Label } from "@formbricks/ui/Label";

View File

@@ -10,7 +10,12 @@ import { cn } from "@formbricks/lib/cn";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TProduct } from "@formbricks/types/product";
import { TI18nString, TSurvey, TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import {
TI18nString,
TSurvey,
TSurveyQuestion,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
import { Switch } from "@formbricks/ui/Switch";

View File

@@ -5,7 +5,7 @@ import { createId } from "@paralleldrive/cuid2";
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
import React, { useState } from "react";
import { TProduct } from "@formbricks/types/product";
import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { ConfirmationModal } from "@formbricks/ui/ConfirmationModal";
import {
DropdownMenu,

View File

@@ -1,7 +1,7 @@
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TProduct } from "@formbricks/types/product";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { QuestionCard } from "./QuestionCard";
interface QuestionsDraggableProps {

View File

@@ -1,7 +1,7 @@
import { PaintbrushIcon, Rows3Icon, SettingsIcon } from "lucide-react";
import { useMemo } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyEditorTabs } from "@formbricks/types/surveys";
import { TSurveyEditorTabs } from "@formbricks/types/surveys/types";
interface Tab {
id: TSurveyEditorTabs;

View File

@@ -17,13 +17,9 @@ import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import {
findQuestionsWithCyclicLogic,
isCardValid,
validateQuestion,
validateSurveyQuestionsInBatch,
} from "../lib/validation";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { findQuestionsWithCyclicLogic } from "@formbricks/types/surveys/validation";
import { isCardValid, validateQuestion, validateSurveyQuestionsInBatch } from "../lib/validation";
import { AddQuestionButton } from "./AddQuestionButton";
import { EditThankYouCard } from "./EditThankYouCard";
import { EditWelcomeCard } from "./EditWelcomeCard";
@@ -37,7 +33,7 @@ interface QuestionsViewProps {
setActiveQuestionId: (questionId: string | null) => void;
product: TProduct;
invalidQuestions: string[] | null;
setInvalidQuestions: (invalidQuestions: string[] | null) => void;
setInvalidQuestions: React.Dispatch<SetStateAction<string[] | null>>;
selectedLanguageCode: string;
setSelectedLanguageCode: (languageCode: string) => void;
isMultiLanguageAllowed?: boolean;
@@ -92,19 +88,24 @@ export const QuestionsView = ({
if (invalidQuestions === null) {
return;
}
const isFirstQuestion = question.id === localSurvey.questions[0].id;
let temp = structuredClone(invalidQuestions);
if (validateQuestion(question, surveyLanguages, isFirstQuestion)) {
// If question is valid, we now check for cyclic logic
const questionsWithCyclicLogic = findQuestionsWithCyclicLogic(localSurvey.questions);
if (!questionsWithCyclicLogic.includes(question.id)) {
temp = invalidQuestions.filter((id) => id !== question.id);
setInvalidQuestions(temp);
if (questionsWithCyclicLogic.includes(question.id) && !invalidQuestions.includes(question.id)) {
setInvalidQuestions([...invalidQuestions, question.id]);
return;
}
} else if (!invalidQuestions.includes(question.id)) {
temp.push(question.id);
setInvalidQuestions(temp);
setInvalidQuestions(invalidQuestions.filter((id) => id !== question.id));
return;
}
setInvalidQuestions([...invalidQuestions, question.id]);
return;
};
const updateQuestion = (questionIdx: number, updatedAttributes: any) => {
@@ -125,6 +126,7 @@ export const QuestionsView = ({
delete internalQuestionIdMap[localSurvey.questions[questionIdx].id];
setActiveQuestionId(updatedAttributes.id);
}
updatedSurvey.questions[questionIdx] = {
...updatedSurvey.questions[questionIdx],
...updatedAttributes,

View File

@@ -1,7 +1,7 @@
import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyRatingQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";

View File

@@ -4,7 +4,7 @@ import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";

View File

@@ -6,7 +6,7 @@ import Link from "next/link";
import { KeyboardEventHandler, useEffect, useState } from "react";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { DatePicker } from "@formbricks/ui/DatePicker";
import { Input } from "@formbricks/ui/Input";
@@ -293,7 +293,12 @@ export const ResponseOptionsCard = ({
};
const handleInputResponse = (e) => {
const updatedSurvey = { ...localSurvey, autoComplete: parseInt(e.target.value) };
let value = parseInt(e.target.value);
if (Number.isNaN(value) || value < 1) {
value = 1;
}
const updatedSurvey = { ...localSurvey, autoComplete: value };
setLocalSurvey(updatedSurvey);
};
@@ -343,7 +348,7 @@ export const ResponseOptionsCard = ({
description="Automatically close the survey after a certain number of responses."
childBorder={true}>
<label htmlFor="autoCompleteResponses" className="cursor-pointer bg-slate-50 p-4">
<p className="text-sm text-slate-700">
<p className="text-sm font-semibold text-slate-700">
Automatically mark the survey as complete after
<Input
autoFocus

View File

@@ -1,7 +1,7 @@
import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react";
import { useState } from "react";
import { TActionClass } from "@formbricks/types/actionClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Input } from "@formbricks/ui/Input";
interface SavedActionsTabProps {

View File

@@ -1,7 +1,6 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { GripVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { createI18nString } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
@@ -10,7 +9,7 @@ import {
TSurvey,
TSurveyLanguage,
TSurveyMultipleChoiceQuestion,
} from "@formbricks/types/surveys";
} from "@formbricks/types/surveys/types";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
import { isLabelValidForAllLanguages } from "../lib/validation";
@@ -24,13 +23,11 @@ interface ChoiceProps {
updateChoice: (choiceIdx: number, updatedAttributes: { label: TI18nString }) => void;
deleteChoice: (choiceIdx: number) => void;
addChoice: (choiceIdx: number) => void;
setisInvalidValue: (value: string | null) => void;
isInvalid: boolean;
localSurvey: TSurvey;
selectedLanguageCode: string;
setSelectedLanguageCode: (language: string) => void;
surveyLanguages: TSurveyLanguage[];
findDuplicateLabel: () => string | null;
question: TSurveyMultipleChoiceQuestion;
updateQuestion: (questionIdx: number, updatedAttributes: Partial<TSurveyMultipleChoiceQuestion>) => void;
surveyLanguageCodes: string[];
@@ -47,10 +44,8 @@ export const SelectQuestionChoice = ({
questionIdx,
selectedLanguageCode,
setSelectedLanguageCode,
setisInvalidValue,
surveyLanguages,
updateChoice,
findDuplicateLabel,
question,
surveyLanguageCodes,
updateQuestion,
@@ -83,15 +78,6 @@ export const SelectQuestionChoice = ({
localSurvey={localSurvey}
questionIdx={questionIdx}
value={choice.label}
onBlur={() => {
const duplicateLabel = findDuplicateLabel();
if (duplicateLabel) {
toast.error("Duplicate choices");
setisInvalidValue(duplicateLabel);
} else {
setisInvalidValue(null);
}
}}
updateChoice={updateChoice}
selectedLanguageCode={selectedLanguageCode}
setSelectedLanguageCode={setSelectedLanguageCode}

View File

@@ -5,7 +5,7 @@ import { TEnvironment } from "@formbricks/types/environment";
import { TMembershipRole } from "@formbricks/types/memberships";
import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { HowToSendCard } from "./HowToSendCard";
import { RecontactOptionsCard } from "./RecontactOptionsCard";
import { ResponseOptionsCard } from "./ResponseOptionsCard";

View File

@@ -7,7 +7,7 @@ import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { TEnvironment } from "@formbricks/types/environment";
import { TProduct, TProductStyling } from "@formbricks/types/product";
import { TBaseStyling } from "@formbricks/types/styling";
import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys";
import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/AlertDialog";
import { Button } from "@formbricks/ui/Button";
import {

View File

@@ -10,7 +10,7 @@ import { TEnvironment } from "@formbricks/types/environment";
import { TMembershipRole } from "@formbricks/types/memberships";
import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyEditorTabs, TSurveyStyling } from "@formbricks/types/surveys";
import { TSurvey, TSurveyEditorTabs, TSurveyStyling } from "@formbricks/types/surveys/types";
import { PreviewSurvey } from "@formbricks/ui/PreviewSurvey";
import { refetchProductAction } from "../actions";
import { LoadingSkeleton } from "./LoadingSkeleton";

View File

@@ -7,10 +7,11 @@ import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { createSegmentAction } from "@formbricks/ee/advanced-targeting/lib/actions";
import { getLanguageLabel } from "@formbricks/lib/i18n/utils";
import { TEnvironment } from "@formbricks/types/environment";
import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyEditorTabs } from "@formbricks/types/surveys";
import { TSurvey, TSurveyEditorTabs, TSurveyQuestion, ZSurvey } from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/AlertDialog";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
@@ -25,7 +26,7 @@ interface SurveyMenuBarProps {
environment: TEnvironment;
activeId: TSurveyEditorTabs;
setActiveId: React.Dispatch<React.SetStateAction<TSurveyEditorTabs>>;
setInvalidQuestions: (invalidQuestions: string[]) => void;
setInvalidQuestions: React.Dispatch<React.SetStateAction<string[]>>;
product: TProduct;
responseCount: number;
selectedLanguageCode: string;
@@ -43,7 +44,6 @@ export const SurveyMenuBar = ({
product,
responseCount,
selectedLanguageCode,
setSelectedLanguageCode,
}: SurveyMenuBarProps) => {
const router = useRouter();
const [audiencePrompt, setAudiencePrompt] = useState(true);
@@ -53,8 +53,6 @@ export const SurveyMenuBar = ({
const [isSurveySaving, setIsSurveySaving] = useState(false);
const cautionText = "This survey received responses.";
const faultyQuestions: string[] = [];
useEffect(() => {
if (audiencePrompt && activeId === "settings") {
setAudiencePrompt(false);
@@ -140,20 +138,65 @@ export const SurveyMenuBar = ({
return localSurvey.segment;
};
const handleSurveySave = async () => {
const validateSurveyWithZod = (): boolean => {
const localSurveyValidation = ZSurvey.safeParse(localSurvey);
if (!localSurveyValidation.success) {
const currentError = localSurveyValidation.error.errors[0];
if (currentError.path[0] === "questions") {
const questionIdx = currentError.path[1];
const question: TSurveyQuestion = localSurvey.questions[questionIdx];
if (question) {
setInvalidQuestions((prevInvalidQuestions) =>
prevInvalidQuestions ? [...prevInvalidQuestions, question.id] : [question.id]
);
}
} else if (currentError.path[0] === "welcomeCard") {
setInvalidQuestions((prevInvalidQuestions) =>
prevInvalidQuestions ? [...prevInvalidQuestions, "start"] : ["start"]
);
} else if (currentError.path[0] === "thankYouCard") {
setInvalidQuestions((prevInvalidQuestions) =>
prevInvalidQuestions ? [...prevInvalidQuestions, "end"] : ["end"]
);
}
if (currentError.code === "custom") {
const params = currentError.params ?? ({} as { invalidLanguageCodes: string[] });
if (params.invalidLanguageCodes && params.invalidLanguageCodes.length) {
const invalidLanguageLabels = params.invalidLanguageCodes.map(
(invalidLanguage: string) => getLanguageLabel(invalidLanguage) ?? invalidLanguage
);
toast.error(`${currentError.message} ${invalidLanguageLabels.join(", ")}`);
} else {
toast.error(currentError.message);
}
return false;
}
toast.error(currentError.message);
return false;
}
return true;
};
const handleSurveySave = async (): Promise<boolean> => {
setIsSurveySaving(true);
const isSurveyValidatedWithZod = validateSurveyWithZod();
if (!isSurveyValidatedWithZod) {
setIsSurveySaving(false);
return false;
}
try {
if (
!isSurveyValid(
localSurvey,
faultyQuestions,
setInvalidQuestions,
selectedLanguageCode,
setSelectedLanguageCode
)
) {
const isSurveyValidResult = isSurveyValid(localSurvey, selectedLanguageCode);
if (!isSurveyValidResult) {
setIsSurveySaving(false);
return;
return false;
}
localSurvey.questions = localSurvey.questions.map((question) => {
@@ -168,31 +211,36 @@ export const SurveyMenuBar = ({
setLocalSurvey(updatedSurvey);
toast.success("Changes saved.");
return true;
} catch (e) {
console.error(e);
setIsSurveySaving(false);
toast.error(`Error saving changes`);
return;
return false;
}
};
const handleSaveAndGoBack = async () => {
await handleSurveySave();
router.back();
const isSurveySaved = await handleSurveySave();
if (isSurveySaved) {
router.back();
}
};
const handleSurveyPublish = async () => {
setIsSurveyPublishing(true);
const isSurveyValidatedWithZod = validateSurveyWithZod();
if (!isSurveyValidatedWithZod) {
setIsSurveyPublishing(false);
return;
}
try {
if (
!isSurveyValid(
localSurvey,
faultyQuestions,
setInvalidQuestions,
selectedLanguageCode,
setSelectedLanguageCode
)
) {
const isSurveyValidResult = isSurveyValid(localSurvey, selectedLanguageCode);
if (!isSurveyValidResult) {
setIsSurveyPublishing(false);
return;
}
@@ -262,7 +310,8 @@ export const SurveyMenuBar = ({
variant="secondary"
className="mr-3"
loading={isSurveySaving}
onClick={() => handleSurveySave()}>
onClick={() => handleSurveySave()}
type="submit">
Save
</Button>
{localSurvey.status !== "draft" && (

View File

@@ -5,7 +5,7 @@ import { CheckIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { TPlacement } from "@formbricks/types/common";
import { TSurvey, TSurveyProductOverwrites } from "@formbricks/types/surveys";
import { TSurvey, TSurveyProductOverwrites } from "@formbricks/types/surveys/types";
import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import { Placement } from "./Placement";

View File

@@ -11,7 +11,7 @@ import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
import { isAdvancedSegment } from "@formbricks/lib/segment/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TBaseFilter, TSegment, TSegmentCreateInput, TSegmentUpdateInput } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/AlertDialog";
import { BasicAddFilterModal } from "@formbricks/ui/BasicAddFilterModal";
import { BasicSegmentEditor } from "@formbricks/ui/BasicSegmentEditor";

View File

@@ -5,7 +5,7 @@ import { SearchIcon } from "lucide-react";
import UnsplashImage from "next/image";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { TSurveyBackgroundBgType } from "@formbricks/types/surveys";
import { TSurveyBackgroundBgType } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
import { LoadingSpinner } from "@formbricks/ui/LoadingSpinner";

View File

@@ -2,11 +2,11 @@
import { useState } from "react";
import toast from "react-hot-toast";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { validateId } from "@formbricks/types/surveys/validation";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";
import { validateId } from "../lib/validation";
interface UpdateQuestionIdProps {
localSurvey: TSurvey;
@@ -35,14 +35,20 @@ export const UpdateQuestionId = ({
const questionIds = localSurvey.questions.map((q) => q.id);
const hiddenFieldIds = localSurvey.hiddenFields.fieldIds ?? [];
if (validateId("Question", currentValue, questionIds, hiddenFieldIds)) {
setIsInputInvalid(false);
toast.success("Question ID updated.");
updateQuestion(questionIdx, { id: currentValue });
setPrevValue(currentValue); // after successful update, set current value as previous value
} else {
const validateIdError = validateId("Question", currentValue, questionIds, hiddenFieldIds);
if (validateIdError) {
setIsInputInvalid(true);
toast.error(validateIdError);
setCurrentValue(prevValue);
return;
}
setIsInputInvalid(false);
toast.success("Question ID updated.");
updateQuestion(questionIdx, { id: currentValue });
setPrevValue(currentValue); // after successful update, set current value as previous value
};
const isButtonDisabled = () => {

View File

@@ -13,7 +13,7 @@ import { useEffect, useMemo, useState } from "react";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { TActionClass } from "@formbricks/types/actionClasses";
import { TMembershipRole } from "@formbricks/types/memberships";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
@@ -86,7 +86,9 @@ export const WhenToSendCard = ({
const handleInputSeconds = (e: any) => {
let value = parseInt(e.target.value);
if (value < 1) value = 1;
if (value < 1 || Number.isNaN(value)) {
value = 0;
}
const updatedSurvey = { ...localSurvey, autoClose: value };
setLocalSurvey(updatedSurvey);
@@ -94,27 +96,28 @@ export const WhenToSendCard = ({
const handleTriggerDelay = (e: any) => {
let value = parseInt(e.target.value);
if (value < 1 || Number.isNaN(value)) {
value = 0;
}
const updatedSurvey = { ...localSurvey, delay: value };
setLocalSurvey(updatedSurvey);
};
const handleRandomizerInput = (e) => {
let value: number | null = null;
let value = parseFloat(e.target.value);
if (e.target.value !== "") {
value = parseFloat(e.target.value);
if (Number.isNaN(value)) {
value = 1;
}
if (value < 0.01) value = 0.01;
if (value > 100) value = 100;
// Round value to two decimal places. eg: 10.555(and higher like 10.556) -> 10.56 and 10.554(and lower like 10.553) ->10.55
value = Math.round(value * 100) / 100;
if (Number.isNaN(value)) {
value = 0.01;
}
if (value < 0.01) value = 0.01;
if (value > 100) value = 100;
// Round value to two decimal places. eg: 10.555(and higher like 10.556) -> 10.56 and 10.554(and lower like 10.553) ->10.55
value = Math.round(value * 100) / 100;
const updatedSurvey = { ...localSurvey, displayPercentage: value };
setLocalSurvey(updatedSurvey);
};

View File

@@ -1,5 +1,4 @@
// extend this object in order to add more validation rules
import { isEqual } from "lodash";
import { toast } from "react-hot-toast";
import { extractLanguageCodes, getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { checkForEmptyFallBackValue } from "@formbricks/lib/utils/recall";
@@ -15,11 +14,10 @@ import {
TSurveyOpenTextQuestion,
TSurveyPictureSelectionQuestion,
TSurveyQuestion,
TSurveyQuestionTypeEnum,
TSurveyQuestions,
TSurveyThankYouCard,
TSurveyWelcomeCard,
} from "@formbricks/types/surveys";
} from "@formbricks/types/surveys/types";
import { findLanguageCodesForDuplicateLabels } from "@formbricks/types/surveys/validation";
// Utility function to check if label is valid for all required languages
export const isLabelValidForAllLanguages = (
@@ -39,22 +37,16 @@ const handleI18nCheckForMultipleChoice = (
question: TSurveyMultipleChoiceQuestion,
languages: TSurveyLanguage[]
): boolean => {
return question.choices.every((choice) => isLabelValidForAllLanguages(choice.label, languages));
};
const invalidLangCodes = findLanguageCodesForDuplicateLabels(
question.choices.map((choice) => choice.label),
languages
);
const hasDuplicates = (labels: TI18nString[]) => {
const flattenedLabels = labels
.map((label) =>
Object.keys(label)
.map((lang) => {
const text = label[lang].trim().toLowerCase();
return text && `${lang}:${text}`;
})
.filter((text) => text)
)
.flat();
const uniqueLabels = new Set(flattenedLabels);
return uniqueLabels.size !== flattenedLabels.length;
if (invalidLangCodes.length > 0) {
return false;
}
return question.choices.every((choice) => isLabelValidForAllLanguages(choice.label, languages));
};
const handleI18nCheckForMatrixLabels = (
@@ -63,13 +55,13 @@ const handleI18nCheckForMatrixLabels = (
): boolean => {
const rowsAndColumns = [...question.rows, ...question.columns];
if (hasDuplicates(question.rows)) {
const invalidRowsLangCodes = findLanguageCodesForDuplicateLabels(question.rows, languages);
const invalidColumnsLangCodes = findLanguageCodesForDuplicateLabels(question.columns, languages);
if (invalidRowsLangCodes.length > 0 || invalidColumnsLangCodes.length > 0) {
return false;
}
if (hasDuplicates(question.columns)) {
return false;
}
return rowsAndColumns.every((label) => isLabelValidForAllLanguages(label, languages));
};
@@ -192,281 +184,13 @@ export const isCardValid = (
);
};
export const isValidUrl = (string: string): boolean => {
try {
new URL(string);
return true;
} catch (e) {
return false;
}
};
// Function to validate question ID and Hidden field Id
export const validateId = (
type: "Hidden field" | "Question",
field: string,
existingQuestionIds: string[],
existingHiddenFieldIds: string[]
): boolean => {
if (field.trim() === "") {
toast.error(`Please enter a ${type} Id.`);
return false;
}
const combinedIds = [...existingQuestionIds, ...existingHiddenFieldIds];
if (combinedIds.findIndex((id) => id.toLowerCase() === field.toLowerCase()) !== -1) {
toast.error(`${type} Id already exists in questions or hidden fields.`);
return false;
}
const forbiddenIds = [
"userId",
"source",
"suid",
"end",
"start",
"welcomeCard",
"hidden",
"verifiedEmail",
"multiLanguage",
"embed",
];
if (forbiddenIds.includes(field)) {
toast.error(`${type} Id not allowed.`);
return false;
}
if (field.includes(" ")) {
toast.error(`${type} Id not allowed, avoid using spaces.`);
return false;
}
if (!/^[a-zA-Z0-9_-]+$/.test(field)) {
toast.error(`${type} Id not allowed, use only alphanumeric characters, hyphens, or underscores.`);
return false;
}
return true;
};
// Checks if there is a cycle present in the survey data logic and returns all questions responsible for the cycle.
export const findQuestionsWithCyclicLogic = (questions: TSurveyQuestions): string[] => {
const visited: Record<string, boolean> = {};
const recStack: Record<string, boolean> = {};
const cyclicQuestions: Set<string> = new Set();
const checkForCyclicLogic = (questionId: string): boolean => {
if (!visited[questionId]) {
visited[questionId] = true;
recStack[questionId] = true;
const question = questions.find((question) => question.id === questionId);
if (question && question.logic && question.logic.length > 0) {
for (const logic of question.logic) {
const destination = logic.destination;
if (!destination) {
continue;
}
if (!visited[destination] && checkForCyclicLogic(destination)) {
cyclicQuestions.add(questionId);
return true;
} else if (recStack[destination]) {
cyclicQuestions.add(questionId);
return true;
}
}
} else {
// Handle default behavior
const nextQuestionIndex = questions.findIndex((question) => question.id === questionId) + 1;
const nextQuestion = questions[nextQuestionIndex];
if (nextQuestion && !visited[nextQuestion.id] && checkForCyclicLogic(nextQuestion.id)) {
return true;
}
}
}
recStack[questionId] = false;
return false;
};
for (const question of questions) {
const questionId = question.id;
checkForCyclicLogic(questionId);
}
return Array.from(cyclicQuestions);
};
export const isSurveyValid = (
survey: TSurvey,
faultyQuestions: string[],
setInvalidQuestions: (questions: string[]) => void,
selectedLanguageCode: string,
setSelectedLanguageCode: (languageCode: string) => void
) => {
const existingQuestionIds = new Set();
// Ensuring at least one question is added to the survey.
if (survey.questions.length === 0) {
toast.error("Please add at least one question");
return false;
}
// Checking the validity of the welcome and thank-you cards if they are enabled.
if (survey.welcomeCard.enabled) {
if (!isCardValid(survey.welcomeCard, "start", survey.languages)) {
faultyQuestions.push("start");
}
}
if (survey.thankYouCard.enabled) {
if (!isCardValid(survey.thankYouCard, "end", survey.languages)) {
faultyQuestions.push("end");
}
}
// Verifying that any provided PIN is exactly four digits long.
const pin = survey.pin;
if (pin && pin.toString().length !== 4) {
toast.error("PIN must be a four digit number.");
return false;
}
// Assessing each question for completeness and correctness,
for (let index = 0; index < survey.questions.length; index++) {
const question = survey.questions[index];
const isFirstQuestion = index === 0;
const isValid = validateQuestion(question, survey.languages, isFirstQuestion);
if (!isValid) {
faultyQuestions.push(question.id);
}
}
// if there are any faulty questions, the user won't be allowed to save the survey
if (faultyQuestions.length > 0) {
setInvalidQuestions(faultyQuestions);
setSelectedLanguageCode("default");
toast.error("Please check for empty fields or duplicate labels");
return false;
}
for (const question of survey.questions) {
const existingLogicConditions = new Set();
if (existingQuestionIds.has(question.id)) {
toast.error("There are 2 identical question IDs. Please update one.");
return false;
}
existingQuestionIds.add(question.id);
if (
question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ||
question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti
) {
const haveSameChoices =
question.choices.some((element) => element.label[selectedLanguageCode]?.trim() === "") ||
question.choices.some((element, index) =>
question.choices
.slice(index + 1)
.some(
(nextElement) =>
nextElement.label[selectedLanguageCode]?.trim() === element.label[selectedLanguageCode].trim()
)
);
if (haveSameChoices) {
toast.error("You have empty or duplicate choices.");
return false;
}
}
for (const logic of question.logic || []) {
const validFields = ["condition", "destination", "value"].filter(
(field) => logic[field] !== undefined
).length;
if (validFields < 2) {
setInvalidQuestions([question.id]);
toast.error("Incomplete logic jumps detected: Fill or remove them in the Questions tab.");
return false;
}
if (question.required && logic.condition === "skipped") {
toast.error("A logic condition is missing: Please update or delete it in the Questions tab.");
return false;
}
const thisLogic = `${logic.condition}-${logic.value}`;
if (existingLogicConditions.has(thisLogic)) {
setInvalidQuestions([question.id]);
toast.error(
"There are two competing logic conditons: Please update or delete one in the Questions tab."
);
return false;
}
existingLogicConditions.add(thisLogic);
}
}
// Checking the validity of redirection URLs to ensure they are properly formatted.
if (
survey.redirectUrl &&
!survey.redirectUrl.includes("https://") &&
!survey.redirectUrl.includes("http://")
) {
toast.error("Please enter a valid URL for redirecting respondents.");
return false;
}
// validate the user segment filters
const localSurveySegment = {
id: survey.segment?.id,
filters: survey.segment?.filters,
title: survey.segment?.title,
description: survey.segment?.description,
};
const surveySegment = {
id: survey.segment?.id,
filters: survey.segment?.filters,
title: survey.segment?.title,
description: survey.segment?.description,
};
// if the non-private segment in the survey and the strippedSurvey are different, don't save
if (!survey.segment?.isPrivate && !isEqual(localSurveySegment, surveySegment)) {
toast.error("Please save the audience filters before saving the survey");
return false;
}
if (!!survey.segment?.filters?.length) {
const parsedFilters = ZSegmentFilters.safeParse(survey.segment.filters);
if (!parsedFilters.success) {
const errMsg =
parsedFilters.error.issues.find((issue) => issue.code === "custom")?.message ||
"Invalid targeting: Please check your audience filters";
toast.error(errMsg);
return false;
}
}
export const isSurveyValid = (survey: TSurvey, selectedLanguageCode: string) => {
const questionWithEmptyFallback = checkForEmptyFallBackValue(survey, selectedLanguageCode);
if (questionWithEmptyFallback) {
toast.error("Fallback missing");
return false;
}
// Detecting any cyclic dependencies in survey logic.
const questionsWithCyclicLogic = findQuestionsWithCyclicLogic(survey.questions);
if (questionsWithCyclicLogic.length > 0) {
setInvalidQuestions(questionsWithCyclicLogic);
toast.error("Cyclic logic detected. Please fix it before saving.");
return false;
}
if (survey.type === "app" && survey.segment?.id === "temp") {
const { filters } = survey.segment;

View File

@@ -1,4 +1,4 @@
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
export const minimalSurvey: TSurvey = {
id: "someUniqueId1",

View File

@@ -5,7 +5,7 @@ import { getResponsesByPersonId } from "@formbricks/lib/response/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
interface ResponseSectionProps {

View File

@@ -6,7 +6,7 @@ import { useEffect, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { TUser } from "@formbricks/types/user";

View File

@@ -7,7 +7,7 @@ import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { TUser } from "@formbricks/types/user";
import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller";

View File

@@ -19,7 +19,7 @@ import {
TIntegrationAirtableInput,
TIntegrationAirtableTables,
} from "@formbricks/types/integration/airtable";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
import { Alert, AlertDescription, AlertTitle } from "@formbricks/ui/Alert";
import { Button } from "@formbricks/ui/Button";

View File

@@ -8,7 +8,7 @@ import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ConnectIntegration } from "@formbricks/ui/ConnectIntegration";
interface AirtableWrapperProps {

View File

@@ -12,7 +12,7 @@ import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { DeleteDialog } from "@formbricks/ui/DeleteDialog";
import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller";

View File

@@ -18,7 +18,7 @@ import {
TIntegrationGoogleSheetsConfigData,
TIntegrationGoogleSheetsInput,
} from "@formbricks/types/integration/googleSheet";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
import { Button } from "@formbricks/ui/Button";
import { Checkbox } from "@formbricks/ui/Checkbox";

View File

@@ -10,7 +10,7 @@ import {
TIntegrationGoogleSheets,
TIntegrationGoogleSheetsConfigData,
} from "@formbricks/types/integration/googleSheet";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ConnectIntegration } from "@formbricks/ui/ConnectIntegration";
import { AddIntegrationModal } from "./AddIntegrationModal";

View File

@@ -21,7 +21,7 @@ import {
TIntegrationNotionConfigData,
TIntegrationNotionDatabase,
} from "@formbricks/types/integration/notion";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { DropdownSelector } from "@formbricks/ui/DropdownSelector";
import { Label } from "@formbricks/ui/Label";

View File

@@ -11,7 +11,7 @@ import {
TIntegrationNotionConfigData,
TIntegrationNotionDatabase,
} from "@formbricks/types/integration/notion";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ConnectIntegration } from "@formbricks/ui/ConnectIntegration";
import { authorize } from "../lib/notion";

View File

@@ -1,4 +1,4 @@
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
export const TYPE_MAPPING = {
[TSurveyQuestionTypeEnum.CTA]: ["checkbox"],

View File

@@ -13,7 +13,7 @@ import {
TIntegrationSlackConfigData,
TIntegrationSlackInput,
} from "@formbricks/types/integration/slack";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdditionalIntegrationSettings } from "@formbricks/ui/AdditionalIntegrationSettings";
import { Button } from "@formbricks/ui/Button";
import { Checkbox } from "@formbricks/ui/Checkbox";

View File

@@ -10,7 +10,7 @@ import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationSlack, TIntegrationSlackConfigData } from "@formbricks/types/integration/slack";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ConnectIntegration } from "@formbricks/ui/ConnectIntegration";
interface SlackWrapperProps {

View File

@@ -3,7 +3,7 @@
import { Webhook } from "lucide-react";
import { useState } from "react";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { AddWebhookModal } from "./AddWebhookModal";

View File

@@ -8,7 +8,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { TPipelineTrigger } from "@formbricks/types/pipelines";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhookInput } from "@formbricks/types/webhooks";
import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";

View File

@@ -1,5 +1,5 @@
import React from "react";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Checkbox } from "@formbricks/ui/Checkbox";
interface SurveyCheckboxGroupProps {

View File

@@ -1,7 +1,7 @@
import { WebhookOverviewTab } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookOverviewTab";
import { WebhookSettingsTab } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookSettingsTab";
import { Webhook } from "lucide-react";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhook } from "@formbricks/types/webhooks";
import { ModalWithTabs } from "@formbricks/ui/ModalWithTabs";

View File

@@ -1,6 +1,6 @@
import { convertDateTimeStringShort } from "@formbricks/lib/time";
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhook } from "@formbricks/types/webhooks";
import { Label } from "@formbricks/ui/Label";

View File

@@ -1,6 +1,6 @@
import { timeSinceConditionally } from "@formbricks/lib/time";
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhook } from "@formbricks/types/webhooks";
import { Badge } from "@formbricks/ui/Badge";

View File

@@ -10,7 +10,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { TPipelineTrigger } from "@formbricks/types/pipelines";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhook, TWebhookInput } from "@formbricks/types/webhooks";
import { Button } from "@formbricks/ui/Button";
import { DeleteDialog } from "@formbricks/ui/DeleteDialog";

View File

@@ -3,7 +3,7 @@
import { WebhookModal } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookDetailModal";
import { useState } from "react";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TWebhook } from "@formbricks/types/webhooks";
import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller";

View File

@@ -12,7 +12,7 @@ import { SubmitHandler, UseFormReturn, useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { COLOR_DEFAULTS, PREVIEW_SURVEY } from "@formbricks/lib/styling/constants";
import { TProduct, TProductStyling, ZProductStyling } from "@formbricks/types/product";
import { TSurvey, TSurveyStyling, TSurveyType } from "@formbricks/types/surveys";
import { TSurvey, TSurveyStyling, TSurveyType } from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/AlertDialog";
import { Button } from "@formbricks/ui/Button";
import {

View File

@@ -3,7 +3,7 @@
import { Variants, motion } from "framer-motion";
import { useRef, useState } from "react";
import type { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
import { ClientLogo } from "@formbricks/ui/ClientLogo";
import { MediaBackground } from "@formbricks/ui/MediaBackground";
import { Modal } from "@formbricks/ui/PreviewSurvey/components/Modal";

View File

@@ -7,7 +7,7 @@ import { getResponseCountBySurveyId, getResponses, getSurveySummary } from "@for
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { AuthorizationError } from "@formbricks/types/errors";
import { TResponse, TResponseFilterCriteria } from "@formbricks/types/responses";
import { TSurveySummary } from "@formbricks/types/surveys";
import { TSurveySummary } from "@formbricks/types/surveys/types";
export const revalidateSurveyIdPath = async (environmentId: string, surveyId: string) => {
revalidatePath(`/environments/${environmentId}/surveys/${surveyId}`);

View File

@@ -17,7 +17,7 @@ import { useParams, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { TEnvironment } from "@formbricks/types/environment";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { TUser } from "@formbricks/types/user";

View File

@@ -6,7 +6,7 @@ import { getMembershipByUserIdOrganizationIdAction } from "@formbricks/lib/membe
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { TEnvironment } from "@formbricks/types/environment";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { TUser } from "@formbricks/types/user";
import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller";

View File

@@ -2,7 +2,7 @@ import Link from "next/link";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { timeSince } from "@formbricks/lib/time";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryAddress } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryAddress } from "@formbricks/types/surveys/types";
import { AddressResponse } from "@formbricks/ui/AddressResponse";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -1,6 +1,6 @@
import { InboxIcon } from "lucide-react";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryCta } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryCta } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@formbricks/ui/ProgressBar";
import { convertFloatToNDecimal } from "../lib/utils";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -1,6 +1,6 @@
import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryCal } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryCal } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@formbricks/ui/ProgressBar";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -1,5 +1,5 @@
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryConsent } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryConsent } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@formbricks/ui/ProgressBar";
import { convertFloatToNDecimal } from "../lib/utils";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -4,7 +4,7 @@ import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { timeSince } from "@formbricks/lib/time";
import { formatDateWithOrdinal } from "@formbricks/lib/utils/datetime";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryDate } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryDate } from "@formbricks/types/surveys/types";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -5,7 +5,7 @@ import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { getOriginalFileNameFromUrl } from "@formbricks/lib/storage/utils";
import { timeSince } from "@formbricks/lib/time";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryFileUpload } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryFileUpload } from "@formbricks/types/surveys/types";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -3,7 +3,7 @@ import { useState } from "react";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { timeSince } from "@formbricks/lib/time";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurveyQuestionSummaryHiddenFields } from "@formbricks/types/surveys";
import { TSurveyQuestionSummaryHiddenFields } from "@formbricks/types/surveys/types";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";

View File

@@ -1,5 +1,5 @@
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryMatrix } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryMatrix } from "@formbricks/types/surveys/types";
import { TooltipRenderer } from "@formbricks/ui/Tooltip";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -2,7 +2,7 @@ import Link from "next/link";
import { useState } from "react";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryMultipleChoice, TSurveyType } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryMultipleChoice, TSurveyType } from "@formbricks/types/surveys/types";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";
import { ProgressBar } from "@formbricks/ui/ProgressBar";

View File

@@ -1,5 +1,5 @@
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryNps } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryNps } from "@formbricks/types/surveys/types";
import { HalfCircle, ProgressBar } from "@formbricks/ui/ProgressBar";
import { convertFloatToNDecimal } from "../lib/utils";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -3,7 +3,7 @@ import { useState } from "react";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { timeSince } from "@formbricks/lib/time";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
import { PersonAvatar } from "@formbricks/ui/Avatars";
import { Button } from "@formbricks/ui/Button";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -1,6 +1,6 @@
import Image from "next/image";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryPictureSelection } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryPictureSelection } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@formbricks/ui/ProgressBar";
import { convertFloatToNDecimal } from "../lib/utils";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -2,7 +2,7 @@ import { questionTypes } from "@/app/lib/questions";
import { InboxIcon } from "lucide-react";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummary } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummary } from "@formbricks/types/surveys/types";
interface HeadProps {
questionSummary: TSurveyQuestionSummary;

View File

@@ -2,7 +2,7 @@ import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]
import { CircleSlash2, SmileIcon, StarIcon } from "lucide-react";
import { useMemo } from "react";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TSurvey, TSurveyQuestionSummaryRating } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionSummaryRating } from "@formbricks/types/surveys/types";
import { ProgressBar } from "@formbricks/ui/ProgressBar";
import { RatingResponse } from "@formbricks/ui/RatingResponse";
import { QuestionSummaryHeader } from "./QuestionSummaryHeader";

View File

@@ -4,7 +4,7 @@ import { BellRing, BlocksIcon, Code2Icon, LinkIcon, MailIcon, UsersRound } from
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
import { Badge } from "@formbricks/ui/Badge";
import { Dialog, DialogContent } from "@formbricks/ui/Dialog";

View File

@@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
import { Confetti } from "@formbricks/ui/Confetti";
import { ShareEmbedSurvey } from "./ShareEmbedSurvey";

View File

@@ -1,5 +1,5 @@
import { TimerIcon } from "lucide-react";
import { TSurveySummary } from "@formbricks/types/surveys";
import { TSurveySummary } from "@formbricks/types/surveys/types";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";
interface SummaryDropOffsProps {

View File

@@ -13,9 +13,9 @@ import { PictureChoiceSummary } from "@/app/(app)/environments/[environmentId]/s
import { RatingSummary } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurveySummary } from "@formbricks/types/surveys";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurveySummary } from "@formbricks/types/surveys/types";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TSurvey } from "@formbricks/types/surveys/types";
import { EmptySpaceFiller } from "@formbricks/ui/EmptySpaceFiller";
import { SkeletonLoader } from "@formbricks/ui/SkeletonLoader";
import { AddressSummary } from "./AddressSummary";

View File

@@ -1,5 +1,5 @@
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { TSurveySummary } from "@formbricks/types/surveys";
import { TSurveySummary } from "@formbricks/types/surveys/types";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";
interface SummaryMetadataProps {

View File

@@ -18,7 +18,7 @@ import { useEffect, useMemo, useState } from "react";
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey, TSurveySummary } from "@formbricks/types/surveys";
import { TSurvey, TSurveySummary } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
import { SummaryList } from "./SummaryList";
import { SummaryMetadata } from "./SummaryMetadata";

View File

@@ -6,7 +6,7 @@ import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/s
import { ShareIcon, SquarePenIcon } from "lucide-react";
import { useState } from "react";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
import { Badge } from "@formbricks/ui/Badge";
import { Button } from "@formbricks/ui/Button";

View File

@@ -1,6 +1,6 @@
import { UrlShortenerForm } from "@/app/(app)/environments/[environmentId]/components/UrlShortenerForm";
import Link from "next/link";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ShareSurveyLink } from "@formbricks/ui/ShareSurveyLink";
interface LinkTabProps {

View File

@@ -8,7 +8,7 @@ import { updateSurvey } from "@formbricks/lib/survey/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { TResponseFilterCriteria } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
export const getResponsesDownloadUrlAction = async (
surveyId: string,

View File

@@ -12,7 +12,7 @@ import { useParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Calendar } from "@formbricks/ui/Calendar";
import {
DropdownMenu,

View File

@@ -6,7 +6,7 @@ import { ChevronDown, ChevronUp, X } from "lucide-react";
import * as React from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from "@formbricks/ui/Command";
import {
DropdownMenu,

View File

@@ -24,7 +24,7 @@ import {
import * as React from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { useClickOutside } from "@formbricks/lib/utils/hooks/useClickOutside";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import {
Command,
CommandEmpty,

View File

@@ -14,7 +14,7 @@ import { TrashIcon } from "lucide-react";
import { ChevronDown, ChevronUp, Plus } from "lucide-react";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/Button";
import { Checkbox } from "@formbricks/ui/Checkbox";
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover";

View File

@@ -8,7 +8,7 @@ import {
import { CopyIcon, DownloadIcon, GlobeIcon, LinkIcon } from "lucide-react";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TUser } from "@formbricks/types/user";
import {
DropdownMenu,

View File

@@ -1,7 +1,7 @@
import { CheckCircle2Icon, PauseCircleIcon, PlayCircleIcon } from "lucide-react";
import toast from "react-hot-toast";
import { TEnvironment } from "@formbricks/types/environment";
import { TSurvey } from "@formbricks/types/surveys";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select";
import { SurveyStatusIndicator } from "@formbricks/ui/SurveyStatusIndicator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";

View File

@@ -11,7 +11,7 @@ import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/ty
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
import { TPipelineInput } from "@formbricks/types/pipelines";
import { TResponseMeta } from "@formbricks/types/responses";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
const convertMetaObjectToString = (metadata: TResponseMeta): string => {
let result: string[] = [];

Some files were not shown because too many files have changed in this diff Show More