diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx
index 6b934fa450..490060e162 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConditionalLogic.tsx
@@ -13,7 +13,7 @@ import {
SplitIcon,
TrashIcon,
} from "lucide-react";
-import { useMemo } from "react";
+import { useEffect, useMemo, useState } from "react";
import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
@@ -42,6 +42,14 @@ export function ConditionalLogic({
questionIdx,
updateQuestion,
}: ConditionalLogicProps) {
+ const [questionLogic, setQuestionLogic] = useState(question.logic);
+
+ useEffect(() => {
+ updateQuestion(questionIdx, {
+ logic: questionLogic,
+ });
+ }, [questionLogic]);
+
const transformedSurvey = useMemo(() => {
let modifiedSurvey = replaceHeadlineRecall(localSurvey, "default", attributeClasses);
modifiedSurvey = replaceEndingCardHeadlineRecall(modifiedSurvey, "default", attributeClasses);
@@ -49,6 +57,10 @@ export function ConditionalLogic({
return modifiedSurvey;
}, [localSurvey, attributeClasses]);
+ const updateQuestionLogic = (_questionIdx: number, updatedAttributes: any) => {
+ setQuestionLogic(updatedAttributes.logic);
+ };
+
const addLogic = () => {
const operator = getDefaultOperatorForQuestion(question);
@@ -77,37 +89,37 @@ export function ConditionalLogic({
],
};
- updateQuestion(questionIdx, {
+ updateQuestionLogic(questionIdx, {
logic: [...(question?.logic ?? []), initialCondition],
});
};
const handleRemoveLogic = (logicItemIdx: number) => {
- const logicCopy = structuredClone(question.logic ?? []);
+ const logicCopy = structuredClone(questionLogic ?? []);
logicCopy.splice(logicItemIdx, 1);
- updateQuestion(questionIdx, {
+ updateQuestionLogic(questionIdx, {
logic: logicCopy,
});
};
const moveLogic = (from: number, to: number) => {
- const logicCopy = structuredClone(question.logic ?? []);
+ const logicCopy = structuredClone(questionLogic ?? []);
const [movedItem] = logicCopy.splice(from, 1);
logicCopy.splice(to, 0, movedItem);
- updateQuestion(questionIdx, {
+ updateQuestionLogic(questionIdx, {
logic: logicCopy,
});
};
const duplicateLogic = (logicItemIdx: number) => {
- const logicCopy = structuredClone(question.logic ?? []);
+ const logicCopy = structuredClone(questionLogic ?? []);
const logicItem = logicCopy[logicItemIdx];
const newLogicItem = duplicateLogicItem(logicItem);
logicCopy.splice(logicItemIdx + 1, 0, newLogicItem);
- updateQuestion(questionIdx, {
+ updateQuestionLogic(questionIdx, {
logic: logicCopy,
});
};
@@ -119,20 +131,20 @@ export function ConditionalLogic({
- {question.logic && question.logic.length > 0 && (
+ {questionLogic && questionLogic.length > 0 && (
- {question.logic.map((logicItem, logicItemIdx) => (
+ {questionLogic.map((logicItem, logicItemIdx) => (
@@ -159,7 +171,7 @@ export function ConditionalLogic({
{
moveLogic(logicItemIdx, logicItemIdx + 1);
}}>
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorAction.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorAction.tsx
new file mode 100644
index 0000000000..d5c273a69d
--- /dev/null
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorAction.tsx
@@ -0,0 +1,241 @@
+import { actionObjectiveOptions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
+import { CopyIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
+import React, { useEffect, useMemo } from "react";
+import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
+import { questionIconMapping } from "@formbricks/lib/utils/questions";
+import {
+ TActionNumberVariableCalculateOperator,
+ TActionObjective,
+ TActionTextVariableCalculateOperator,
+ TActionVariableValueType,
+ TSurvey,
+ TSurveyLogicAction,
+ TSurveyQuestion,
+} from "@formbricks/types/surveys/types";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@formbricks/ui/DropdownMenu";
+import { InputCombobox, TComboboxOption } from "@formbricks/ui/InputCombobox";
+
+interface LogicEditorActionProps {
+ action: TSurveyLogicAction;
+ actionIdx: number;
+ handleObjectiveChange: (actionIdx: number, val: TActionObjective) => void;
+ handleValuesChange: (actionIdx: number, values: any) => void;
+ handleActionsChange: (operation: "remove" | "addBelow" | "duplicate", actionIdx: number) => void;
+ isRemoveDisabled: boolean;
+ filteredQuestions: TSurveyQuestion[];
+ endings: TSurvey["endings"];
+}
+
+const _LogicEditorAction = ({
+ action,
+ actionIdx,
+ handleActionsChange,
+ handleObjectiveChange,
+ handleValuesChange,
+ isRemoveDisabled,
+ filteredQuestions,
+ endings,
+}: LogicEditorActionProps) => {
+ useEffect(() => {
+ console.log("action changed");
+ }, [action]);
+ useEffect(() => {
+ console.log("filteredQuestions changed");
+ }, [filteredQuestions]);
+ useEffect(() => {
+ console.log("endings changed");
+ }, [endings]);
+ useEffect(() => {
+ console.log("isRemoveDisabled changed");
+ }, [isRemoveDisabled]);
+ useEffect(() => {
+ console.log("actionIdx changed");
+ }, [actionIdx]);
+ useEffect(() => {
+ console.log("handleActionsChange changed");
+ }, [handleActionsChange]);
+ useEffect(() => {
+ console.log("handleObjectiveChange changed");
+ }, [handleObjectiveChange]);
+ useEffect(() => {
+ console.log("handleValuesChange changed");
+ }, [handleValuesChange]);
+
+ const actionTargetOptions = useMemo((): TComboboxOption[] => {
+ // let questions = localSurvey.questions.filter((_, idx) => idx !== questionIdx);
+ let questions = [...filteredQuestions];
+
+ if (action.objective === "requireAnswer") {
+ questions = questions.filter((question) => !question.required);
+ }
+
+ const questionOptions = questions.map((question) => {
+ return {
+ icon: questionIconMapping[question.type],
+ label: getLocalizedValue(question.headline, "default"),
+ value: question.id,
+ };
+ });
+
+ if (action.objective === "requireAnswer") return questionOptions;
+
+ const endingCardOptions = endings.map((ending) => {
+ return {
+ label:
+ ending.type === "endScreen"
+ ? getLocalizedValue(ending.headline, "default") || "End Screen"
+ : ending.label || "Redirect Thank you card",
+ value: ending.id,
+ };
+ });
+
+ return [...questionOptions, ...endingCardOptions];
+ }, [action.objective, JSON.stringify(filteredQuestions), endings]);
+
+ return (
+
+
{actionIdx === 0 ? "Then" : "and"}
+
+ {
+ handleObjectiveChange(actionIdx, val);
+ }}
+ comboboxClasses="grow"
+ />
+ {action.objective !== "calculate" && (
+ {
+ handleValuesChange(actionIdx, {
+ target: val,
+ });
+ }}
+ comboboxClasses="grow"
+ />
+ )}
+ {/* {action.objective === "calculate" && (
+ <>
+ {
+ handleValuesChange(actionIdx, {
+ variableId: val,
+ value: {
+ type: "static",
+ value: "",
+ },
+ });
+ }}
+ comboboxClasses="grow"
+ emptyDropdownText="Add a variable to calculate"
+ />
+ v.id === action.variableId)?.type
+ )}
+ value={action.operator}
+ onChangeValue={(
+ val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator
+ ) => {
+ handleValuesChange(actionIdx, {
+ operator: val,
+ });
+ }}
+ comboboxClasses="grow"
+ />
+ v.id === action.variableId)?.type || "text",
+ }}
+ groupedOptions={getActionValueOptions(action.variableId, localSurvey)}
+ onChangeValue={(val, option, fromInput) => {
+ const fieldType = option?.meta?.type as TActionVariableValueType;
+
+ if (!fromInput && fieldType !== "static") {
+ handleValuesChange(actionIdx, {
+ value: {
+ type: fieldType,
+ value: val as string,
+ },
+ });
+ } else if (fromInput) {
+ handleValuesChange(actionIdx, {
+ value: {
+ type: "static",
+ value: val as string,
+ },
+ });
+ }
+ }}
+ comboboxClasses="grow shrink-0"
+ />
+ >
+ )} */}
+
+
+
+
+
+
+
+ {
+ handleActionsChange("addBelow", actionIdx);
+ }}>
+
+ Add action below
+
+
+ {
+ handleActionsChange("remove", actionIdx);
+ }}>
+
+ Remove
+
+
+ {
+ handleActionsChange("duplicate", actionIdx);
+ }}>
+
+ Duplicate
+
+
+
+
+ );
+};
+
+export const LogicEditorAction = React.memo(_LogicEditorAction);
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions.tsx
index e33dc22b81..eaa2b6b5fd 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions.tsx
@@ -1,13 +1,16 @@
+import { LogicEditorAction } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorAction";
import {
actionObjectiveOptions,
- getActionOperatorOptions,
- getActionTargetOptions,
+ getActionOperatorOptions, // getActionTargetOptions,
getActionValueOptions,
getActionVariableOptions,
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { createId } from "@paralleldrive/cuid2";
import { CopyIcon, CornerDownRightIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
+import React, { useCallback, useEffect, useMemo, useRef } from "react";
+import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { getUpdatedActionBody } from "@formbricks/lib/surveyLogic/utils";
+import { questionIconMapping } from "@formbricks/lib/utils/questions";
import {
TActionNumberVariableCalculateOperator,
TActionObjective,
@@ -24,7 +27,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@formbricks/ui/DropdownMenu";
-import { InputCombobox } from "@formbricks/ui/InputCombobox";
+import { InputCombobox, TComboboxOption } from "@formbricks/ui/InputCombobox";
interface LogicEditorActions {
localSurvey: TSurvey;
@@ -35,202 +38,129 @@ interface LogicEditorActions {
questionIdx: number;
}
-export function LogicEditorActions({
+export const LogicEditorActions = ({
localSurvey,
logicItem,
logicIdx,
question,
updateQuestion,
questionIdx,
-}: LogicEditorActions) {
+}: LogicEditorActions) => {
const actions = logicItem.actions;
- const handleActionsChange = (
- operation: "remove" | "addBelow" | "duplicate" | "update",
- actionIdx: number,
- action?: TSurveyLogicAction
- ) => {
- const logicCopy = structuredClone(question.logic) ?? [];
- const currentLogicItem = logicCopy[logicIdx];
- const actionsClone = currentLogicItem.actions;
+ const handleActionsChange = useCallback(
+ (
+ operation: "remove" | "addBelow" | "duplicate" | "update",
+ actionIdx: number,
+ action?: TSurveyLogicAction
+ ) => {
+ const logicCopy = structuredClone(question.logic) ?? [];
+ const currentLogicItem = logicCopy[logicIdx];
+ const actionsClone = currentLogicItem.actions;
- switch (operation) {
- case "remove":
- actionsClone.splice(actionIdx, 1);
- break;
- case "addBelow":
- actionsClone.splice(actionIdx + 1, 0, { id: createId(), objective: "jumpToQuestion", target: "" });
- break;
- case "duplicate":
- actionsClone.splice(actionIdx + 1, 0, { ...actionsClone[actionIdx], id: createId() });
- break;
- case "update":
- if (!action) return;
- actionsClone[actionIdx] = action;
- break;
- }
+ switch (operation) {
+ case "remove":
+ actionsClone.splice(actionIdx, 1);
+ break;
+ case "addBelow":
+ actionsClone.splice(actionIdx + 1, 0, {
+ id: createId(),
+ objective: "jumpToQuestion",
+ target: "",
+ });
+ break;
+ case "duplicate":
+ actionsClone.splice(actionIdx + 1, 0, { ...actionsClone[actionIdx], id: createId() });
+ break;
+ case "update":
+ if (!action) return;
+ actionsClone[actionIdx] = action;
+ break;
+ }
- updateQuestion(questionIdx, {
- logic: logicCopy,
- });
- };
+ updateQuestion(questionIdx, {
+ logic: logicCopy,
+ });
+ },
+ [logicIdx, question.logic, questionIdx]
+ );
- const handleObjectiveChange = (actionIdx: number, objective: TActionObjective) => {
- const action = actions[actionIdx];
- const actionBody = getUpdatedActionBody(action, objective);
- handleActionsChange("update", actionIdx, actionBody);
- };
+ const handleObjectiveChange = useCallback(
+ (actionIdx: number, objective: TActionObjective) => {
+ const action = actions[actionIdx];
+ const actionBody = getUpdatedActionBody(action, objective);
+ handleActionsChange("update", actionIdx, actionBody);
+ },
+ [actions]
+ );
- const handleValuesChange = (actionIdx: number, values: Partial) => {
- const action = actions[actionIdx];
- const actionBody = { ...action, ...values } as TSurveyLogicAction;
- handleActionsChange("update", actionIdx, actionBody);
- };
+ const handleValuesChange = useCallback(
+ (actionIdx: number, values: Partial) => {
+ const action = actions[actionIdx];
+ const actionBody = { ...action, ...values } as TSurveyLogicAction;
+ handleActionsChange("update", actionIdx, actionBody);
+ },
+ [actions]
+ );
+
+ const filteredQuestions = useMemo(
+ () => localSurvey.questions.filter((_, idx) => idx !== questionIdx),
+ [localSurvey.questions, questionIdx]
+ );
+
+ const endings = useMemo(() => localSurvey.endings, [JSON.stringify(localSurvey.endings)]);
return (
{actions?.map((action, idx) => (
-
-
{idx === 0 ? "Then" : "and"}
-
- {
- handleObjectiveChange(idx, val);
- }}
- comboboxClasses="grow"
- />
- {action.objective !== "calculate" && (
- {
- handleValuesChange(idx, {
- target: val,
- });
- }}
- comboboxClasses="grow"
- />
- )}
- {action.objective === "calculate" && (
- <>
- {
- handleValuesChange(idx, {
- variableId: val,
- value: {
- type: "static",
- value: "",
- },
- });
- }}
- comboboxClasses="grow"
- emptyDropdownText="Add a variable to calculate"
- />
- v.id === action.variableId)?.type
- )}
- value={action.operator}
- onChangeValue={(
- val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator
- ) => {
- handleValuesChange(idx, {
- operator: val,
- });
- }}
- comboboxClasses="grow"
- />
- v.id === action.variableId)?.type || "text",
- }}
- groupedOptions={getActionValueOptions(action.variableId, localSurvey)}
- onChangeValue={(val, option, fromInput) => {
- const fieldType = option?.meta?.type as TActionVariableValueType;
-
- if (!fromInput && fieldType !== "static") {
- handleValuesChange(idx, {
- value: {
- type: fieldType,
- value: val as string,
- },
- });
- } else if (fromInput) {
- handleValuesChange(idx, {
- value: {
- type: "static",
- value: val as string,
- },
- });
- }
- }}
- comboboxClasses="grow shrink-0"
- />
- >
- )}
-
-
-
-
-
-
-
- {
- handleActionsChange("addBelow", idx);
- }}>
-
- Add action below
-
-
- {
- handleActionsChange("remove", idx);
- }}>
-
- Remove
-
-
- {
- handleActionsChange("duplicate", idx);
- }}>
-
- Duplicate
-
-
-
-
+
))}
);
-}
+};
+
+// a code snippet living in a component
+// source: https://stackoverflow.com/a/59843241/3600510
+const usePrevious = (value, initialValue) => {
+ const ref = useRef(initialValue);
+ useEffect(() => {
+ ref.current = value;
+ });
+ return ref.current;
+};
+const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
+ const previousDeps = usePrevious(dependencies, []);
+
+ const changedDeps = dependencies.reduce((accum, dependency, index) => {
+ if (dependency !== previousDeps[index]) {
+ const keyName = dependencyNames[index] || index;
+ return {
+ ...accum,
+ [keyName]: {
+ before: previousDeps[index],
+ after: dependency,
+ },
+ };
+ }
+
+ return accum;
+ }, {});
+
+ if (Object.keys(changedDeps).length) {
+ console.log("[use-effect-debugger] ", changedDeps);
+ }
+
+ useEffect(effectHook, dependencies);
+};
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx
index ef7a0d9c37..1b5f36ad65 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions.tsx
@@ -258,7 +258,7 @@ export function LogicEditorConditions({
)}
-
- )}
+ )} */}
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
index 5e3bdb3d7f..a939af2475 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
@@ -264,6 +264,7 @@ export const QuestionsView = ({
}
}
});
+
setLocalSurvey(updatedSurvey);
validateSurveyQuestion(updatedSurvey.questions[questionIdx]);
};
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx
index a91f3cda24..63757f475d 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils.tsx
@@ -1,8 +1,8 @@
import { EyeOffIcon, FileDigitIcon, FileType2Icon } from "lucide-react";
-import { HTMLInputTypeAttribute } from "react";
+import { HTMLInputTypeAttribute, useMemo } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { isConditionGroup } from "@formbricks/lib/surveyLogic/utils";
-import { questionTypes } from "@formbricks/lib/utils/questions";
+import { questionIconMapping, questionTypes } from "@formbricks/lib/utils/questions";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import {
@@ -40,14 +40,6 @@ export const formatTextWithSlashes = (text: string) => {
});
};
-const questionIconMapping = questionTypes.reduce(
- (prev, curr) => ({
- ...prev,
- [curr.id]: curr.icon,
- }),
- {}
-);
-
export const getConditionValueOptions = (
localSurvey: TSurvey,
currQuestionIdx: number
@@ -756,40 +748,6 @@ export const getMatchValueProps = (
return { show: false, options: [] };
};
-export const getActionTargetOptions = (
- action: TSurveyLogicAction,
- localSurvey: TSurvey,
- currQuestionIdx: number
-): TComboboxOption[] => {
- let questions = localSurvey.questions.filter((_, idx) => idx !== currQuestionIdx);
-
- if (action.objective === "requireAnswer") {
- questions = questions.filter((question) => !question.required);
- }
-
- const questionOptions = questions.map((question) => {
- return {
- icon: questionIconMapping[question.type],
- label: getLocalizedValue(question.headline, "default"),
- value: question.id,
- };
- });
-
- if (action.objective === "requireAnswer") return questionOptions;
-
- const endingCardOptions = localSurvey.endings.map((ending) => {
- return {
- label:
- ending.type === "endScreen"
- ? getLocalizedValue(ending.headline, "default") || "End Screen"
- : ending.label || "Redirect Thank you card",
- value: ending.id,
- };
- });
-
- return [...questionOptions, ...endingCardOptions];
-};
-
export const getActionVariableOptions = (localSurvey: TSurvey): TComboboxOption[] => {
const variables = localSurvey.variables ?? [];
diff --git a/packages/lib/utils/questions.tsx b/packages/lib/utils/questions.tsx
index 9d1c913d52..5809c7a54a 100644
--- a/packages/lib/utils/questions.tsx
+++ b/packages/lib/utils/questions.tsx
@@ -231,6 +231,14 @@ export const questionTypes: TQuestion[] = [
},
];
+export const questionIconMapping = questionTypes.reduce(
+ (prev, curr) => ({
+ ...prev,
+ [curr.id]: curr.icon,
+ }),
+ {}
+);
+
export const CXQuestionTypes = questionTypes.filter((questionType) => {
return [
TSurveyQuestionTypeEnum.OpenText,
diff --git a/packages/lib/utils/recall.ts b/packages/lib/utils/recall.ts
index 65423e68d6..03cb3a2e4e 100644
--- a/packages/lib/utils/recall.ts
+++ b/packages/lib/utils/recall.ts
@@ -12,7 +12,7 @@ import { getLocalizedValue } from "../i18n/utils";
import { structuredClone } from "../pollyfills/structuredClone";
import { formatDateWithOrdinal, isValidDateString } from "./datetime";
-export interface fallbacks {
+export interface TFallbackString {
[id: string]: string;
}
@@ -209,17 +209,19 @@ export const getRecallItems = (
};
// Constructs a fallbacks object from a text containing multiple recall and fallback patterns.
-export const getFallbackValues = (text: string): fallbacks => {
+export const getFallbackValues = (text: string): TFallbackString => {
if (!text.includes("#recall:")) return {};
- const pattern = /#recall:([A-Za-z0-9_-]+)\/fallback:([\S*]+)#/g;
- let match;
- const fallbacks: fallbacks = {};
- while ((match = pattern.exec(text)) !== null) {
+ const pattern = /#recall:([A-Za-z0-9_-]+)\/fallback:([\S*]+)#/g;
+ const fallbacks: TFallbackString = {};
+
+ let match = pattern.exec(text);
+ while (match !== null) {
const id = match[1];
const fallbackValue = match[2];
fallbacks[id] = fallbackValue;
}
+
return fallbacks;
};
@@ -227,7 +229,7 @@ export const getFallbackValues = (text: string): fallbacks => {
export const headlineToRecall = (
text: string,
recallItems: TSurveyRecallItem[],
- fallbacks: fallbacks
+ fallbacks: TFallbackString
): string => {
recallItems.forEach((recallItem) => {
const recallInfo = `#recall:${recallItem.id}/fallback:${fallbacks[recallItem.id]}#`;
diff --git a/packages/ui/InputCombobox/index.tsx b/packages/ui/InputCombobox/index.tsx
index 273791223e..10d19becb4 100644
--- a/packages/ui/InputCombobox/index.tsx
+++ b/packages/ui/InputCombobox/index.tsx
@@ -82,9 +82,28 @@ export const InputCombobox = ({
setInputValue(value || "");
}, [value]);
+ // useEffect(() => {
+ // console.log("changing the options");
+ // }, [options]);
+ // useEffect(() => {
+ // console.log("changing the groupedOptions");
+ // }, [groupedOptions]);
+ // useEffect(() => {
+ // console.log("changing the value");
+ // }, [value]);
+ // useEffect(() => {
+ // console.log("changing the inputType");
+ // }, [inputType]);
+ // useEffect(() => {
+ // console.log("changing the withInput");
+ // }, [withInput]);
+
useEffect(() => {
+ // console.log("running this useEffect");
const validOptions = options?.length ? options : groupedOptions?.flatMap((group) => group.options);
+ console.log({ options, groupedOptions, value, validOptions });
+
if (value === null || value === undefined) {
setLocalValue("");
setInputType(null);
@@ -158,11 +177,11 @@ export const InputCombobox = ({
if (value === "") {
setLocalValue("");
setInputValue("");
- if (!isE2E) {
- debouncedOnChangeValue("");
- } else {
- onChangeValue("", undefined, true);
- }
+ // if (!isE2E) {
+ // debouncedOnChangeValue("");
+ // } else {
+ onChangeValue("", undefined, true);
+ // }
}
if (inputType !== "input") {
@@ -177,11 +196,11 @@ export const InputCombobox = ({
// Trigger the debounced onChangeValue
- if (!isE2E) {
- debouncedOnChangeValue(val);
- } else {
- onChangeValue(val, undefined, true);
- }
+ // if (!isE2E) {
+ // debouncedOnChangeValue(val);
+ // } else {
+ onChangeValue(val, undefined, true);
+ // }
};
const getDisplayValue = useMemo(() => {
diff --git a/packages/ui/QuestionFormInput/index.tsx b/packages/ui/QuestionFormInput/index.tsx
index 2c84cb6f0b..274059bdda 100644
--- a/packages/ui/QuestionFormInput/index.tsx
+++ b/packages/ui/QuestionFormInput/index.tsx
@@ -120,7 +120,7 @@ export const QuestionFormInput = ({
[value, id, isInvalid, surveyLanguageCodes]
);
- const getElementTextBasedOnType = useCallback((): TI18nString => {
+ const elementText = useMemo((): TI18nString => {
if (isChoice && typeof index === "number") {
return getChoiceLabel(question, index, surveyLanguageCodes);
}
@@ -155,8 +155,7 @@ export const QuestionFormInput = ({
surveyLanguageCodes,
]);
- const [text, setText] = useState(getElementTextBasedOnType());
- // const [debouncedText, setDebouncedText] = useState(text); // Added debouncedText state
+ const [text, setText] = useState(elementText);
const [renderedText, setRenderedText] = useState();
const [showImageUploader, setShowImageUploader] = useState(
determineImageUploaderVisibility(questionIdx, localSurvey)
@@ -173,11 +172,11 @@ export const QuestionFormInput = ({
)
: []
);
- const [fallbacks, setFallbacks] = useState<{ [type: string]: string }>(
- getLocalizedValue(text, usedLanguageCode).includes("/fallback:")
- ? getFallbackValues(getLocalizedValue(text, usedLanguageCode))
- : {}
- );
+
+ const [fallbacks, setFallbacks] = useState<{ [type: string]: string }>(() => {
+ const localizedValue = getLocalizedValue(text, usedLanguageCode);
+ return localizedValue.includes("/fallback:") ? getFallbackValues(localizedValue) : {};
+ });
const highlightContainerRef = useRef(null);
const fallbackInputRef = useRef(null);
@@ -204,9 +203,9 @@ export const QuestionFormInput = ({
}, [usedLanguageCode]);
useEffect(() => {
- if (id === "headline" || id === "subheader") {
- checkForRecallSymbol();
- }
+ // if (id === "headline" || id === "subheader") {
+ // checkForRecallSymbol();
+ // }
// Generates an array of headlines from recallItems, replacing nested recall questions with '___' .
const recallItemLabels = recallItems.flatMap((recallItem) => {
if (!recallItem.label.includes("#recall:")) {
@@ -262,6 +261,7 @@ export const QuestionFormInput = ({
}
return parts;
};
+
setRenderedText(processInput());
}, [text, recallItems]);
@@ -271,100 +271,21 @@ export const QuestionFormInput = ({
}
}, [showFallbackInput]);
- useEffect(() => {
- setText(getElementTextBasedOnType());
- }, [localSurvey]);
+ // useEffect(() => {
+ // setText(getElementTextBasedOnType());
+ // }, [localSurvey]);
- const checkForRecallSymbol = () => {
- const pattern = /(^|\s)@(\s|$)/;
- if (pattern.test(getLocalizedValue(text, usedLanguageCode))) {
- setShowRecallItemSelect(true);
- } else {
- setShowRecallItemSelect(false);
- }
- };
-
- // Adds a new recall question to the recallItems array, updates fallbacks, modifies the text with recall details.
- const addRecallItem = (recallItem: TSurveyRecallItem) => {
- if (recallItem.label.trim() === "") {
- toast.error("Cannot add question with empty headline as recall");
- return;
- }
- let recallItemTemp = structuredClone(recallItem);
- recallItemTemp.label = replaceRecallInfoWithUnderline(recallItem.label);
- setRecallItems((prevQuestions) => {
- const updatedQuestions = [...prevQuestions, recallItemTemp];
- return updatedQuestions;
- });
- if (!Object.keys(fallbacks).includes(recallItem.id)) {
- setFallbacks((prevFallbacks) => ({
- ...prevFallbacks,
- [recallItem.id]: "",
- }));
- }
- setShowRecallItemSelect(false);
- let modifiedHeadlineWithId = { ...getElementTextBasedOnType() };
- modifiedHeadlineWithId[usedLanguageCode] = getLocalizedValue(
- modifiedHeadlineWithId,
- usedLanguageCode
- ).replace(/(?<=^|\s)@(?=\s|$)/g, `#recall:${recallItem.id}/fallback:# `);
- handleUpdate(getLocalizedValue(modifiedHeadlineWithId, usedLanguageCode));
- const modifiedHeadlineWithName = recallToHeadline(
- modifiedHeadlineWithId,
- localSurvey,
- false,
- usedLanguageCode,
- attributeClasses
- );
- setText(modifiedHeadlineWithName);
- setShowFallbackInput(true);
- };
-
- // Filters and updates the list of recall questions based on their presence in the given text, also managing related text and fallback states.
- const filterRecallItems = (remainingText: string) => {
- let includedRecallItems: TSurveyRecallItem[] = [];
- recallItems.forEach((recallItem) => {
- if (remainingText.includes(`@${recallItem.label}`)) {
- includedRecallItems.push(recallItem);
+ const checkForRecallSymbol = useCallback(
+ (value: TI18nString) => {
+ const pattern = /(^|\s)@(\s|$)/;
+ if (pattern.test(getLocalizedValue(value, usedLanguageCode))) {
+ setShowRecallItemSelect(true);
} else {
- const recallItemToRemove = recallItem.label.slice(0, -1);
- const newText = { ...text };
- newText[usedLanguageCode] = text[usedLanguageCode].replace(`@${recallItemToRemove}`, "");
- setText(newText);
- handleUpdate(text[usedLanguageCode].replace(`@${recallItemToRemove}`, ""));
- let updatedFallback = { ...fallbacks };
- delete updatedFallback[recallItem.id];
- setFallbacks(updatedFallback);
- setRecallItems(includedRecallItems);
+ setShowRecallItemSelect(false);
}
- });
- };
-
- const addFallback = () => {
- let headlineWithFallback = getElementTextBasedOnType();
- filteredRecallItems.forEach((recallQuestion) => {
- if (recallQuestion) {
- const recallInfo = findRecallInfoById(
- getLocalizedValue(headlineWithFallback, usedLanguageCode),
- recallQuestion!.id
- );
- if (recallInfo) {
- let fallBackValue = fallbacks[recallQuestion.id].trim();
- fallBackValue = fallBackValue.replace(/ /g, "nbsp");
- let updatedFallback = { ...fallbacks };
- updatedFallback[recallQuestion.id] = fallBackValue;
- setFallbacks(updatedFallback);
- headlineWithFallback[usedLanguageCode] = getLocalizedValue(
- headlineWithFallback,
- usedLanguageCode
- ).replace(recallInfo, `#recall:${recallQuestion?.id}/fallback:${fallBackValue}#`);
- handleUpdate(getLocalizedValue(headlineWithFallback, usedLanguageCode));
- }
- }
- });
- setShowFallbackInput(false);
- inputRef.current?.focus();
- };
+ },
+ [usedLanguageCode]
+ );
// updation of questions, WelcomeCard, ThankYouCard and choices is done in a different manner,
// questions -> updateQuestion
@@ -375,11 +296,11 @@ export const QuestionFormInput = ({
const createUpdatedText = useCallback(
(updatedText: string): TI18nString => {
return {
- ...getElementTextBasedOnType(),
+ ...elementText,
[usedLanguageCode]: updatedText,
};
},
- [getElementTextBasedOnType, usedLanguageCode]
+ [elementText, usedLanguageCode]
);
const updateChoiceDetails = useCallback(
@@ -446,6 +367,103 @@ export const QuestionFormInput = ({
]
);
+ // Adds a new recall question to the recallItems array, updates fallbacks, modifies the text with recall details.
+ const addRecallItem = useCallback(
+ (recallItem: TSurveyRecallItem) => {
+ if (recallItem.label.trim() === "") {
+ toast.error("Cannot add question with empty headline as recall");
+ return;
+ }
+
+ let recallItemTemp = structuredClone(recallItem);
+ recallItemTemp.label = replaceRecallInfoWithUnderline(recallItem.label);
+
+ setRecallItems((prevQuestions) => {
+ const updatedQuestions = [...prevQuestions, recallItemTemp];
+ return updatedQuestions;
+ });
+
+ if (!Object.keys(fallbacks).includes(recallItem.id)) {
+ setFallbacks((prevFallbacks) => ({
+ ...prevFallbacks,
+ [recallItem.id]: "",
+ }));
+ }
+
+ setShowRecallItemSelect(false);
+
+ let modifiedHeadlineWithId = { ...elementText };
+ modifiedHeadlineWithId[usedLanguageCode] = getLocalizedValue(
+ modifiedHeadlineWithId,
+ usedLanguageCode
+ ).replace(/(?<=^|\s)@(?=\s|$)/g, `#recall:${recallItem.id}/fallback:# `);
+
+ handleUpdate(getLocalizedValue(modifiedHeadlineWithId, usedLanguageCode));
+
+ const modifiedHeadlineWithName = recallToHeadline(
+ modifiedHeadlineWithId,
+ localSurvey,
+ false,
+ usedLanguageCode,
+ attributeClasses
+ );
+
+ setText(modifiedHeadlineWithName);
+ setShowFallbackInput(true);
+ },
+ [attributeClasses, elementText, fallbacks, handleUpdate, localSurvey, usedLanguageCode]
+ );
+
+ // Filters and updates the list of recall questions based on their presence in the given text, also managing related text and fallback states.
+ const filterRecallItems = useCallback(
+ (remainingText: string) => {
+ let includedRecallItems: TSurveyRecallItem[] = [];
+
+ recallItems.forEach((recallItem) => {
+ if (remainingText.includes(`@${recallItem.label}`)) {
+ includedRecallItems.push(recallItem);
+ } else {
+ const recallItemToRemove = recallItem.label.slice(0, -1);
+ const newText = { ...text };
+ newText[usedLanguageCode] = text[usedLanguageCode].replace(`@${recallItemToRemove}`, "");
+ setText(newText);
+ handleUpdate(text[usedLanguageCode].replace(`@${recallItemToRemove}`, ""));
+ let updatedFallback = { ...fallbacks };
+ delete updatedFallback[recallItem.id];
+ setFallbacks(updatedFallback);
+ setRecallItems(includedRecallItems);
+ }
+ });
+ },
+ [fallbacks, handleUpdate, recallItems, text, usedLanguageCode]
+ );
+
+ const addFallback = () => {
+ let headlineWithFallback = elementText;
+ filteredRecallItems.forEach((recallQuestion) => {
+ if (recallQuestion) {
+ const recallInfo = findRecallInfoById(
+ getLocalizedValue(headlineWithFallback, usedLanguageCode),
+ recallQuestion!.id
+ );
+ if (recallInfo) {
+ let fallBackValue = fallbacks[recallQuestion.id].trim();
+ fallBackValue = fallBackValue.replace(/ /g, "nbsp");
+ let updatedFallback = { ...fallbacks };
+ updatedFallback[recallQuestion.id] = fallBackValue;
+ setFallbacks(updatedFallback);
+ headlineWithFallback[usedLanguageCode] = getLocalizedValue(
+ headlineWithFallback,
+ usedLanguageCode
+ ).replace(recallInfo, `#recall:${recallQuestion?.id}/fallback:${fallBackValue}#`);
+ handleUpdate(getLocalizedValue(headlineWithFallback, usedLanguageCode));
+ }
+ }
+ });
+ setShowFallbackInput(false);
+ inputRef.current?.focus();
+ };
+
const getFileUrl = (): string | undefined => {
if (isWelcomeCard) return localSurvey.welcomeCard.fileUrl;
if (isEndingCard) {
@@ -470,16 +488,29 @@ export const QuestionFormInput = ({
const handleInputChange = (e: React.ChangeEvent) => {
const value = e.target.value;
const updatedText = {
- ...getElementTextBasedOnType(),
+ ...elementText,
[usedLanguageCode]: value,
};
- setText(recallToHeadline(updatedText, localSurvey, false, usedLanguageCode, attributeClasses));
- if (!isE2E) {
- debouncedHandleUpdate(value);
- } else {
- handleUpdate(headlineToRecall(value, recallItems, fallbacks));
+ const valueTI18nString = recallToHeadline(
+ updatedText,
+ localSurvey,
+ false,
+ usedLanguageCode,
+ attributeClasses
+ );
+
+ setText(valueTI18nString);
+
+ if (id === "headline" || id === "subheader") {
+ checkForRecallSymbol(valueTI18nString);
}
+
+ // if (!isE2E) {
+ // debouncedHandleUpdate(value);
+ // } else {
+ handleUpdate(headlineToRecall(value, recallItems, fallbacks));
+ // }
};
return (
@@ -525,7 +556,7 @@ export const QuestionFormInput = ({
dir="auto">
{renderedText}
- {getLocalizedValue(getElementTextBasedOnType(), usedLanguageCode).includes("recall:") && (
+ {getLocalizedValue(elementText, usedLanguageCode).includes("recall:") && (