Files
formbricks-formbricks/apps/web/components/preview/RatingQuestion.tsx
tyjkerr ec0d3f2fa2 Add Back Button to Surveys (#501)
* add back button, next with local storaage wip

* handle submission and skip submission logic

* handle showing stored value on same concurrent question type.

* remove console.log

* fix next button not showing, add saving answer on pressing back to local storage

* add temp props to QuestionCondition in preview modal

* add temp props to QuestionCondition in preview modal again...

* update navigation logic

* update survey question preview

* add back-button component

* add back button to formbricks/js

* refactor localStorage functions to lib

* remove unused import

* add form prefilling when reloading forms

* merge main into branch

* Revert "merge main into branch"

This reverts commit 13bc9c06ec.

* rename localStorage key answers->responses

* rename answers -> responses in linkSurvey lib

* when survey page reloaded jump to next question instead of current question

* rename getStoredAnswer -> getStoredResponse

* continue renaming

* continue renaming

* rename answerValue -> responseValue

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2023-08-02 13:08:20 +02:00

191 lines
6.6 KiB
TypeScript

import { useEffect, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import type { RatingQuestion } from "@formbricks/types/questions";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { StarIcon } from "@heroicons/react/24/outline";
import { StarIcon as FilledStarIcon } from "@heroicons/react/24/solid";
import {
ConfusedFace,
FrowningFace,
GrinningFaceWithSmilingEyes,
GrinningSquintingFace,
NeutralFace,
PerseveringFace,
SlightlySmilingFace,
SmilingFaceWithSmilingEyes,
TiredFace,
WearyFace,
} from "../Smileys";
import SubmitButton from "@/components/preview/SubmitButton";
import { Response } from "@formbricks/types/js";
import { BackButton } from "@/components/preview/BackButton";
interface RatingQuestionProps {
question: RatingQuestion;
onSubmit: (data: { [x: string]: any }) => void;
lastQuestion: boolean;
brandColor: string;
storedResponseValue: number | null;
goToNextQuestion: (answer: Response["data"]) => void;
goToPreviousQuestion?: (answer?: Response["data"]) => void;
}
export default function RatingQuestion({
question,
onSubmit,
lastQuestion,
brandColor,
storedResponseValue,
goToNextQuestion,
goToPreviousQuestion,
}: RatingQuestionProps) {
const [selectedChoice, setSelectedChoice] = useState<number | null>(null);
const [hoveredNumber, setHoveredNumber] = useState(0);
// const icons = RatingSmileyList(question.range);
useEffect(() => {
setSelectedChoice(storedResponseValue);
}, [storedResponseValue, question]);
const handleSubmit = (value: number | null) => {
const data = {
[question.id]: value ?? null,
};
if (storedResponseValue === value) {
goToNextQuestion(data);
setSelectedChoice(null);
return;
}
onSubmit(data);
setSelectedChoice(null);
};
const handleSelect = (number: number) => {
setSelectedChoice(number);
if (question.required) {
handleSubmit(number);
}
};
const HiddenRadioInput = ({ number }) => (
<input
type="radio"
name="rating"
value={number}
className="absolute left-0 h-full w-full cursor-pointer opacity-0"
onChange={() => handleSelect(number)}
checked={selectedChoice === number}
required={question.required}
/>
);
return (
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit(selectedChoice);
}}>
<Headline headline={question.headline} questionId={question.id} />
<Subheader subheader={question.subheader} questionId={question.id} />
<div className="my-4">
<fieldset className="max-w-full">
<legend className="sr-only">Options</legend>
<div className="flex">
{Array.from({ length: question.range }, (_, i) => i + 1).map((number, i, a) => (
<span
key={number}
onMouseOver={() => setHoveredNumber(number)}
onMouseLeave={() => setHoveredNumber(0)}
className="relative max-h-10 flex-1 cursor-pointer bg-white text-center text-sm leading-10">
{question.scale === "number" ? (
<label
className={cn(
selectedChoice === number ? "z-10 border-slate-400 bg-slate-50" : "",
a.length === number ? "rounded-r-md" : "",
number === 1 ? "rounded-l-md" : "",
"box-border block h-full w-full border hover:bg-gray-100 focus:outline-none"
)}>
<HiddenRadioInput number={number} />
{number}
</label>
) : question.scale === "star" ? (
<label
className={cn(
number <= hoveredNumber ? "text-yellow-500" : "",
"flex h-full w-full justify-center"
)}>
<HiddenRadioInput number={number} />
{selectedChoice && selectedChoice >= number ? (
<FilledStarIcon className="max-h-full text-yellow-300" />
) : (
<StarIcon className="max-h-full " />
)}
</label>
) : (
<label className="flex h-full w-full justify-center">
<HiddenRadioInput number={number} />
<RatingSmiley
active={selectedChoice == number || hoveredNumber == number}
idx={i}
range={question.range}
/>
</label>
)}
</span>
))}
</div>
<div className="flex justify-between px-1.5 text-xs leading-6 text-slate-500">
<p>{question.lowerLabel}</p>
<p>{question.upperLabel}</p>
</div>
</fieldset>
</div>
<div className="mt-4 flex w-full justify-between">
{goToPreviousQuestion && (
<BackButton
onClick={() => {
goToPreviousQuestion({ [question.id]: selectedChoice });
}}
/>
)}
<div></div>
{(!question.required || storedResponseValue) && <SubmitButton {...{ question, lastQuestion, brandColor }} />}
</div>
</form>
);
}
interface RatingSmileyProps {
active: boolean;
idx: number;
range: number;
}
function RatingSmiley({ active, idx, range }: RatingSmileyProps): JSX.Element {
const activeColor = "fill-yellow-500";
const inactiveColor = "fill-none";
let icons = [
<TiredFace className={active ? activeColor : inactiveColor} />,
<WearyFace className={active ? activeColor : inactiveColor} />,
<PerseveringFace className={active ? activeColor : inactiveColor} />,
<FrowningFace className={active ? activeColor : inactiveColor} />,
<ConfusedFace className={active ? activeColor : inactiveColor} />,
<NeutralFace className={active ? activeColor : inactiveColor} />,
<SlightlySmilingFace className={active ? activeColor : inactiveColor} />,
<SmilingFaceWithSmilingEyes className={active ? activeColor : inactiveColor} />,
<GrinningFaceWithSmilingEyes className={active ? activeColor : inactiveColor} />,
<GrinningSquintingFace className={active ? activeColor : inactiveColor} />,
];
if (range == 7) icons = [icons[1], icons[3], icons[4], icons[5], icons[6], icons[8], icons[9]];
else if (range == 5) icons = [icons[3], icons[4], icons[5], icons[6], icons[7]];
else if (range == 4) icons = [icons[4], icons[5], icons[6], icons[7]];
else if (range == 3) icons = [icons[4], icons[5], icons[7]];
return icons[idx];
}