mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-25 03:09:24 -06:00
question id can now be changed in advanced settings
This commit is contained in:
@@ -45,9 +45,11 @@ Formbricks helps you apply best practices from data-driven work and experience m
|
||||
| --- | --------------------------------------------- |
|
||||
| 👷 | Multiple-Choice Multi-Select Question Type |
|
||||
| 👷 | NPS Question Type |
|
||||
| 👷 | Filter Audience by Attributes |
|
||||
| 👷 | Share Surveys via Link |
|
||||
| 🗒️ | Rating Scale (Numbers + Emojis) Question Type |
|
||||
| 🗒️ | Filter Audience by Attributes |
|
||||
| 🗒️ | Share Surveys via Link |
|
||||
| 🗒️ | Advanced Response Filtering & Analysis |
|
||||
| 🗒️ | Zapier, Slack & Posthog Integration |
|
||||
|
||||
_👷 In Progress | 🗒️ Up Next_
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@formbricks/ui";
|
||||
import { Switch } from "@formbricks/ui";
|
||||
import { getQuestionTypeName } from "@/lib/questions";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Question } from "@formbricks/types/questions";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Label, Switch } from "@formbricks/ui";
|
||||
import { Bars3BottomLeftIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import MultipleChoiceSingleForm from "./MultipleChoiceSingleForm";
|
||||
import OpenQuestionForm from "./OpenQuestionForm";
|
||||
import QuestionDropdown from "./QuestionDropdown";
|
||||
import UpdateQuestionId from "./UpdateQuestionId";
|
||||
|
||||
interface QuestionCardProps {
|
||||
localSurvey: Survey;
|
||||
question: Question;
|
||||
questionIdx: number;
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
@@ -23,6 +26,7 @@ interface QuestionCardProps {
|
||||
}
|
||||
|
||||
export default function QuestionCard({
|
||||
localSurvey,
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
@@ -32,6 +36,7 @@ export default function QuestionCard({
|
||||
lastQuestion,
|
||||
}: QuestionCardProps) {
|
||||
const open = activeQuestionId === question.id;
|
||||
const [openAdvanced, setOpenAdvanced] = useState(false);
|
||||
return (
|
||||
<Draggable draggableId={question.id} index={questionIdx}>
|
||||
{(provided) => (
|
||||
@@ -108,6 +113,24 @@ export default function QuestionCard({
|
||||
lastQuestion={lastQuestion}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mt-4 border-t border-slate-200">
|
||||
<Collapsible.Root open={openAdvanced} onOpenChange={setOpenAdvanced} className="mt-3">
|
||||
<Collapsible.CollapsibleTrigger className="text-sm text-slate-800">
|
||||
{openAdvanced ? "Hide Advanced Settings" : "Show Advanced Settings"}
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
|
||||
<Collapsible.CollapsibleContent className="space-y-2">
|
||||
<div className="mt-3">
|
||||
<UpdateQuestionId
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
updateQuestion={updateQuestion}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,8 @@ import AddQuestionButton from "./AddQuestionButton";
|
||||
import QuestionCard from "./QuestionCard";
|
||||
import { StrictModeDroppable } from "./StrictModeDroppable";
|
||||
import EditThankYouCard from "./EditThankYouCard";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface QuestionsViewProps {
|
||||
localSurvey: Survey;
|
||||
@@ -20,6 +22,13 @@ export default function QuestionsView({
|
||||
localSurvey,
|
||||
setLocalSurvey,
|
||||
}: QuestionsViewProps) {
|
||||
const internalQuestionIdMap = useMemo(() => {
|
||||
return localSurvey.questions.reduce((acc, question) => {
|
||||
acc[question.id] = createId();
|
||||
return acc;
|
||||
}, {});
|
||||
}, []);
|
||||
|
||||
const updateQuestion = (questionIdx: number, updatedAttributes: any) => {
|
||||
const updatedSurvey = JSON.parse(JSON.stringify(localSurvey));
|
||||
updatedSurvey.questions[questionIdx] = {
|
||||
@@ -27,12 +36,20 @@ export default function QuestionsView({
|
||||
...updatedAttributes,
|
||||
};
|
||||
setLocalSurvey(updatedSurvey);
|
||||
if ("id" in updatedAttributes) {
|
||||
// relink the question to internal Id
|
||||
internalQuestionIdMap[updatedAttributes.id] =
|
||||
internalQuestionIdMap[localSurvey.questions[questionIdx].id];
|
||||
delete internalQuestionIdMap[localSurvey.questions[questionIdx].id];
|
||||
setActiveQuestionId(updatedAttributes.id);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteQuestion = (questionIdx: number) => {
|
||||
const updatedSurvey = JSON.parse(JSON.stringify(localSurvey));
|
||||
updatedSurvey.questions.splice(questionIdx, 1);
|
||||
setLocalSurvey(updatedSurvey);
|
||||
delete internalQuestionIdMap[localSurvey.questions[questionIdx].id];
|
||||
};
|
||||
|
||||
const addQuestion = (question: any) => {
|
||||
@@ -40,6 +57,7 @@ export default function QuestionsView({
|
||||
updatedSurvey.questions.push(question);
|
||||
setLocalSurvey(updatedSurvey);
|
||||
setActiveQuestionId(question.id);
|
||||
internalQuestionIdMap[question.id] = createId();
|
||||
};
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
@@ -64,7 +82,8 @@ export default function QuestionsView({
|
||||
{localSurvey.questions.map((question, questionIdx) => (
|
||||
// display a question form
|
||||
<QuestionCard
|
||||
key={question.id}
|
||||
key={internalQuestionIdMap[question.id]}
|
||||
localSurvey={localSurvey}
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
updateQuestion={updateQuestion}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function UpdateQuestionId({ localSurvey, question, questionIdx, updateQuestion }) {
|
||||
const [currentValue, setCurrentValue] = useState(question.id);
|
||||
|
||||
const saveAction = () => {
|
||||
// check if id is unique
|
||||
const questionIds = localSurvey.questions.map((q) => q.id);
|
||||
if (questionIds.includes(currentValue)) {
|
||||
alert("Question Identifier must be unique within the survey.");
|
||||
setCurrentValue(question.id);
|
||||
return;
|
||||
}
|
||||
updateQuestion(questionIdx, { id: currentValue });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label className="block" htmlFor="questionId">
|
||||
Internal Question Identifier
|
||||
</Label>
|
||||
<div className="mt-2 inline-flex w-full">
|
||||
<Input
|
||||
id="questionId"
|
||||
name="questionId"
|
||||
value={currentValue}
|
||||
onChange={(e) => setCurrentValue(e.target.value)}
|
||||
disabled={localSurvey.status !== "draft"}
|
||||
/>
|
||||
{localSurvey.status === "draft" && (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="ml-2 bg-slate-600 text-white hover:bg-slate-700 disabled:bg-slate-400"
|
||||
onClick={saveAction}
|
||||
disabled={currentValue === question.id}>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user