mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 10:30:00 -06:00
fix: testing perf
This commit is contained in:
@@ -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({
|
||||
<SplitIcon className="h-4 w-4 rotate-90" />
|
||||
</Label>
|
||||
|
||||
{question.logic && question.logic.length > 0 && (
|
||||
{questionLogic && questionLogic.length > 0 && (
|
||||
<div className="mt-2 flex flex-col gap-4">
|
||||
{question.logic.map((logicItem, logicItemIdx) => (
|
||||
{questionLogic.map((logicItem, logicItemIdx) => (
|
||||
<div
|
||||
key={logicItem.id}
|
||||
className="flex w-full grow items-start gap-2 rounded-lg border border-slate-200 bg-slate-50 p-4">
|
||||
<LogicEditor
|
||||
localSurvey={transformedSurvey}
|
||||
logicItem={logicItem}
|
||||
updateQuestion={updateQuestion}
|
||||
updateQuestion={updateQuestionLogic}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
logicIdx={logicItemIdx}
|
||||
isLast={logicItemIdx === (question.logic ?? []).length - 1}
|
||||
isLast={logicItemIdx === (questionLogic ?? []).length - 1}
|
||||
/>
|
||||
|
||||
<DropdownMenu>
|
||||
@@ -159,7 +171,7 @@ export function ConditionalLogic({
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
disabled={logicItemIdx === (question.logic ?? []).length - 1}
|
||||
disabled={logicItemIdx === (questionLogic ?? []).length - 1}
|
||||
onClick={() => {
|
||||
moveLogic(logicItemIdx, logicItemIdx + 1);
|
||||
}}>
|
||||
|
||||
@@ -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 (
|
||||
<div key={action.id} className="flex grow items-center justify-between gap-x-2">
|
||||
<div className="block w-9 shrink-0">{actionIdx === 0 ? "Then" : "and"}</div>
|
||||
<div className="flex grow items-center gap-x-2">
|
||||
<InputCombobox
|
||||
id={`action-${actionIdx}-objective`}
|
||||
key={`objective-${action.id}`}
|
||||
showSearch={false}
|
||||
options={actionObjectiveOptions}
|
||||
value={action.objective}
|
||||
onChangeValue={(val: TActionObjective) => {
|
||||
handleObjectiveChange(actionIdx, val);
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
{action.objective !== "calculate" && (
|
||||
<InputCombobox
|
||||
id={`action-${actionIdx}-target`}
|
||||
key={`target-${action.id}`}
|
||||
showSearch={false}
|
||||
options={actionTargetOptions}
|
||||
value={action.target}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(actionIdx, {
|
||||
target: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
)}
|
||||
{/* {action.objective === "calculate" && (
|
||||
<>
|
||||
<InputCombobox
|
||||
id={`action-${actionIdx}-variableId`}
|
||||
key={`variableId-${action.id}`}
|
||||
showSearch={false}
|
||||
options={getActionVariableOptions(localSurvey)}
|
||||
value={action.variableId}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(actionIdx, {
|
||||
variableId: val,
|
||||
value: {
|
||||
type: "static",
|
||||
value: "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
emptyDropdownText="Add a variable to calculate"
|
||||
/>
|
||||
<InputCombobox
|
||||
id={`action-${actionIdx}-operator`}
|
||||
key={`operator-${action.id}`}
|
||||
showSearch={false}
|
||||
options={getActionOperatorOptions(
|
||||
localSurvey.variables.find((v) => v.id === action.variableId)?.type
|
||||
)}
|
||||
value={action.operator}
|
||||
onChangeValue={(
|
||||
val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator
|
||||
) => {
|
||||
handleValuesChange(actionIdx, {
|
||||
operator: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
<InputCombobox
|
||||
id={`action-${actionIdx}-value`}
|
||||
key={`value-${action.id}`}
|
||||
withInput={true}
|
||||
clearable={true}
|
||||
value={action.value?.value ?? ""}
|
||||
inputProps={{
|
||||
placeholder: "Value",
|
||||
type: localSurvey.variables.find((v) => 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"
|
||||
/>
|
||||
</>
|
||||
)} */}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger id={`actions-${actionIdx}-dropdown`}>
|
||||
<EllipsisVerticalIcon className="h-4 w-4 text-slate-700 hover:text-slate-950" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
handleActionsChange("addBelow", actionIdx);
|
||||
}}>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add action below
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
disabled={isRemoveDisabled}
|
||||
onClick={() => {
|
||||
handleActionsChange("remove", actionIdx);
|
||||
}}>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
handleActionsChange("duplicate", actionIdx);
|
||||
}}>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
Duplicate
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogicEditorAction = React.memo(_LogicEditorAction);
|
||||
@@ -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<TSurveyLogicAction>) => {
|
||||
const action = actions[actionIdx];
|
||||
const actionBody = { ...action, ...values } as TSurveyLogicAction;
|
||||
handleActionsChange("update", actionIdx, actionBody);
|
||||
};
|
||||
const handleValuesChange = useCallback(
|
||||
(actionIdx: number, values: Partial<TSurveyLogicAction>) => {
|
||||
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 (
|
||||
<div className="flex grow gap-2">
|
||||
<CornerDownRightIcon className="mt-3 h-4 w-4 shrink-0" />
|
||||
<div className="flex grow flex-col gap-y-2">
|
||||
{actions?.map((action, idx) => (
|
||||
<div key={action.id} className="flex grow items-center justify-between gap-x-2">
|
||||
<div className="block w-9 shrink-0">{idx === 0 ? "Then" : "and"}</div>
|
||||
<div className="flex grow items-center gap-x-2">
|
||||
<InputCombobox
|
||||
id={`action-${idx}-objective`}
|
||||
key={`objective-${action.id}`}
|
||||
showSearch={false}
|
||||
options={actionObjectiveOptions}
|
||||
value={action.objective}
|
||||
onChangeValue={(val: TActionObjective) => {
|
||||
handleObjectiveChange(idx, val);
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
{action.objective !== "calculate" && (
|
||||
<InputCombobox
|
||||
id={`action-${idx}-target`}
|
||||
key={`target-${action.id}`}
|
||||
showSearch={false}
|
||||
options={getActionTargetOptions(action, localSurvey, questionIdx)}
|
||||
value={action.target}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(idx, {
|
||||
target: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
)}
|
||||
{action.objective === "calculate" && (
|
||||
<>
|
||||
<InputCombobox
|
||||
id={`action-${idx}-variableId`}
|
||||
key={`variableId-${action.id}`}
|
||||
showSearch={false}
|
||||
options={getActionVariableOptions(localSurvey)}
|
||||
value={action.variableId}
|
||||
onChangeValue={(val: string) => {
|
||||
handleValuesChange(idx, {
|
||||
variableId: val,
|
||||
value: {
|
||||
type: "static",
|
||||
value: "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
emptyDropdownText="Add a variable to calculate"
|
||||
/>
|
||||
<InputCombobox
|
||||
id={`action-${idx}-operator`}
|
||||
key={`operator-${action.id}`}
|
||||
showSearch={false}
|
||||
options={getActionOperatorOptions(
|
||||
localSurvey.variables.find((v) => v.id === action.variableId)?.type
|
||||
)}
|
||||
value={action.operator}
|
||||
onChangeValue={(
|
||||
val: TActionTextVariableCalculateOperator | TActionNumberVariableCalculateOperator
|
||||
) => {
|
||||
handleValuesChange(idx, {
|
||||
operator: val,
|
||||
});
|
||||
}}
|
||||
comboboxClasses="grow"
|
||||
/>
|
||||
<InputCombobox
|
||||
id={`action-${idx}-value`}
|
||||
key={`value-${action.id}`}
|
||||
withInput={true}
|
||||
clearable={true}
|
||||
value={action.value?.value ?? ""}
|
||||
inputProps={{
|
||||
placeholder: "Value",
|
||||
type: localSurvey.variables.find((v) => 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"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger id={`actions-${idx}-dropdown`}>
|
||||
<EllipsisVerticalIcon className="h-4 w-4 text-slate-700 hover:text-slate-950" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
handleActionsChange("addBelow", idx);
|
||||
}}>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add action below
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
disabled={actions.length === 1}
|
||||
onClick={() => {
|
||||
handleActionsChange("remove", idx);
|
||||
}}>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => {
|
||||
handleActionsChange("duplicate", idx);
|
||||
}}>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
Duplicate
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<LogicEditorAction
|
||||
action={action}
|
||||
actionIdx={idx}
|
||||
handleActionsChange={handleActionsChange}
|
||||
handleObjectiveChange={handleObjectiveChange}
|
||||
handleValuesChange={handleValuesChange}
|
||||
endings={endings}
|
||||
isRemoveDisabled={actions.length === 1}
|
||||
filteredQuestions={filteredQuestions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -258,7 +258,7 @@ export function LogicEditorConditions({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<InputCombobox
|
||||
{/* <InputCombobox
|
||||
id={`condition-${depth}-${index}-conditionValue`}
|
||||
key="conditionValue"
|
||||
showSearch={false}
|
||||
@@ -300,7 +300,7 @@ export function LogicEditorConditions({
|
||||
handleRightOperandChange(condition, val, option);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger id={`condition-${depth}-${index}-dropdown`}>
|
||||
<EllipsisVerticalIcon className="h-4 w-4 text-slate-700 hover:text-slate-950" />
|
||||
|
||||
@@ -264,6 +264,7 @@ export const QuestionsView = ({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setLocalSurvey(updatedSurvey);
|
||||
validateSurveyQuestion(updatedSurvey.questions[questionIdx]);
|
||||
};
|
||||
|
||||
@@ -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 ?? [];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]}#`;
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<JSX.Element[]>();
|
||||
const [showImageUploader, setShowImageUploader] = useState<boolean>(
|
||||
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<HTMLInputElement>(null);
|
||||
const fallbackInputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
|
||||
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}
|
||||
</div>
|
||||
{getLocalizedValue(getElementTextBasedOnType(), usedLanguageCode).includes("recall:") && (
|
||||
{getLocalizedValue(elementText, usedLanguageCode).includes("recall:") && (
|
||||
<button
|
||||
className="fixed right-14 hidden items-center rounded-b-lg bg-slate-100 px-2.5 py-1 text-xs hover:bg-slate-200 group-hover:flex"
|
||||
onClick={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user