mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-05 16:19:55 -06:00
fix: UX improvement (#2791)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -291,7 +291,7 @@ export const AddIntegrationModal = ({
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 rounded-lg border border-slate-200">
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default", attributeClasses)?.questions.map(
|
||||
(question) => (
|
||||
|
||||
@@ -229,7 +229,7 @@ export const AddIntegrationModal = ({
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 rounded-lg border border-slate-200">
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default", attributeClasses)?.questions.map(
|
||||
(question) => (
|
||||
|
||||
@@ -231,7 +231,7 @@ export const AddChannelMappingModal = ({
|
||||
<div>
|
||||
<div>
|
||||
<Label htmlFor="Surveys">Questions</Label>
|
||||
<div className="mt-1 rounded-lg border border-slate-200">
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{replaceHeadlineRecall(selectedSurvey, "default", attributeClasses)?.questions?.map(
|
||||
(question) => (
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { JSX } from "preact";
|
||||
import { useCallback } from "preact/hooks";
|
||||
|
||||
interface SubmitButtonProps {
|
||||
interface SubmitButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
|
||||
buttonLabel: string | undefined;
|
||||
isLastQuestion: boolean;
|
||||
onClick?: () => void;
|
||||
focus?: boolean;
|
||||
tabIndex?: number;
|
||||
type?: "submit" | "button";
|
||||
}
|
||||
|
||||
export const SubmitButton = ({
|
||||
buttonLabel,
|
||||
isLastQuestion,
|
||||
onClick = () => {},
|
||||
tabIndex = 1,
|
||||
focus = false,
|
||||
type = "submit",
|
||||
onClick,
|
||||
disabled,
|
||||
type,
|
||||
...props
|
||||
}: SubmitButtonProps) => {
|
||||
const buttonRef = useCallback(
|
||||
(currentButton: HTMLButtonElement | null) => {
|
||||
@@ -30,13 +30,15 @@ export const SubmitButton = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
dir="auto"
|
||||
ref={buttonRef}
|
||||
type={type}
|
||||
tabIndex={tabIndex}
|
||||
autoFocus={focus}
|
||||
className="bg-brand border-submit-button-border text-on-brand focus:ring-focus rounded-custom flex items-center border px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
onClick={onClick}>
|
||||
onClick={onClick}
|
||||
disabled={disabled}>
|
||||
{buttonLabel || (isLastQuestion ? "Finish" : "Next")}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -29,5 +29,9 @@ export const ProgressBar = ({ survey, questionId }: ProgressBarProps) => {
|
||||
return survey.questions.map((_, index) => calculateProgress(index, survey.questions.length));
|
||||
}, [calculateProgress, survey]);
|
||||
|
||||
return <Progress progress={questionId === "end" ? 1 : progressArray[currentQuestionIdx]} />;
|
||||
return (
|
||||
<Progress
|
||||
progress={questionId === "end" ? 1 : questionId === "start" ? 0 : progressArray[currentQuestionIdx]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -253,6 +253,7 @@ export const Survey = ({
|
||||
if (questionIdx === -1) {
|
||||
return (
|
||||
<WelcomeCard
|
||||
key="start"
|
||||
headline={survey.welcomeCard.headline}
|
||||
html={survey.welcomeCard.html}
|
||||
fileUrl={survey.welcomeCard.fileUrl}
|
||||
@@ -263,11 +264,13 @@ export const Survey = ({
|
||||
responseCount={responseCount}
|
||||
autoFocusEnabled={autoFocusEnabled}
|
||||
replaceRecallInfo={replaceRecallInfo}
|
||||
isCurrent={offset === 0}
|
||||
/>
|
||||
);
|
||||
} else if (questionIdx === survey.questions.length) {
|
||||
return (
|
||||
<ThankYouCard
|
||||
key="end"
|
||||
headline={replaceRecallInfo(
|
||||
getLocalizedValue(survey.thankYouCard.headline, selectedLanguage),
|
||||
responseData
|
||||
@@ -279,11 +282,13 @@ export const Survey = ({
|
||||
isResponseSendingFinished={isResponseSendingFinished}
|
||||
buttonLabel={getLocalizedValue(survey.thankYouCard.buttonLabel, selectedLanguage)}
|
||||
buttonLink={survey.thankYouCard.buttonLink}
|
||||
survey={survey}
|
||||
imageUrl={survey.thankYouCard.imageUrl}
|
||||
videoUrl={survey.thankYouCard.videoUrl}
|
||||
redirectUrl={survey.redirectUrl}
|
||||
isRedirectDisabled={isRedirectDisabled}
|
||||
autoFocusEnabled={autoFocusEnabled}
|
||||
isCurrent={offset === 0}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -291,6 +296,7 @@ export const Survey = ({
|
||||
return (
|
||||
question && (
|
||||
<QuestionConditional
|
||||
key={question.id}
|
||||
surveyId={survey.id}
|
||||
question={parseRecallInformation(question, selectedLanguage, responseData)}
|
||||
value={responseData[question.id]}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { QuestionMedia } from "@/components/general/QuestionMedia";
|
||||
import { RedirectCountDown } from "@/components/general/RedirectCountdown";
|
||||
import { Subheader } from "@/components/general/Subheader";
|
||||
import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
|
||||
interface ThankYouCardProps {
|
||||
headline?: string;
|
||||
@@ -17,6 +19,8 @@ interface ThankYouCardProps {
|
||||
videoUrl?: string;
|
||||
isResponseSendingFinished: boolean;
|
||||
autoFocusEnabled: boolean;
|
||||
isCurrent: boolean;
|
||||
survey: TSurvey;
|
||||
}
|
||||
|
||||
export const ThankYouCard = ({
|
||||
@@ -30,6 +34,8 @@ export const ThankYouCard = ({
|
||||
videoUrl,
|
||||
isResponseSendingFinished,
|
||||
autoFocusEnabled,
|
||||
isCurrent,
|
||||
survey,
|
||||
}: ThankYouCardProps) => {
|
||||
const media = imageUrl || videoUrl ? <QuestionMedia imgUrl={imageUrl} videoUrl={videoUrl} /> : null;
|
||||
const checkmark = (
|
||||
@@ -51,6 +57,30 @@ export const ThankYouCard = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (buttonLink) window.location.replace(buttonLink);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
if (isCurrent && survey.type === "link") {
|
||||
document.addEventListener("keydown", handleEnter);
|
||||
} else {
|
||||
document.removeEventListener("keydown", handleEnter);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEnter);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCurrent]);
|
||||
|
||||
return (
|
||||
<ScrollableContainer>
|
||||
<div className="text-center">
|
||||
@@ -66,10 +96,7 @@ export const ThankYouCard = ({
|
||||
buttonLabel={buttonLabel}
|
||||
isLastQuestion={false}
|
||||
focus={autoFocusEnabled}
|
||||
onClick={() => {
|
||||
if (!buttonLink) return;
|
||||
window.location.replace(buttonLink);
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SubmitButton } from "@/components/buttons/SubmitButton";
|
||||
import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
|
||||
import { calculateElementIdx } from "@/lib/utils";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
|
||||
import { TI18nString, TSurvey } from "@formbricks/types/surveys";
|
||||
@@ -18,6 +19,7 @@ interface WelcomeCardProps {
|
||||
responseCount?: number;
|
||||
autoFocusEnabled: boolean;
|
||||
replaceRecallInfo: (text: string, responseData: TResponseData) => string;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
|
||||
const TimerIcon = () => {
|
||||
@@ -68,6 +70,7 @@ export const WelcomeCard = ({
|
||||
responseCount,
|
||||
autoFocusEnabled,
|
||||
replaceRecallInfo,
|
||||
isCurrent,
|
||||
}: WelcomeCardProps) => {
|
||||
const calculateTimeToComplete = () => {
|
||||
let idx = calculateElementIdx(survey, 0);
|
||||
@@ -100,6 +103,30 @@ export const WelcomeCard = ({
|
||||
const timeToFinish = survey.welcomeCard.timeToFinish;
|
||||
const showResponseCount = survey.welcomeCard.showResponseCount;
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit({ ["welcomeCard"]: "clicked" }, {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
if (isCurrent && survey.type === "link") {
|
||||
document.addEventListener("keydown", handleEnter);
|
||||
} else {
|
||||
document.removeEventListener("keydown", handleEnter);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEnter);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCurrent]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ScrollableContainer>
|
||||
@@ -124,10 +151,9 @@ export const WelcomeCard = ({
|
||||
buttonLabel={getLocalizedValue(buttonLabel, languageCode)}
|
||||
isLastQuestion={false}
|
||||
focus={autoFocusEnabled}
|
||||
onClick={() => {
|
||||
onSubmit({ ["welcomeCard"]: "clicked" }, {});
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
type="button"
|
||||
onKeyDown={(e) => e.key === "Enter" && e.preventDefault()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export const DropdownSelector = ({
|
||||
{!disabled && (
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
className="z-50 max-h-64 min-w-[220px] max-w-[90%] overflow-auto rounded-md bg-white text-sm text-slate-800 shadow-md"
|
||||
className="z-50 max-h-64 min-w-[220px] max-w-96 overflow-auto rounded-md bg-white text-sm text-slate-800 shadow-md"
|
||||
align="start">
|
||||
{items
|
||||
.sort((a, b) => a.name?.localeCompare(b.name))
|
||||
|
||||
Reference in New Issue
Block a user