mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 10:30:00 -06:00
Merge commit '03e83caeb7bedbf38f1c01d404a9772a73f2187e' into FBRICKS-332
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import LogicEditor from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor";
|
||||
import UpdateQuestionId from "./UpdateQuestionId";
|
||||
import { Question } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
|
||||
interface AdvancedSettingsProps {
|
||||
question: Question;
|
||||
questionIdx: number;
|
||||
localSurvey: Survey;
|
||||
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
|
||||
}
|
||||
|
||||
export default function AdvancedSettings({
|
||||
question,
|
||||
questionIdx,
|
||||
localSurvey,
|
||||
updateQuestion,
|
||||
}: AdvancedSettingsProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<LogicEditor
|
||||
question={question}
|
||||
updateQuestion={updateQuestion}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UpdateQuestionId
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
updateQuestion={updateQuestion}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||
import { PlusIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -18,10 +18,10 @@ export default function MultipleChoiceMultiForm({
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: OpenQuestionFormProps): JSX.Element {
|
||||
const lastChoiceRef = useRef<HTMLInputElement>(null);
|
||||
const [isNew, setIsNew] = useState(true);
|
||||
const [showSubheader, setShowSubheader] = useState(!!question.subheader);
|
||||
const questionRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const updateChoice = (choiceIdx: number, updatedAttributes: any) => {
|
||||
@@ -36,14 +36,19 @@ export default function MultipleChoiceMultiForm({
|
||||
updateQuestion(questionIdx, { choices: newChoices });
|
||||
};
|
||||
|
||||
const addChoice = () => {
|
||||
const addChoice = (choiceIdx?: number) => {
|
||||
setIsNew(false); // This question is no longer new.
|
||||
let newChoices = !question.choices ? [] : question.choices;
|
||||
const otherChoice = newChoices.find((choice) => choice.id === "other");
|
||||
if (otherChoice) {
|
||||
newChoices = newChoices.filter((choice) => choice.id !== "other");
|
||||
}
|
||||
newChoices.push({ id: createId(), label: "" });
|
||||
const newChoice = { id: createId(), label: "" };
|
||||
if (choiceIdx !== undefined) {
|
||||
newChoices.splice(choiceIdx + 1, 0, newChoice);
|
||||
} else {
|
||||
newChoices.push(newChoice);
|
||||
}
|
||||
if (otherChoice) {
|
||||
newChoices.push(otherChoice);
|
||||
}
|
||||
@@ -105,15 +110,32 @@ export default function MultipleChoiceMultiForm({
|
||||
</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>
|
||||
{showSubheader && (
|
||||
<>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2 inline-flex w-full items-center">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => {
|
||||
setShowSubheader(false);
|
||||
updateQuestion(questionIdx, { subheader: "" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showSubheader && (
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => setShowSubheader(true)}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
Add Description
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
@@ -137,36 +159,25 @@ export default function MultipleChoiceMultiForm({
|
||||
onClick={() => deleteChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
{choice.id !== "other" && (
|
||||
<PlusIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => addChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="secondary" size="sm" type="button" onClick={() => addChoice()}>
|
||||
Add Option
|
||||
</Button>
|
||||
{question.choices.filter((c) => c.id === "other").length === 0 && (
|
||||
<>
|
||||
<p>or</p>
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => addOther()}>
|
||||
Add "Other" with specify
|
||||
Add "Other"
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MultipleChoiceSingleQuestion } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||
import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -18,10 +18,10 @@ export default function MultipleChoiceSingleForm({
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: OpenQuestionFormProps): JSX.Element {
|
||||
const lastChoiceRef = useRef<HTMLInputElement>(null);
|
||||
const [isNew, setIsNew] = useState(true);
|
||||
const [showSubheader, setShowSubheader] = useState(!!question.subheader);
|
||||
const questionRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const updateChoice = (choiceIdx: number, updatedAttributes: any) => {
|
||||
@@ -36,14 +36,19 @@ export default function MultipleChoiceSingleForm({
|
||||
updateQuestion(questionIdx, { choices: newChoices });
|
||||
};
|
||||
|
||||
const addChoice = () => {
|
||||
const addChoice = (choiceIdx?: number) => {
|
||||
setIsNew(false); // This question is no longer new.
|
||||
let newChoices = !question.choices ? [] : question.choices;
|
||||
const otherChoice = newChoices.find((choice) => choice.id === "other");
|
||||
if (otherChoice) {
|
||||
newChoices = newChoices.filter((choice) => choice.id !== "other");
|
||||
}
|
||||
newChoices.push({ id: createId(), label: "" });
|
||||
const newChoice = { id: createId(), label: "" };
|
||||
if (choiceIdx !== undefined) {
|
||||
newChoices.splice(choiceIdx + 1, 0, newChoice);
|
||||
} else {
|
||||
newChoices.push(newChoice);
|
||||
}
|
||||
if (otherChoice) {
|
||||
newChoices.push(otherChoice);
|
||||
}
|
||||
@@ -105,15 +110,32 @@ export default function MultipleChoiceSingleForm({
|
||||
</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>
|
||||
{showSubheader && (
|
||||
<>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2 inline-flex w-full items-center">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => {
|
||||
setShowSubheader(false);
|
||||
updateQuestion(questionIdx, { subheader: "" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showSubheader && (
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => setShowSubheader(true)}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
Add Description
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
@@ -137,36 +159,23 @@ export default function MultipleChoiceSingleForm({
|
||||
onClick={() => deleteChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
{choice.id !== "other" && (
|
||||
<PlusIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => addChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="secondary" size="sm" type="button" onClick={() => addChoice()}>
|
||||
Add Option
|
||||
</Button>
|
||||
{question.choices.filter((c) => c.id === "other").length === 0 && (
|
||||
<>
|
||||
<p>or</p>
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => addOther()}>
|
||||
Add "Other" with specify
|
||||
</Button>
|
||||
</>
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => addOther()}>
|
||||
Add "Other"
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { NPSQuestion } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
import { Input, Label } from "@formbricks/ui";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
interface NPSQuestionFormProps {
|
||||
localSurvey: Survey;
|
||||
@@ -16,6 +18,8 @@ export default function NPSQuestionForm({
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: NPSQuestionFormProps): JSX.Element {
|
||||
const [showSubheader, setShowSubheader] = useState(!!question.subheader);
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
@@ -32,15 +36,32 @@ export default function NPSQuestionForm({
|
||||
</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>
|
||||
{showSubheader && (
|
||||
<>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2 inline-flex w-full items-center">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => {
|
||||
setShowSubheader(false);
|
||||
updateQuestion(questionIdx, { subheader: "" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showSubheader && (
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => setShowSubheader(true)}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
Add Description
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex justify-between">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { OpenTextQuestion } from "@formbricks/types/questions";
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
import { Input, Label } from "@formbricks/ui";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
interface OpenQuestionFormProps {
|
||||
localSurvey: Survey;
|
||||
@@ -14,8 +16,9 @@ export default function OpenQuestionForm({
|
||||
question,
|
||||
questionIdx,
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: OpenQuestionFormProps): JSX.Element {
|
||||
const [showSubheader, setShowSubheader] = useState(!!question.subheader);
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
@@ -32,15 +35,32 @@ export default function OpenQuestionForm({
|
||||
</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>
|
||||
{showSubheader && (
|
||||
<>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2 inline-flex w-full items-center">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => {
|
||||
setShowSubheader(false);
|
||||
updateQuestion(questionIdx, { subheader: "" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showSubheader && (
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => setShowSubheader(true)}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
Add Description
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
@@ -54,19 +74,6 @@ export default function OpenQuestionForm({
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import LogicEditor from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor";
|
||||
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 { Input, Label, Switch } from "@formbricks/ui";
|
||||
import {
|
||||
ChatBubbleBottomCenterTextIcon,
|
||||
ChevronDownIcon,
|
||||
@@ -24,9 +23,9 @@ import MultipleChoiceMultiForm from "./MultipleChoiceMultiForm";
|
||||
import MultipleChoiceSingleForm from "./MultipleChoiceSingleForm";
|
||||
import NPSQuestionForm from "./NPSQuestionForm";
|
||||
import OpenQuestionForm from "./OpenQuestionForm";
|
||||
import QuestionDropdown from "./QuestionDropdown";
|
||||
import QuestionDropdown from "./QuestionMenu";
|
||||
import RatingQuestionForm from "./RatingQuestionForm";
|
||||
import UpdateQuestionId from "./UpdateQuestionId";
|
||||
import AdvancedSettings from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/AdvancedSettings";
|
||||
|
||||
interface QuestionCardProps {
|
||||
localSurvey: Survey;
|
||||
@@ -54,7 +53,7 @@ export default function QuestionCard({
|
||||
lastQuestion,
|
||||
}: QuestionCardProps) {
|
||||
const open = activeQuestionId === question.id;
|
||||
const [openAdvanced, setOpenAdvanced] = useState(false);
|
||||
const [openAdvanced, setOpenAdvanced] = useState(question.logic && question.logic.length > 0);
|
||||
return (
|
||||
<Draggable draggableId={question.id} index={questionIdx}>
|
||||
{(provided) => (
|
||||
@@ -116,19 +115,6 @@ export default function QuestionCard({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{open && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="required-toggle">Required</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={question.required}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuestion(questionIdx, { required: !question.required });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<QuestionDropdown
|
||||
questionIdx={questionIdx}
|
||||
lastQuestion={lastQuestion}
|
||||
@@ -189,15 +175,9 @@ export default function QuestionCard({
|
||||
lastQuestion={lastQuestion}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mt-4 border-t border-slate-200">
|
||||
<LogicEditor
|
||||
question={question}
|
||||
updateQuestion={updateQuestion}
|
||||
localSurvey={localSurvey}
|
||||
questionIdx={questionIdx}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<Collapsible.Root open={openAdvanced} onOpenChange={setOpenAdvanced} className="mt-5">
|
||||
<Collapsible.CollapsibleTrigger className="flex items-center text-xs text-slate-700 ">
|
||||
<Collapsible.CollapsibleTrigger className="flex items-center text-xs text-slate-700">
|
||||
{openAdvanced ? (
|
||||
<ChevronDownIcon className="mr-1 h-4 w-3" />
|
||||
) : (
|
||||
@@ -206,19 +186,48 @@ export default function QuestionCard({
|
||||
{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 className="space-y-4">
|
||||
{question.type !== "nps" && question.type !== "rating" && question.type !== "cta" ? (
|
||||
<div className="mt-4">
|
||||
<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>
|
||||
) : null}
|
||||
|
||||
<AdvancedSettings
|
||||
question={question}
|
||||
questionIdx={questionIdx}
|
||||
localSurvey={localSurvey}
|
||||
updateQuestion={updateQuestion}
|
||||
/>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
|
||||
{open && (
|
||||
<div className="m-4 mt-0 border-t border-slate-200">
|
||||
<div className="m-4 mr-0 flex items-center justify-end space-x-2">
|
||||
<Label htmlFor="required-toggle">Required</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={question.required}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuestion(questionIdx, { required: !question.required });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@formbricks/ui";
|
||||
import {
|
||||
EllipsisHorizontalIcon,
|
||||
ArrowUpIcon,
|
||||
ArrowDownIcon,
|
||||
TrashIcon,
|
||||
DocumentDuplicateIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
|
||||
interface QuestionDropdownProps {
|
||||
questionIdx: number;
|
||||
lastQuestion: boolean;
|
||||
duplicateQuestion: (questionIdx: number) => void;
|
||||
deleteQuestion: (questionIdx: number) => void;
|
||||
moveQuestion: (questionIdx: number, up: boolean) => void;
|
||||
}
|
||||
|
||||
export default function QuestionDropdown({
|
||||
questionIdx,
|
||||
lastQuestion,
|
||||
duplicateQuestion,
|
||||
deleteQuestion,
|
||||
moveQuestion,
|
||||
}: QuestionDropdownProps) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<EllipsisHorizontalIcon className="h-5 w-5 text-slate-600 focus:outline-none active:outline-none" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveQuestion(questionIdx, true);
|
||||
}}
|
||||
disabled={questionIdx == 0}>
|
||||
Move up <ArrowUpIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveQuestion(questionIdx, false);
|
||||
}}
|
||||
disabled={lastQuestion}>
|
||||
Move down
|
||||
<ArrowDownIcon className="h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateQuestion(questionIdx);
|
||||
}}>
|
||||
Duplicate <DocumentDuplicateIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteQuestion(questionIdx);
|
||||
}}>
|
||||
Delete <TrashIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowUpIcon, ArrowDownIcon, TrashIcon, DocumentDuplicateIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
interface QuestionDropdownProps {
|
||||
questionIdx: number;
|
||||
lastQuestion: boolean;
|
||||
duplicateQuestion: (questionIdx: number) => void;
|
||||
deleteQuestion: (questionIdx: number) => void;
|
||||
moveQuestion: (questionIdx: number, up: boolean) => void;
|
||||
}
|
||||
|
||||
export default function QuestionActions({
|
||||
questionIdx,
|
||||
lastQuestion,
|
||||
duplicateQuestion,
|
||||
deleteQuestion,
|
||||
moveQuestion,
|
||||
}: QuestionDropdownProps) {
|
||||
return (
|
||||
<div className="flex space-x-4">
|
||||
<ArrowUpIcon
|
||||
className={`h-4 cursor-pointer text-slate-500 hover:text-slate-600 ${
|
||||
questionIdx === 0 ? "opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (questionIdx !== 0) {
|
||||
e.stopPropagation();
|
||||
moveQuestion(questionIdx, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ArrowDownIcon
|
||||
className={`h-4 cursor-pointer text-slate-500 hover:text-slate-600 ${
|
||||
lastQuestion ? "opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (!lastQuestion) {
|
||||
e.stopPropagation();
|
||||
moveQuestion(questionIdx, false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<DocumentDuplicateIcon
|
||||
className="h-4 cursor-pointer text-slate-500 hover:text-slate-600"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateQuestion(questionIdx);
|
||||
}}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="h-4 cursor-pointer text-slate-500 hover:text-slate-600"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteQuestion(questionIdx);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { RatingQuestion } from "@formbricks/types/questions";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Input, Label } from "@formbricks/ui";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { FaceSmileIcon, HashtagIcon, StarIcon } from "@heroicons/react/24/outline";
|
||||
import Dropdown from "./RatingTypeDropdown";
|
||||
import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
|
||||
interface RatingQuestionFormProps {
|
||||
localSurvey: Survey;
|
||||
@@ -18,6 +20,8 @@ export default function RatingQuestionForm({
|
||||
updateQuestion,
|
||||
lastQuestion,
|
||||
}: RatingQuestionFormProps) {
|
||||
const [showSubheader, setShowSubheader] = useState(!!question.subheader);
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
@@ -34,15 +38,32 @@ export default function RatingQuestionForm({
|
||||
</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>
|
||||
{showSubheader && (
|
||||
<>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2 inline-flex w-full items-center">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
value={question.subheader}
|
||||
onChange={(e) => updateQuestion(questionIdx, { subheader: e.target.value })}
|
||||
/>
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => {
|
||||
setShowSubheader(false);
|
||||
updateQuestion(questionIdx, { subheader: "" });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showSubheader && (
|
||||
<Button size="sm" variant="minimal" type="button" onClick={() => setShowSubheader(true)}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
Add Description
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex justify-between gap-8">
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function UpdateQuestionId({ localSurvey, question, questionIdx, u
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label className="block" htmlFor="questionId">
|
||||
<Label htmlFor="questionId">
|
||||
Question ID
|
||||
</Label>
|
||||
<div className="mt-2 inline-flex w-full">
|
||||
|
||||
@@ -203,7 +203,7 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
loadingElement && "animate-pulse opacity-60",
|
||||
"flex h-full flex-1 items-center overflow-y-auto bg-white"
|
||||
)}>
|
||||
<ContentWrapper className="w-full md:max-w-lg">
|
||||
<ContentWrapper className="h-full w-full md:max-w-lg">
|
||||
{isPreview && (
|
||||
<div className="absolute left-0 top-0 flex w-full items-center justify-between bg-slate-600 p-2 px-4 text-center text-sm text-white shadow-sm">
|
||||
<div className="w-20"></div>
|
||||
|
||||
Reference in New Issue
Block a user