diff --git a/README.md b/README.md index 1aa40c33d2..db4145bf14 100644 --- a/README.md +++ b/README.md @@ -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_ diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx index cce7010335..d30222a5b1 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx @@ -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 ( {(provided) => ( @@ -108,6 +113,24 @@ export default function QuestionCard({ lastQuestion={lastQuestion} /> ) : null} +
+ + + {openAdvanced ? "Hide Advanced Settings" : "Show Advanced Settings"} + + + +
+ +
+
+
+
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx index 41774da443..7351cbbbc7 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx @@ -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 { + // 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 ( +
+ +
+ setCurrentValue(e.target.value)} + disabled={localSurvey.status !== "draft"} + /> + {localSurvey.status === "draft" && ( + + )} +
+
+ ); +}