mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-24 11:39:22 -05:00
Add MultipleChoice Multi-Select Question Type (#238)
Add MultipleChoice Multi-Select Question Type --------- Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -52,7 +52,9 @@ export default function ResponseFeed({ person, sortByDate, environmentId }) {
|
||||
<div key={question.id}>
|
||||
<p className="text-sm text-slate-500">{question.headline}</p>
|
||||
<p className="my-1 text-lg font-semibold text-slate-700">
|
||||
{response.data[question.id]}
|
||||
{response.data[question.id] instanceof Array
|
||||
? response.data[question.id].join(", ")
|
||||
: response.data[question.id]}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -24,19 +24,18 @@ export default function PreviewSurvey({
|
||||
brandColor,
|
||||
}: PreviewSurveyProps) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const [currentQuestion, setCurrentQuestion] = useState<Question | null>(null);
|
||||
const [progress, setProgress] = useState(0); // [0, 1]
|
||||
|
||||
useEffect(() => {
|
||||
if (currentQuestion && localSurvey) {
|
||||
setProgress(calculateProgress(currentQuestion, localSurvey));
|
||||
if (activeQuestionId && localSurvey) {
|
||||
setProgress(calculateProgress(localSurvey));
|
||||
}
|
||||
|
||||
function calculateProgress(currentQuestion, survey) {
|
||||
const elementIdx = survey.questions.findIndex((e) => e.id === currentQuestion.id);
|
||||
function calculateProgress(survey) {
|
||||
const elementIdx = survey.questions.findIndex((e) => e.id === activeQuestionId);
|
||||
return elementIdx / survey.questions.length;
|
||||
}
|
||||
}, [currentQuestion, localSurvey]);
|
||||
}, [activeQuestionId, localSurvey]);
|
||||
|
||||
useEffect(() => {
|
||||
// close modal if there are no questions left
|
||||
@@ -44,7 +43,6 @@ export default function PreviewSurvey({
|
||||
if (activeQuestionId === "thank-you-card") {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
@@ -52,43 +50,25 @@ export default function PreviewSurvey({
|
||||
}
|
||||
}, [activeQuestionId, localSurvey, questions, setActiveQuestionId]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentIndex = questions.findIndex((q) => q.id === currentQuestion?.id);
|
||||
if (currentIndex < questions.length && currentIndex >= 0 && !localSurvey) return;
|
||||
|
||||
if (activeQuestionId) {
|
||||
if (currentQuestion && currentQuestion.id === activeQuestionId) {
|
||||
setCurrentQuestion(questions.find((q) => q.id === activeQuestionId) || null);
|
||||
return;
|
||||
}
|
||||
if (activeQuestionId === "thank-you-card") return;
|
||||
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions.find((q) => q.id === activeQuestionId) || null);
|
||||
setIsModalOpen(true);
|
||||
}, 300);
|
||||
} else {
|
||||
if (questions && questions.length > 0) {
|
||||
setCurrentQuestion(questions[0]);
|
||||
}
|
||||
}
|
||||
}, [activeQuestionId, currentQuestion, localSurvey, questions]);
|
||||
|
||||
const gotoNextQuestion = () => {
|
||||
if (currentQuestion) {
|
||||
const currentIndex = questions.findIndex((q) => q.id === currentQuestion.id);
|
||||
if (currentIndex < questions.length - 1) {
|
||||
setCurrentQuestion(questions[currentIndex + 1]);
|
||||
setActiveQuestionId(questions[currentIndex + 1].id);
|
||||
const currentIndex = questions.findIndex((q) => q.id === activeQuestionId);
|
||||
if (currentIndex < questions.length - 1) {
|
||||
setActiveQuestionId(questions[currentIndex + 1].id);
|
||||
} else {
|
||||
if (localSurvey?.thankYouCard?.enabled) {
|
||||
setActiveQuestionId("thank-you-card");
|
||||
} else {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
if (localSurvey?.thankYouCard?.enabled) {
|
||||
setActiveQuestionId("thank-you-card");
|
||||
setProgress(1);
|
||||
} else {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
@@ -100,18 +80,15 @@ export default function PreviewSurvey({
|
||||
const resetPreview = () => {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
if (!currentQuestion) {
|
||||
if (!activeQuestionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastQuestion = questions.length > 0 && currentQuestion.id === questions[questions.length - 1].id;
|
||||
|
||||
return (
|
||||
<>
|
||||
{localSurvey?.type === "link" ? (
|
||||
@@ -131,12 +108,18 @@ export default function PreviewSurvey({
|
||||
subheader={localSurvey?.thankYouCard?.subheader || ""}
|
||||
/>
|
||||
) : (
|
||||
<QuestionConditional
|
||||
currentQuestion={currentQuestion}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={lastQuestion}
|
||||
onSubmit={gotoNextQuestion}
|
||||
/>
|
||||
questions.map(
|
||||
(question, idx) =>
|
||||
activeQuestionId === question.id && (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
question={question}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={idx === questions.length - 1}
|
||||
onSubmit={gotoNextQuestion}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</ContentWrapper>
|
||||
</div>
|
||||
@@ -155,12 +138,18 @@ export default function PreviewSurvey({
|
||||
subheader={localSurvey?.thankYouCard?.subheader || ""}
|
||||
/>
|
||||
) : (
|
||||
<QuestionConditional
|
||||
currentQuestion={currentQuestion}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={lastQuestion}
|
||||
onSubmit={gotoNextQuestion}
|
||||
/>
|
||||
questions.map(
|
||||
(question, idx) =>
|
||||
activeQuestionId === question.id && (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
question={question}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={idx === questions.length - 1}
|
||||
onSubmit={gotoNextQuestion}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { Label } from "@formbricks/ui";
|
||||
import type { MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
interface OpenQuestionFormProps {
|
||||
question: MultipleChoiceMultiQuestion;
|
||||
questionIdx: number;
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
lastQuestion: boolean;
|
||||
}
|
||||
|
||||
export default function MultipleChoiceMultiForm({
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: OpenQuestionFormProps) {
|
||||
const updateChoice = (choiceIdx: number, updatedAttributes: any) => {
|
||||
const newChoices = !question.choices
|
||||
? []
|
||||
: question.choices.map((choice, idx) => {
|
||||
if (idx === choiceIdx) {
|
||||
return { ...choice, ...updatedAttributes };
|
||||
}
|
||||
return choice;
|
||||
});
|
||||
updateQuestion(questionIdx, { choices: newChoices });
|
||||
};
|
||||
|
||||
const addChoice = () => {
|
||||
const newChoices = !question.choices ? [] : question.choices;
|
||||
newChoices.push({ id: createId(), label: "" });
|
||||
updateQuestion(questionIdx, { choices: newChoices });
|
||||
};
|
||||
|
||||
const deleteChoice = (choiceIdx: number) => {
|
||||
const newChoices = !question.choices ? [] : question.choices.filter((_, idx) => idx !== choiceIdx);
|
||||
updateQuestion(questionIdx, { choices: newChoices });
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="headline">Question</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="headline"
|
||||
name="headline"
|
||||
value={question.headline}
|
||||
onChange={(e) => updateQuestion(questionIdx, { headline: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="choices">Choices</Label>
|
||||
<div className="mt-2 space-y-2" id="choices">
|
||||
{question.choices &&
|
||||
question.choices.map((choice, choiceIdx) => (
|
||||
<div key={choiceIdx} className="inline-flex w-full items-center">
|
||||
<Input
|
||||
id={choice.id}
|
||||
name={choice.id}
|
||||
value={choice.label}
|
||||
placeholder={`Choice ${choiceIdx + 1}`}
|
||||
onChange={(e) => updateChoice(choiceIdx, { label: e.target.value })}
|
||||
/>
|
||||
{question.choices && question.choices.length > 2 && (
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 text-slate-400"
|
||||
onClick={() => deleteChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button variant="secondary" type="button" onClick={() => addChoice()}>
|
||||
Add Choice
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="buttonLabel">Button Label</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="buttonLabel"
|
||||
name="buttonLabel"
|
||||
value={question.buttonLabel}
|
||||
placeholder={lastQuestion ? "Finish" : "Next"}
|
||||
onChange={(e) => updateQuestion(questionIdx, { buttonLabel: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import MultipleChoiceSingleForm from "./MultipleChoiceSingleForm";
|
||||
import MultipleChoiceMultiForm from "./MultipleChoiceMultiForm";
|
||||
import OpenQuestionForm from "./OpenQuestionForm";
|
||||
import QuestionDropdown from "./QuestionDropdown";
|
||||
import UpdateQuestionId from "./UpdateQuestionId";
|
||||
@@ -112,6 +113,13 @@ export default function QuestionCard({
|
||||
updateQuestion={updateQuestion}
|
||||
lastQuestion={lastQuestion}
|
||||
/>
|
||||
) : question.type === "multipleChoiceMulti" ? (
|
||||
<MultipleChoiceMultiForm
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
updateQuestion={updateQuestion}
|
||||
lastQuestion={lastQuestion}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mt-4 border-t border-slate-200">
|
||||
<Collapsible.Root open={openAdvanced} onOpenChange={setOpenAdvanced} className="mt-3">
|
||||
|
||||
-1
@@ -36,7 +36,6 @@ export default function ResponseTimeline({ environmentId, surveyId }) {
|
||||
}
|
||||
return { ...response, responses: updatedResponse };
|
||||
});
|
||||
|
||||
return updatedResponses;
|
||||
}
|
||||
return [];
|
||||
|
||||
+10
-8
@@ -13,7 +13,7 @@ interface OpenTextSummaryProps {
|
||||
responses: {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
answer: string | any[];
|
||||
}[];
|
||||
};
|
||||
environmentId: string;
|
||||
@@ -53,14 +53,16 @@ export default function SingleResponse({ data, environmentId }: OpenTextSummaryP
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-6 rounded-b-lg bg-white p-6">
|
||||
{data.responses.map((response) => {
|
||||
return (
|
||||
<div key={response.id}>
|
||||
<p className="text-sm text-slate-500">{response.question}</p>
|
||||
{data.responses.map((response, idx) => (
|
||||
<div key={`${response.id}-${idx}`}>
|
||||
<p className="text-sm text-slate-500">{response.question}</p>
|
||||
{typeof response.answer === "string" ? (
|
||||
<p className="my-1 font-semibold text-slate-700">{response.answer}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
) : (
|
||||
<p className="my-1 font-semibold text-slate-700">{response.answer.join(", ")}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+16
-3
@@ -14,6 +14,8 @@ interface ChoiceResult {
|
||||
}
|
||||
|
||||
export default function MultipleChoiceSummary({ questionSummary }: MultipleChoiceSummaryProps) {
|
||||
const isSingleChoice = questionSummary.question.type === "multipleChoiceSingle";
|
||||
|
||||
const results: ChoiceResult[] = useMemo(() => {
|
||||
if (!("choices" in questionSummary.question)) return [];
|
||||
// build a dictionary of choices
|
||||
@@ -27,9 +29,16 @@ export default function MultipleChoiceSummary({ questionSummary }: MultipleChoic
|
||||
}
|
||||
// count the responses
|
||||
for (const response of questionSummary.responses) {
|
||||
// only add responses that are in the choices
|
||||
if (response.value in resultsDict) {
|
||||
// if single choice, only add responses that are in the choices
|
||||
if (isSingleChoice && response.value in resultsDict) {
|
||||
resultsDict[response.value].count += 1;
|
||||
} else {
|
||||
// if multi choice add all responses
|
||||
for (const choice of response.value) {
|
||||
if (choice in resultsDict) {
|
||||
resultsDict[choice].count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// add the percentage
|
||||
@@ -59,7 +68,11 @@ export default function MultipleChoiceSummary({ questionSummary }: MultipleChoic
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Multiple-Choice Single Select Question</div>
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">
|
||||
{isSingleChoice
|
||||
? "Multiple-Choice Single Select Question"
|
||||
: "Multiple-Choice Multi Select Question"}
|
||||
</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{totalResponses} responses
|
||||
|
||||
+4
-1
@@ -61,7 +61,10 @@ export default function SummaryList({ environmentId, surveyId }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (questionSummary.question.type === "multipleChoiceSingle") {
|
||||
if (
|
||||
questionSummary.question.type === "multipleChoiceSingle" ||
|
||||
questionSummary.question.type === "multipleChoiceMulti"
|
||||
) {
|
||||
return (
|
||||
<MultipleChoiceSummary
|
||||
key={questionSummary.question.id}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import MultipleChoiceSingleQuestion from "@/components/preview/MultipleChoiceSingleQuestion";
|
||||
import OpenTextQuestion from "@/components/preview/OpenTextQuestion";
|
||||
import Progress from "@/components/preview/Progress";
|
||||
import ThankYouCard from "@/components/preview/ThankYouCard";
|
||||
import ContentWrapper from "@/components/shared/ContentWrapper";
|
||||
@@ -12,6 +10,7 @@ import type { Question } from "@formbricks/types/questions";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Confetti } from "@formbricks/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import QuestionConditional from "@/components/preview/QuestionConditional";
|
||||
import { createDisplay, markDisplayResponded } from "@formbricks/lib/clientDisplay/display";
|
||||
|
||||
type EnhancedSurvey = Survey & {
|
||||
@@ -124,21 +123,14 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
brandColor={survey.brandColor}
|
||||
/>
|
||||
</div>
|
||||
) : currentQuestion.type === "openText" ? (
|
||||
<OpenTextQuestion
|
||||
) : (
|
||||
<QuestionConditional
|
||||
question={currentQuestion}
|
||||
onSubmit={submitResponse}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={survey.brandColor}
|
||||
/>
|
||||
) : currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
<MultipleChoiceSingleQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={submitResponse}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={survey.brandColor}
|
||||
onSubmit={submitResponse}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</ContentWrapper>
|
||||
</div>
|
||||
<div className="top-0 z-10 w-full border-b bg-white">
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
interface MultipleChoiceMultiProps {
|
||||
question: MultipleChoiceMultiQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function MultipleChoiceMultiQuestion({
|
||||
question,
|
||||
onSubmit,
|
||||
lastQuestion,
|
||||
brandColor,
|
||||
}: MultipleChoiceMultiProps) {
|
||||
const [selectedChoices, setSelectedChoices] = useState<string[]>([]);
|
||||
const [isAtLeastOneChecked, setIsAtLeastOneChecked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsAtLeastOneChecked(selectedChoices.length > 0);
|
||||
}, [selectedChoices]);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (question.required && selectedChoices.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
[question.id]: selectedChoices,
|
||||
};
|
||||
onSubmit(data);
|
||||
setSelectedChoices([]); // reset value
|
||||
}}>
|
||||
<Headline headline={question.headline} questionId={question.id} />
|
||||
<Subheader subheader={question.subheader} questionId={question.id} />
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Choices</legend>
|
||||
<div className="relative space-y-2 rounded-md bg-white">
|
||||
{question.choices &&
|
||||
question.choices.map((choice) => (
|
||||
<label
|
||||
key={choice.id}
|
||||
className={cn(
|
||||
selectedChoices.includes(choice.label)
|
||||
? "z-10 border-slate-400 bg-slate-50"
|
||||
: "border-gray-200",
|
||||
"relative flex cursor-pointer flex-col rounded-md border p-4 hover:bg-slate-50 focus:outline-none"
|
||||
)}>
|
||||
<span className="flex items-center text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={choice.id}
|
||||
name={question.id}
|
||||
value={choice.label}
|
||||
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
|
||||
aria-labelledby={`${choice.id}-label`}
|
||||
checked={selectedChoices.includes(choice.label)}
|
||||
onChange={(e) => {
|
||||
if (e.currentTarget.checked) {
|
||||
setSelectedChoices([...selectedChoices, e.currentTarget.value]);
|
||||
} else {
|
||||
setSelectedChoices(
|
||||
selectedChoices.filter((label) => label !== e.currentTarget.value)
|
||||
);
|
||||
}
|
||||
}}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
/>
|
||||
<span id={`${choice.id}-label`} className="ml-3 font-medium">
|
||||
{choice.label}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="clip-[rect(0,0,0,0)] absolute m-[-1px] h-1 w-1 overflow-hidden whitespace-nowrap border-0 p-0 text-transparent caret-transparent focus:border-transparent focus:ring-0"
|
||||
required={question.required}
|
||||
value={isAtLeastOneChecked ? "checked" : ""}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<div className="mt-4 flex w-full justify-between">
|
||||
<div></div>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 text-white shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
|
||||
style={{ backgroundColor: brandColor }}>
|
||||
{question.buttonLabel || (lastQuestion ? "Finish" : "Next")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -26,9 +26,8 @@ export default function MultipleChoiceSingleQuestion({
|
||||
[question.id]: e.currentTarget[question.id].value,
|
||||
};
|
||||
|
||||
e.currentTarget[question.id].value = "";
|
||||
onSubmit(data);
|
||||
// reset form
|
||||
setSelectedChoice(null); // reset form
|
||||
}}>
|
||||
<Headline headline={question.headline} questionId={question.id} />
|
||||
<Subheader subheader={question.subheader} questionId={question.id} />
|
||||
@@ -55,6 +54,7 @@ export default function MultipleChoiceSingleQuestion({
|
||||
onChange={(e) => {
|
||||
setSelectedChoice(e.currentTarget.value);
|
||||
}}
|
||||
checked={selectedChoice === choice.label}
|
||||
style={{ borderColor: brandColor, color: brandColor }}
|
||||
required={question.required && idx === 0}
|
||||
/>
|
||||
|
||||
@@ -26,9 +26,8 @@ export default function OpenTextQuestion({
|
||||
const data = {
|
||||
[question.id]: value,
|
||||
};
|
||||
setValue("");
|
||||
setValue(""); // reset value
|
||||
onSubmit(data);
|
||||
// reset form
|
||||
}}>
|
||||
<Headline headline={question.headline} questionId={question.id} />
|
||||
<Subheader subheader={question.subheader} questionId={question.id} />
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import type { Question } from "@formbricks/types/questions";
|
||||
import OpenTextQuestion from "./OpenTextQuestion";
|
||||
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
|
||||
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
|
||||
|
||||
interface QuestionConditionalProps {
|
||||
currentQuestion: Question;
|
||||
question: Question;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function QuestionConditional({
|
||||
currentQuestion,
|
||||
question,
|
||||
onSubmit,
|
||||
lastQuestion,
|
||||
brandColor,
|
||||
}: QuestionConditionalProps) {
|
||||
return currentQuestion.type === "openText" ? (
|
||||
return question.type === "openText" ? (
|
||||
<OpenTextQuestion
|
||||
question={currentQuestion}
|
||||
question={question}
|
||||
onSubmit={onSubmit}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
) : question.type === "multipleChoiceSingle" ? (
|
||||
<MultipleChoiceSingleQuestion
|
||||
question={currentQuestion}
|
||||
question={question}
|
||||
onSubmit={onSubmit}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : question.type === "multipleChoiceMulti" ? (
|
||||
<MultipleChoiceMultiQuestion
|
||||
question={question}
|
||||
onSubmit={onSubmit}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={brandColor}
|
||||
|
||||
@@ -25,12 +25,14 @@ export default function ThankYouCard({ headline, subheader, brandColor }: ThankY
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span className="mb-[10px] inline-block h-1 w-16 rounded-[100%] bg-slate-300"></span>
|
||||
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
</div>
|
||||
|
||||
{/* <span
|
||||
className="mb-[10px] mt-[35px] inline-block h-[2px] w-4/5 rounded-full opacity-25"
|
||||
style={{ backgroundColor: brandColor }}></span>
|
||||
|
||||
@@ -31,6 +31,18 @@ export const questionTypes: QuestionType[] = [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "multipleChoiceMulti",
|
||||
label: "Multiple Choice Multi-Select",
|
||||
description: "Number of choices from a list of options (checkboxes)",
|
||||
icon: ListBulletIcon,
|
||||
defaults: {
|
||||
choices: [
|
||||
{ id: createId(), label: "" },
|
||||
{ id: createId(), label: "" },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const universalQuestionDefaults = {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"eslint-config-next": "^13.3.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"lucide-react": "^0.161.0",
|
||||
"next": "13.3.0",
|
||||
"next": "13.2.4",
|
||||
"next-auth": "^4.22.0",
|
||||
"nodemailer": "^6.9.1",
|
||||
"platform": "^1.3.6",
|
||||
|
||||
Reference in New Issue
Block a user