feat: Add ability to change back button label (#753)

* adds back button label adjust

* update wording

* made some refactors

---------

Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Dhruwang Jariwala <dhruwang@Dhruwangs-MacBook-Pro.local>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Dhruwang Jariwala
2023-09-06 09:59:44 +05:30
committed by GitHub
parent edc8870e09
commit 6a121680ba
20 changed files with 106 additions and 27 deletions

View File

@@ -5,6 +5,7 @@ import type { CTAQuestion } from "@formbricks/types/questions";
import { Survey } from "@formbricks/types/surveys";
import { Editor, Input, Label, RadioGroup, RadioGroupItem } from "@formbricks/ui";
import { useState } from "react";
import { BackButtonInput } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard";
interface CTAQuestionFormProps {
localSurvey: Survey;
@@ -80,18 +81,27 @@ export default function CTAQuestionForm({
</RadioGroup>
<div className="mt-3 flex justify-between gap-8">
<div className="flex-1">
<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 className="flex w-full space-x-2">
<div className="w-full">
<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>
{questionIdx !== 0 && (
<BackButtonInput
value={question.backButtonLabel}
onChange={(e) => updateQuestion(questionIdx, { backButtonLabel: e.target.value })}
/>
)}
</div>
{question.buttonExternal && (
<div className="flex-1">
<Label htmlFor="buttonLabel">Button URL</Label>

View File

@@ -42,6 +42,23 @@ interface QuestionCardProps {
isInValid: boolean;
}
export function BackButtonInput({ value, onChange }) {
return (
<div className="w-full">
<Label htmlFor="backButtonLabel">&quot;Back&quot; Button Label</Label>
<div className="mt-2">
<Input
id="backButtonLabel"
name="backButtonLabel"
value={value}
placeholder="Back"
onChange={onChange}
/>
</div>
</div>
);
}
export default function QuestionCard({
localSurvey,
questionIdx,
@@ -57,6 +74,7 @@ export default function QuestionCard({
const question = localSurvey.questions[questionIdx];
const open = activeQuestionId === question.id;
const [openAdvanced, setOpenAdvanced] = useState(question.logic && question.logic.length > 0);
return (
<Draggable draggableId={question.id} index={questionIdx}>
{(provided) => (
@@ -210,19 +228,36 @@ export default function QuestionCard({
{question.type !== QuestionType.NPS &&
question.type !== QuestionType.Rating &&
question.type !== QuestionType.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 className="mt-4 flex space-x-2">
<div className="w-full">
<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>
{questionIdx !== 0 && (
<BackButtonInput
value={question.backButtonLabel}
onChange={(e) => updateQuestion(questionIdx, { backButtonLabel: e.target.value })}
/>
)}
</div>
) : null}
{(question.type === QuestionType.Rating || question.type === QuestionType.NPS) &&
questionIdx !== 0 && (
<div className="mt-4">
<BackButtonInput
value={question.backButtonLabel}
onChange={(e) => updateQuestion(questionIdx, { backButtonLabel: e.target.value })}
/>
</div>
)}
<AdvancedSettings
question={question}

View File

@@ -2,7 +2,7 @@
import type { Survey } from "@formbricks/types/surveys";
import { createId } from "@paralleldrive/cuid2";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { DragDropContext } from "react-beautiful-dnd";
import toast from "react-hot-toast";
import AddQuestionButton from "./AddQuestionButton";
@@ -38,6 +38,8 @@ export default function QuestionsView({
}, {});
}, []);
const [backButtonLabel, setbackButtonLabel] = useState(null);
const handleQuestionLogicChange = (survey: Survey, compareId: string, updatedId: string): Survey => {
survey.questions.forEach((question) => {
if (!question.logic) return;
@@ -68,6 +70,7 @@ export default function QuestionsView({
const updateQuestion = (questionIdx: number, updatedAttributes: any) => {
let updatedSurvey = JSON.parse(JSON.stringify(localSurvey));
if ("id" in updatedAttributes) {
// if the survey whose id is to be changed is linked to logic of any other survey then changing it
const initialQuestionId = updatedSurvey.questions[questionIdx].id;
@@ -89,6 +92,13 @@ export default function QuestionsView({
...updatedSurvey.questions[questionIdx],
...updatedAttributes,
};
if ("backButtonLabel" in updatedAttributes) {
updatedSurvey.questions.forEach((question) => {
question.backButtonLabel = updatedAttributes.backButtonLabel;
});
setbackButtonLabel(updatedAttributes.backButtonLabel);
}
setLocalSurvey(updatedSurvey);
validateSurvey(updatedSurvey.questions[questionIdx]);
};
@@ -138,6 +148,9 @@ export default function QuestionsView({
const addQuestion = (question: any) => {
const updatedSurvey = JSON.parse(JSON.stringify(localSurvey));
if (backButtonLabel) {
question.backButtonLabel = backButtonLabel;
}
updatedSurvey.questions.push({ ...question, isDraft: true });
setLocalSurvey(updatedSurvey);
setActiveQuestionId(question.id);

View File

@@ -2,16 +2,17 @@ import { Button } from "@formbricks/ui";
interface BackButtonProps {
onClick: () => void;
backButtonLabel?: string;
}
export function BackButton({ onClick }: BackButtonProps) {
export function BackButton({ onClick, backButtonLabel }: BackButtonProps) {
return (
<Button
type="button"
variant="minimal"
className="mr-auto px-3 py-3 text-base font-medium leading-4 focus:ring-offset-2"
onClick={() => onClick()}>
Back
{backButtonLabel || "Back"}
</Button>
);
}

View File

@@ -32,7 +32,9 @@ export default function CTAQuestion({
<HtmlBody htmlString={question.html || ""} questionId={question.id} />
<div className="mt-4 flex w-full justify-end">
{goToPreviousQuestion && <BackButton onClick={() => goToPreviousQuestion()} />}
{goToPreviousQuestion && (
<BackButton backButtonLabel={question.backButtonLabel} onClick={() => goToPreviousQuestion()} />
)}
<div></div>
{(!question.required || storedResponseValue) && (
<button

View File

@@ -81,6 +81,7 @@ export default function ConsentQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() =>
goToPreviousQuestion({
[question.id]: answer,

View File

@@ -191,6 +191,7 @@ export default function MultipleChoiceMultiQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
if (otherSpecified.length > 0 && showOther) {
selectedChoices.push(otherSpecified);

View File

@@ -149,6 +149,7 @@ export default function MultipleChoiceSingleQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion(
selectedChoice === "other"

View File

@@ -94,6 +94,7 @@ export default function NPSQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion(
storedResponseValue !== selectedChoice

View File

@@ -83,6 +83,7 @@ export default function OpenTextQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion({
[question.id]: value,

View File

@@ -149,6 +149,7 @@ export default function RatingQuestion({
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion({ [question.id]: selectedChoice });
}}

View File

@@ -4,9 +4,10 @@ import { cn } from "@/../../packages/lib/cn";
interface BackButtonProps {
onClick: () => void;
backButtonLabel?: string;
}
export function BackButton({ onClick }: BackButtonProps) {
export function BackButton({ onClick, backButtonLabel }: BackButtonProps) {
return (
<button
type={"button"}
@@ -14,7 +15,7 @@ export function BackButton({ onClick }: BackButtonProps) {
"fb-flex fb-items-center fb-rounded-md fb-border fb-border-transparent fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 fb-shadow-sm hover:fb-opacity-90 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-slate-500 focus:fb-ring-offset-2"
)}
onClick={onClick}>
Back
{backButtonLabel ? backButtonLabel : "Back"}
</button>
);
}

View File

@@ -31,7 +31,11 @@ export default function CTAQuestion({
<HtmlBody htmlString={question.html} questionId={question.id} />
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
<div>{goToPreviousQuestion && <BackButton onClick={() => goToPreviousQuestion()} />}</div>
<div>
{goToPreviousQuestion && (
<BackButton backButtonLabel={question.backButtonLabel} onClick={() => goToPreviousQuestion()} />
)}
</div>
<div className="fb-flex fb-justify-end">
{(!question.required || storedResponseValue) && (
<button

View File

@@ -186,6 +186,7 @@ export default function MultipleChoiceMultiQuestion({
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
if (otherSpecified.length > 0 && showOther) {
selectedChoices.push(otherSpecified);

View File

@@ -149,6 +149,7 @@ export default function MultipleChoiceSingleQuestion({
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion(
selectedChoice === "other"

View File

@@ -97,6 +97,7 @@ export default function NPSQuestion({
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion(
storedResponseValue !== selectedChoice

View File

@@ -78,6 +78,7 @@ export default function OpenTextQuestion({
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion({
[question.id]: value,

View File

@@ -170,6 +170,7 @@ export default function RatingQuestion({
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-between">
{goToPreviousQuestion && (
<BackButton
backButtonLabel={question.backButtonLabel}
onClick={() => {
goToPreviousQuestion({ [question.id]: selectedChoice });
}}

View File

@@ -29,6 +29,7 @@ export interface IQuestion<T extends Logic> {
subheader?: string;
required: boolean;
buttonLabel?: string;
backButtonLabel?: string;
logic?: T[];
isDraft?: boolean;
}

View File

@@ -131,6 +131,7 @@ const ZSurveyQuestionBase = z.object({
subheader: z.string().optional(),
required: z.boolean(),
buttonLabel: z.string().optional(),
backButtonLabel: z.string().optional(),
scale: z.enum(["number", "smiley", "star"]).optional(),
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]).optional(),
logic: z.array(ZSurveyLogic).optional(),