mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
fixes feedback
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
TSurveyAddressElement,
|
||||
TSurveyContactInfoElement,
|
||||
TSurveyElement,
|
||||
TSurveyElementChoice,
|
||||
TSurveyElementTypeEnum,
|
||||
} from "@formbricks/types/surveys/elements";
|
||||
import {
|
||||
@@ -33,7 +34,6 @@ import {
|
||||
TSurveyElementSummaryRanking,
|
||||
TSurveyElementSummaryRating,
|
||||
TSurveyLanguage,
|
||||
TSurveyQuestionChoice,
|
||||
TSurveySummary,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
@@ -323,7 +323,7 @@ const checkForI18n = (
|
||||
// Return the localized value of the choice fo multiSelect single question
|
||||
if (question && "choices" in question) {
|
||||
const choice = question.choices?.find(
|
||||
(choice: TSurveyQuestionChoice) => choice.label?.[languageCode] === responseData[id]
|
||||
(choice: TSurveyElementChoice) => choice.label?.[languageCode] === responseData[id]
|
||||
);
|
||||
return choice && "label" in choice
|
||||
? getLocalizedValue(choice.label, "default") || responseData[id]
|
||||
|
||||
@@ -6,13 +6,12 @@ import { ImagePlusIcon, TrashIcon } from "lucide-react";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type TI18nString } from "@formbricks/types/i18n";
|
||||
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
|
||||
import {
|
||||
TSurvey,
|
||||
TSurveyEndScreenCard,
|
||||
TSurveyQuestionChoice,
|
||||
TSurveyRedirectUrlCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
TSurveyElement,
|
||||
TSurveyElementChoice,
|
||||
TSurveyElementTypeEnum,
|
||||
} from "@formbricks/types/surveys/elements";
|
||||
import { TSurvey, TSurveyEndScreenCard, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
|
||||
import { useSyncScroll } from "@/lib/utils/hooks/useSyncScroll";
|
||||
@@ -44,7 +43,7 @@ interface QuestionFormInputProps {
|
||||
questionIdx: number;
|
||||
updateQuestion?: (questionIdx: number, data: Partial<TSurveyElement>) => void;
|
||||
updateSurvey?: (data: Partial<TSurveyEndScreenCard> | Partial<TSurveyRedirectUrlCard>) => void;
|
||||
updateChoice?: (choiceIdx: number, data: Partial<TSurveyQuestionChoice>) => void;
|
||||
updateChoice?: (choiceIdx: number, data: Partial<TSurveyElementChoice>) => void;
|
||||
updateMatrixLabel?: (index: number, type: "row" | "column", matrixLabel: TI18nString) => void;
|
||||
isInvalid: boolean;
|
||||
selectedLanguageCode: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { Project } from "@prisma/client";
|
||||
import { type Project } from "@prisma/client";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
@@ -5,8 +5,12 @@ import { CSS } from "@dnd-kit/utilities";
|
||||
import { GripVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TI18nString } from "@formbricks/types/i18n";
|
||||
import { TSurveyMultipleChoiceElement, TSurveyRankingElement } from "@formbricks/types/surveys/elements";
|
||||
import { TSurvey, TSurveyLanguage, TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
TSurveyElementChoice,
|
||||
TSurveyMultipleChoiceElement,
|
||||
TSurveyRankingElement,
|
||||
} from "@formbricks/types/surveys/elements";
|
||||
import { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { createI18nString } from "@/lib/i18n/utils";
|
||||
@@ -16,7 +20,7 @@ import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||
import { isLabelValidForAllLanguages } from "../lib/validation";
|
||||
|
||||
interface ChoiceProps {
|
||||
choice: TSurveyQuestionChoice;
|
||||
choice: TSurveyElementChoice;
|
||||
choiceIdx: number;
|
||||
questionIdx: number;
|
||||
updateChoice: (choiceIdx: number, updatedAttributes: { label: TI18nString }) => void;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { VerifyEmail } from "@/modules/survey/link/components/verify-email";
|
||||
import { getPrefillValue } from "@/modules/survey/link/lib/utils";
|
||||
import { SurveyInline } from "@/modules/ui/components/survey";
|
||||
|
||||
let setQuestionId = (_: string) => {};
|
||||
let setBlockId = (_: string) => {};
|
||||
let setResponseData = (_: TResponseData) => {};
|
||||
|
||||
interface LinkSurveyProps {
|
||||
@@ -158,7 +158,11 @@ export const LinkSurvey = ({
|
||||
};
|
||||
|
||||
const handleResetSurvey = () => {
|
||||
setQuestionId(survey.welcomeCard.enabled ? "start" : questions[0].id);
|
||||
if (survey.welcomeCard.enabled) {
|
||||
setBlockId("start");
|
||||
} else if (survey.blocks[0]) {
|
||||
setBlockId(survey.blocks[0].id);
|
||||
}
|
||||
setResponseData({});
|
||||
};
|
||||
|
||||
@@ -191,8 +195,8 @@ export const LinkSurvey = ({
|
||||
prefillResponseData={prefillValue}
|
||||
skipPrefilled={skipPrefilled}
|
||||
responseCount={responseCount}
|
||||
getSetQuestionId={(f: (value: string) => void) => {
|
||||
setQuestionId = f;
|
||||
getSetBlockId={(f: (value: string) => void) => {
|
||||
setBlockId = f;
|
||||
}}
|
||||
getSetResponseData={(f: (value: TResponseData) => void) => {
|
||||
setResponseData = f;
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface SurveyBaseProps {
|
||||
isBrandingEnabled: boolean;
|
||||
getSetIsError?: (getSetError: (value: boolean) => void) => void;
|
||||
getSetIsResponseSendingFinished?: (getSetIsResponseSendingFinished: (value: boolean) => void) => void;
|
||||
getSetQuestionId?: (getSetQuestionId: (value: string) => void) => void;
|
||||
getSetResponseData?: (getSetResponseData: (value: TResponseData) => void) => void;
|
||||
onDisplay?: () => void;
|
||||
onResponse?: (response: TResponseUpdate) => void;
|
||||
|
||||
@@ -12,7 +12,6 @@ import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface BlockConditionalProps {
|
||||
// survey: TJsEnvironmentStateSurvey;
|
||||
block: TSurveyBlock;
|
||||
value: TResponseData;
|
||||
onChange: (responseData: TResponseData) => void;
|
||||
@@ -36,7 +35,6 @@ interface BlockConditionalProps {
|
||||
}
|
||||
|
||||
export function BlockConditional({
|
||||
// survey,
|
||||
block,
|
||||
value,
|
||||
onChange,
|
||||
|
||||
@@ -2,8 +2,11 @@ import { useEffect, useRef } from "preact/hooks";
|
||||
import { type TJsFileUploadParams } from "@formbricks/types/js";
|
||||
import { type TResponseData, type TResponseDataValue, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import { type TUploadFileConfig } from "@formbricks/types/storage";
|
||||
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
|
||||
import { type TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
TSurveyElement,
|
||||
TSurveyElementChoice,
|
||||
TSurveyElementTypeEnum,
|
||||
} from "@formbricks/types/surveys/elements";
|
||||
import { AddressQuestion } from "@/components/questions/address-question";
|
||||
import { CalQuestion } from "@/components/questions/cal-question";
|
||||
import { ConsentQuestion } from "@/components/questions/consent-question";
|
||||
@@ -74,10 +77,7 @@ export function ElementConditional({
|
||||
}
|
||||
}, [formRef]);
|
||||
|
||||
const getResponseValueForRankingQuestion = (
|
||||
value: string[],
|
||||
choices: TSurveyQuestionChoice[]
|
||||
): string[] => {
|
||||
const getResponseValueForRankingQuestion = (value: string[], choices: TSurveyElementChoice[]): string[] => {
|
||||
return value
|
||||
.map((entry) => {
|
||||
// First check if entry is already a valid choice ID
|
||||
@@ -87,7 +87,7 @@ export function ElementConditional({
|
||||
// Otherwise, treat it as a localized label and find the choice by label
|
||||
return choices.find((choice) => getLocalizedValue(choice.label, languageCode) === entry)?.id;
|
||||
})
|
||||
.filter((id): id is TSurveyQuestionChoice["id"] => id !== undefined);
|
||||
.filter((id): id is TSurveyElementChoice["id"] => id !== undefined);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -99,6 +99,20 @@ export function ElementConditional({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this only once when the element renders for the first time
|
||||
}, []);
|
||||
|
||||
const isRecognizedType = Object.values(TSurveyElementTypeEnum).includes(element.type);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRecognizedType) {
|
||||
console.warn(
|
||||
`[Formbricks] Unrecognized element type "${element.type}" for element with id "${element.id}". No component will be rendered.`
|
||||
);
|
||||
}
|
||||
}, [element.type, element.id, isRecognizedType]);
|
||||
|
||||
if (!isRecognizedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
{element.type === TSurveyElementTypeEnum.OpenText ? (
|
||||
|
||||
@@ -26,7 +26,7 @@ import { evaluateLogic, performActions } from "@/lib/logic";
|
||||
import { parseRecallInformation } from "@/lib/recall";
|
||||
import { ResponseQueue } from "@/lib/response-queue";
|
||||
import { SurveyState } from "@/lib/survey-state";
|
||||
import { cn, findBlockByElementId, getDefaultLanguageCode, getElementsFromSurvey } from "@/lib/utils";
|
||||
import { cn, findBlockByElementId, getDefaultLanguageCode, getElementsFromSurveyBlocks } from "@/lib/utils";
|
||||
import { TResponseErrorCodesEnum } from "@/types/response-error-codes";
|
||||
|
||||
interface VariableStackEntry {
|
||||
@@ -58,7 +58,6 @@ export function Survey({
|
||||
languageCode,
|
||||
getSetIsError,
|
||||
getSetIsResponseSendingFinished,
|
||||
getSetQuestionId,
|
||||
getSetBlockId,
|
||||
getSetResponseData,
|
||||
responseCount,
|
||||
@@ -140,7 +139,7 @@ export function Survey({
|
||||
return null;
|
||||
}, [appUrl, environmentId, getSetIsError, getSetIsResponseSendingFinished, surveyState]);
|
||||
|
||||
const questions = useMemo(() => getElementsFromSurvey(localSurvey), [localSurvey]);
|
||||
const questions = useMemo(() => getElementsFromSurveyBlocks(localSurvey.blocks), [localSurvey.blocks]);
|
||||
|
||||
const originalQuestionRequiredStates = useMemo(() => {
|
||||
return questions.reduce<Record<string, boolean>>((acc, question) => {
|
||||
@@ -173,7 +172,7 @@ export function Survey({
|
||||
const [blockId, setBlockId] = useState(() => {
|
||||
if (startAtQuestionId) {
|
||||
// If starting at a specific question, find its parent block
|
||||
const startBlock = findBlockByElementId(localSurvey, startAtQuestionId);
|
||||
const startBlock = findBlockByElementId(localSurvey.blocks, startAtQuestionId);
|
||||
return startBlock?.id || localSurvey.blocks[0]?.id;
|
||||
} else if (localSurvey.welcomeCard.enabled) {
|
||||
return "start";
|
||||
@@ -312,18 +311,6 @@ export function Survey({
|
||||
}
|
||||
}, [getSetIsError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getSetQuestionId) {
|
||||
getSetQuestionId((value: string) => {
|
||||
// Convert question ID to block ID
|
||||
const block = findBlockByElementId(survey, value);
|
||||
if (block) {
|
||||
setBlockId(block.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [getSetQuestionId, survey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (getSetBlockId) {
|
||||
getSetBlockId((value: string) => {
|
||||
@@ -770,7 +757,6 @@ export function Survey({
|
||||
Boolean(block) && (
|
||||
<BlockConditional
|
||||
key={block.id}
|
||||
// survey={localSurvey}
|
||||
surveyId={localSurvey.id}
|
||||
block={{
|
||||
...block,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { ScrollableContainer } from "@/components/wrappers/scrollable-container";
|
||||
import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { replaceRecallInfo } from "@/lib/recall";
|
||||
import { calculateElementIdx, getElementsFromSurvey } from "@/lib/utils";
|
||||
import { calculateElementIdx, getElementsFromSurveyBlocks } from "@/lib/utils";
|
||||
import { Headline } from "./headline";
|
||||
import { Subheader } from "./subheader";
|
||||
|
||||
@@ -84,7 +84,7 @@ export function WelcomeCard({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const calculateTimeToComplete = () => {
|
||||
const questions = getElementsFromSurvey(survey);
|
||||
const questions = getElementsFromSurveyBlocks(survey.blocks);
|
||||
let totalCards = questions.length;
|
||||
if (survey.endings.length > 0) totalCards += 1;
|
||||
let idx = calculateElementIdx(survey, 0, totalCards);
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useCallback, useMemo, useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyRankingElement } from "@formbricks/types/surveys/elements";
|
||||
import type { TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
|
||||
import type { TSurveyElementChoice, TSurveyRankingElement } from "@formbricks/types/surveys/elements";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
import { Subheader } from "@/components/general/subheader";
|
||||
@@ -55,7 +54,7 @@ export function RankingQuestion({
|
||||
const sortedItems = useMemo(() => {
|
||||
return localValue
|
||||
.map((id) => question.choices.find((c) => c.id === id))
|
||||
.filter((item): item is TSurveyQuestionChoice => item !== undefined);
|
||||
.filter((item): item is TSurveyElementChoice => item !== undefined);
|
||||
}, [localValue, question.choices]);
|
||||
|
||||
const unsortedItems = useMemo(() => {
|
||||
@@ -66,7 +65,7 @@ export function RankingQuestion({
|
||||
}, [question.choices, question.shuffleOption, localValue, sortedItems, shuffledChoicesIds]);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(item: TSurveyQuestionChoice) => {
|
||||
(item: TSurveyElementChoice) => {
|
||||
const isAlreadySorted = localValue.includes(item.id);
|
||||
const newLocalValue = isAlreadySorted
|
||||
? localValue.filter((id) => id !== item.id)
|
||||
@@ -77,7 +76,7 @@ export function RankingQuestion({
|
||||
// Immediately update parent state with the new ranking
|
||||
const sortedLabels = newLocalValue
|
||||
.map((id) => question.choices.find((c) => c.id === id))
|
||||
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
|
||||
.filter((item): item is TSurveyElementChoice => item !== undefined)
|
||||
.map((item) => getLocalizedValue(item.label, languageCode));
|
||||
onChange({ [question.id]: sortedLabels });
|
||||
|
||||
@@ -101,7 +100,7 @@ export function RankingQuestion({
|
||||
// Immediately update parent state with the new ranking
|
||||
const sortedLabels = newLocalValue
|
||||
.map((id) => question.choices.find((c) => c.id === id))
|
||||
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
|
||||
.filter((item): item is TSurveyElementChoice => item !== undefined)
|
||||
.map((item) => getLocalizedValue(item.label, languageCode));
|
||||
onChange({ [question.id]: sortedLabels });
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/survey
|
||||
import { TConditionGroup, TSingleCondition } from "@formbricks/types/surveys/logic";
|
||||
import { TSurveyVariable } from "@formbricks/types/surveys/types";
|
||||
import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { getElementsFromSurvey } from "./utils";
|
||||
import { getElementsFromSurveyBlocks } from "./utils";
|
||||
|
||||
const getVariableValue = (
|
||||
variables: TSurveyVariable[],
|
||||
@@ -89,7 +89,7 @@ const getLeftOperandValue = (
|
||||
) => {
|
||||
switch (leftOperand.type) {
|
||||
case "question":
|
||||
const questions = getElementsFromSurvey(localSurvey);
|
||||
const questions = getElementsFromSurveyBlocks(localSurvey.blocks);
|
||||
const currentQuestion = questions.find((q) => q.id === leftOperand.value);
|
||||
if (!currentQuestion) return undefined;
|
||||
|
||||
@@ -223,7 +223,7 @@ const evaluateSingleCondition = (
|
||||
|
||||
let leftField: TSurveyElement | TSurveyVariable | string;
|
||||
|
||||
const questions = getElementsFromSurvey(localSurvey);
|
||||
const questions = getElementsFromSurveyBlocks(localSurvey.blocks);
|
||||
if (condition.leftOperand?.type === "question") {
|
||||
leftField = questions.find((q) => q.id === condition.leftOperand?.value) ?? "";
|
||||
} else if (condition.leftOperand?.type === "variable") {
|
||||
|
||||
@@ -2,11 +2,11 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { TJsEnvironmentStateSurvey } from "../../../types/js";
|
||||
import { type TAllowedFileExtension, mimeTypes } from "../../../types/storage";
|
||||
import { TSurveyElementTypeEnum } from "../../../types/surveys/elements";
|
||||
import type { TSurveyLanguage, TSurveyQuestionChoice } from "../../../types/surveys/types";
|
||||
import type { TSurveyLanguage } from "../../../types/surveys/types";
|
||||
import {
|
||||
findBlockByElementId,
|
||||
getDefaultLanguageCode,
|
||||
getElementsFromSurvey,
|
||||
getElementsFromSurveyBlocks,
|
||||
getMimeType,
|
||||
getShuffledChoicesIds,
|
||||
getShuffledRowIndices,
|
||||
@@ -140,12 +140,12 @@ describe("getShuffledChoicesIds", () => {
|
||||
mockGetRandomValues.mockReset();
|
||||
});
|
||||
|
||||
const choicesBase: TSurveyQuestionChoice[] = [
|
||||
const choicesBase = [
|
||||
{ id: "c1", label: { en: "Choice 1" } },
|
||||
{ id: "c2", label: { en: "Choice 2" } },
|
||||
{ id: "c3", label: { en: "Choice 3" } },
|
||||
];
|
||||
const choicesWithOther: TSurveyQuestionChoice[] = [...choicesBase, { id: "other", label: { en: "Other" } }];
|
||||
const choicesWithOther = [...choicesBase, { id: "other", label: { en: "Other" } }];
|
||||
|
||||
test('should return unshuffled for "none"', () => {
|
||||
expect(getShuffledChoicesIds(choicesBase, "none")).toEqual(["c1", "c2", "c3"]);
|
||||
@@ -225,7 +225,7 @@ describe("getQuestionsFromSurvey", () => {
|
||||
],
|
||||
};
|
||||
|
||||
const questions = getElementsFromSurvey(survey);
|
||||
const questions = getElementsFromSurveyBlocks(survey.blocks);
|
||||
expect(questions).toHaveLength(3);
|
||||
expect(questions[0].id).toBe("q1");
|
||||
expect(questions[1].id).toBe("q2");
|
||||
@@ -238,7 +238,7 @@ describe("getQuestionsFromSurvey", () => {
|
||||
blocks: [],
|
||||
} as TJsEnvironmentStateSurvey;
|
||||
|
||||
expect(getElementsFromSurvey(survey)).toEqual([]);
|
||||
expect(getElementsFromSurveyBlocks(survey.blocks)).toEqual([]);
|
||||
});
|
||||
|
||||
test("should handle blocks with no elements", () => {
|
||||
@@ -263,7 +263,7 @@ describe("getQuestionsFromSurvey", () => {
|
||||
],
|
||||
};
|
||||
|
||||
const questions = getElementsFromSurvey(survey);
|
||||
const questions = getElementsFromSurveyBlocks(survey.blocks);
|
||||
expect(questions).toHaveLength(1);
|
||||
expect(questions[0].id).toBe("q1");
|
||||
});
|
||||
@@ -313,17 +313,17 @@ describe("findBlockByElementId", () => {
|
||||
};
|
||||
|
||||
test("should find block containing the element", () => {
|
||||
const block = findBlockByElementId(survey, "q1");
|
||||
const block = findBlockByElementId(survey.blocks, "q1");
|
||||
expect(block).toBeDefined();
|
||||
expect(block?.id).toBe("block1");
|
||||
|
||||
const block2 = findBlockByElementId(survey, "q3");
|
||||
const block2 = findBlockByElementId(survey.blocks, "q3");
|
||||
expect(block2).toBeDefined();
|
||||
expect(block2?.id).toBe("block2");
|
||||
});
|
||||
|
||||
test("should return undefined for non-existent element", () => {
|
||||
const block = findBlockByElementId(survey, "nonexistent");
|
||||
const block = findBlockByElementId(survey.blocks, "nonexistent");
|
||||
expect(block).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@ import { type Result, err, ok, wrapThrowsAsync } from "@formbricks/types/error-h
|
||||
import { type ApiErrorResponse } from "@formbricks/types/errors";
|
||||
import { type TJsEnvironmentStateSurvey } from "@formbricks/types/js";
|
||||
import { TAllowedFileExtension, mimeTypes } from "@formbricks/types/storage";
|
||||
import { TSurveyBlockLogic, TSurveyBlockLogicAction } from "@formbricks/types/surveys/blocks";
|
||||
import { type TSurveyElement } from "@formbricks/types/surveys/elements";
|
||||
import { type TShuffleOption, type TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
|
||||
import { TSurveyBlock, TSurveyBlockLogic, TSurveyBlockLogicAction } from "@formbricks/types/surveys/blocks";
|
||||
import { type TSurveyElement, TSurveyElementChoice } from "@formbricks/types/surveys/elements";
|
||||
import { type TShuffleOption } from "@formbricks/types/surveys/types";
|
||||
import { ApiResponse, ApiSuccessResponse } from "@/types/api";
|
||||
|
||||
export const cn = (...classes: string[]) => {
|
||||
@@ -41,7 +41,7 @@ export const getShuffledRowIndices = (n: number, shuffleOption: TShuffleOption):
|
||||
};
|
||||
|
||||
export const getShuffledChoicesIds = (
|
||||
choices: TSurveyQuestionChoice[],
|
||||
choices: TSurveyElementChoice[],
|
||||
shuffleOption: TShuffleOption
|
||||
): string[] => {
|
||||
const otherOption = choices.find((choice) => {
|
||||
@@ -79,10 +79,10 @@ export const calculateElementIdx = (
|
||||
currentQustionIdx: number,
|
||||
totalCards: number
|
||||
): number => {
|
||||
const questions = getElementsFromSurvey(survey);
|
||||
const questions = getElementsFromSurveyBlocks(survey.blocks);
|
||||
const currentQuestion = questions[currentQustionIdx];
|
||||
const middleIdx = Math.floor(totalCards / 2);
|
||||
const possibleNextBlockIds = getPossibleNextBlocks(survey, currentQuestion);
|
||||
const possibleNextBlockIds = getPossibleNextBlocks(survey.blocks, currentQuestion);
|
||||
const endingCardIds = survey.endings.map((ending) => ending.id);
|
||||
|
||||
// Convert block IDs to element IDs (get first element of each block)
|
||||
@@ -106,9 +106,9 @@ export const calculateElementIdx = (
|
||||
return elementIdx;
|
||||
};
|
||||
|
||||
const getPossibleNextBlocks = (survey: TJsEnvironmentStateSurvey, element: TSurveyElement): string[] => {
|
||||
const getPossibleNextBlocks = (blocks: TSurveyBlock[], element: TSurveyElement): string[] => {
|
||||
// In the blocks model, logic is stored at the block level
|
||||
const parentBlock = findBlockByElementId(survey, element.id);
|
||||
const parentBlock = findBlockByElementId(blocks, element.id);
|
||||
if (!parentBlock?.logic) return [];
|
||||
|
||||
const possibleBlockIds: string[] = [];
|
||||
@@ -197,7 +197,7 @@ export const checkIfSurveyIsRTL = (survey: TJsEnvironmentStateSurvey, languageCo
|
||||
}
|
||||
}
|
||||
|
||||
const questions = getElementsFromSurvey(survey);
|
||||
const questions = getElementsFromSurveyBlocks(survey.blocks);
|
||||
for (const question of questions) {
|
||||
const questionHeadline = question.headline[languageCode];
|
||||
|
||||
@@ -212,11 +212,11 @@ export const checkIfSurveyIsRTL = (survey: TJsEnvironmentStateSurvey, languageCo
|
||||
|
||||
/**
|
||||
* Derives a flat array of elements from the survey's blocks structure.
|
||||
* @param survey The survey object with blocks
|
||||
* @param blocks The blocks array
|
||||
* @returns An array of TSurveyElement (pure elements without block-level properties)
|
||||
*/
|
||||
export const getElementsFromSurvey = (survey: TJsEnvironmentStateSurvey): TSurveyElement[] =>
|
||||
survey.blocks.flatMap((block) => block.elements);
|
||||
export const getElementsFromSurveyBlocks = (blocks: TSurveyBlock[]): TSurveyElement[] =>
|
||||
blocks.flatMap((block) => block.elements);
|
||||
|
||||
/**
|
||||
* Finds the parent block that contains the specified element ID.
|
||||
@@ -225,8 +225,8 @@ export const getElementsFromSurvey = (survey: TJsEnvironmentStateSurvey): TSurve
|
||||
* @param elementId The ID of the element to find
|
||||
* @returns The parent block or undefined if not found
|
||||
*/
|
||||
export const findBlockByElementId = (survey: TJsEnvironmentStateSurvey, elementId: string) =>
|
||||
survey.blocks.find((b) => b.elements.some((e) => e.id === elementId));
|
||||
export const findBlockByElementId = (blocks: TSurveyBlock[], elementId: string) =>
|
||||
blocks.find((block) => block.elements.some((e) => e.id === elementId));
|
||||
|
||||
/**
|
||||
* Converts a block ID to the first element ID in that block.
|
||||
@@ -242,39 +242,3 @@ export const getFirstElementIdInBlock = (
|
||||
const block = survey.blocks.find((b) => b.id === blockId);
|
||||
return block?.elements[0]?.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a block by its ID.
|
||||
* @param survey The survey object
|
||||
* @param blockId The block ID to find
|
||||
* @returns The block or undefined if not found
|
||||
*/
|
||||
export const getBlockById = (survey: TJsEnvironmentStateSurvey, blockId: string) => {
|
||||
return survey.blocks.find((b) => b.id === blockId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the next block ID after the current block.
|
||||
* @param survey The survey object
|
||||
* @param currentBlockId The current block ID
|
||||
* @returns The next block ID or undefined if current block is last
|
||||
*/
|
||||
export const getNextBlockId = (
|
||||
survey: TJsEnvironmentStateSurvey,
|
||||
currentBlockId: string
|
||||
): string | undefined => {
|
||||
const currentIndex = survey.blocks.findIndex((b) => b.id === currentBlockId);
|
||||
if (currentIndex === -1) return undefined;
|
||||
return survey.blocks[currentIndex + 1]?.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all element IDs in a block.
|
||||
* @param survey The survey object
|
||||
* @param blockId The block ID
|
||||
* @returns Array of element IDs in the block
|
||||
*/
|
||||
export const getElementIdsInBlock = (survey: TJsEnvironmentStateSurvey, blockId: string): string[] => {
|
||||
const block = getBlockById(survey, blockId);
|
||||
return block?.elements.map((e) => e.id) ?? [];
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface SurveyBaseProps {
|
||||
isBrandingEnabled: boolean;
|
||||
getSetIsError?: (getSetError: (value: boolean) => void) => void;
|
||||
getSetIsResponseSendingFinished?: (getSetIsResponseSendingFinished: (value: boolean) => void) => void;
|
||||
getSetQuestionId?: (getSetQuestionId: (value: string) => void) => void;
|
||||
getSetBlockId?: (getSetBlockId: (value: string) => void) => void;
|
||||
getSetResponseData?: (getSetResponseData: (value: TResponseData) => void) => void;
|
||||
onDisplay?: () => Promise<void>;
|
||||
|
||||
@@ -126,6 +126,8 @@ export const ZSurveyElementChoice = z.object({
|
||||
label: ZI18nString,
|
||||
});
|
||||
|
||||
export type TSurveyElementChoice = z.infer<typeof ZSurveyElementChoice>;
|
||||
|
||||
export const ZShuffleOption = z.enum(["none", "all", "exceptLast"]);
|
||||
export type TShuffleOption = z.infer<typeof ZShuffleOption>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user