fix: survey jumping to 3rd question with prefilling (#2421)

Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
This commit is contained in:
Dhruwang Jariwala
2024-04-18 13:30:48 +05:30
committed by GitHub
parent 1ddd1cfc3d
commit 7f438afc30
22 changed files with 170 additions and 166 deletions

View File

@@ -18,6 +18,8 @@ import { Switch } from "@formbricks/ui/Switch";
import { updateProductAction } from "../actions";
let setQuestionId = (_: string) => {};
type ThemeStylingProps = {
product: TProduct;
environmentId: string;
@@ -47,12 +49,10 @@ export const ThemeStyling = ({ product, environmentId, colors }: ThemeStylingPro
}));
};
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
const [styledPreviewSurvey, setStyledPreviewSurvey] = useState<TSurvey>(PREVIEW_SURVEY);
useEffect(() => {
setActiveQuestionId(PREVIEW_SURVEY.questions[0].id);
setQuestionId(PREVIEW_SURVEY.questions[0].id);
}, []);
useEffect(() => {
@@ -234,8 +234,7 @@ export const ThemeStyling = ({ product, environmentId, colors }: ThemeStylingPro
<div className="relative w-1/2 rounded-lg bg-slate-100 pt-4">
<div className="sticky top-4 mb-4 h-full max-h-[600px]">
<ThemeStylingPreviewSurvey
activeQuestionId={activeQuestionId}
setActiveQuestionId={setActiveQuestionId}
setQuestionId={setQuestionId}
survey={styledPreviewSurvey as TSurvey}
product={localProduct}
previewType={previewSurveyType}

View File

@@ -3,19 +3,17 @@
import Modal from "@/app/(app)/environments/[environmentId]/surveys/components/Modal";
import { MediaBackground } from "@/app/s/[surveyId]/components/MediaBackground";
import { Variants, motion } from "framer-motion";
import { Repeat2 } from "lucide-react";
import { useRef, useState } from "react";
import type { TProduct } from "@formbricks/types/product";
import { TSurvey } from "@formbricks/types/surveys";
import { Button } from "@formbricks/ui/Button";
import { ClientLogo } from "@formbricks/ui/ClientLogo";
import { ResetProgressButton } from "@formbricks/ui/ResetProgressButton";
import { SurveyInline } from "@formbricks/ui/Survey";
interface ThemeStylingPreviewSurveyProps {
survey: TSurvey;
setActiveQuestionId: (id: string | null) => void;
activeQuestionId?: string | null;
setQuestionId: (_: string) => void;
product: TProduct;
previewType: "link" | "web";
setPreviewType: (type: "link" | "web") => void;
@@ -49,12 +47,11 @@ const previewParentContainerVariant: Variants = {
};
export const ThemeStylingPreviewSurvey = ({
setActiveQuestionId,
activeQuestionId,
survey,
product,
previewType,
setPreviewType,
setQuestionId,
}: ThemeStylingPreviewSurveyProps) => {
const [isFullScreenPreview] = useState(false);
const [previewPosition] = useState("relative");
@@ -109,7 +106,7 @@ export const ThemeStylingPreviewSurvey = ({
const highlightBorderColor = product.styling.highlightBorderColor?.light;
function resetQuestionProgress() {
setActiveQuestionId(survey?.questions[0]?.id);
setQuestionId(survey?.questions[0]?.id);
}
const onFileUpload = async (file: File) => file.name;
@@ -143,7 +140,7 @@ export const ThemeStylingPreviewSurvey = ({
<p>{previewType === "web" ? "Your web app" : "Preview"}</p>
<div className="flex items-center">
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
<ResetProgressButton onClick={resetQuestionProgress} />
</div>
</div>
</div>
@@ -160,14 +157,15 @@ export const ThemeStylingPreviewSurvey = ({
borderRadius={product.styling.roundness ?? 8}>
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.inAppSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={onFileUpload}
styling={product.styling}
isCardBorderVisible={!highlightBorderColor}
languageCode="default"
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
/>
</Modal>
) : (
@@ -181,14 +179,15 @@ export const ThemeStylingPreviewSurvey = ({
className={`${product.logo?.url && !product.styling.isLogoHidden && !isFullScreenPreview ? "mt-12" : ""} z-0 w-full max-w-md rounded-lg p-4`}>
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={onFileUpload}
responseCount={42}
styling={product.styling}
languageCode="default"
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
/>
</div>
</MediaBackground>
@@ -213,15 +212,3 @@ export const ThemeStylingPreviewSurvey = ({
</div>
);
};
const ResetProgressButton = ({ resetQuestionProgress }: { resetQuestionProgress: () => void }) => {
return (
<Button
variant="minimal"
className="py-0.2 mr-2 bg-white px-2 font-sans text-sm text-slate-500"
onClick={resetQuestionProgress}>
Restart
<Repeat2 className="ml-2 h-4 w-4" />
</Button>
);
};

View File

@@ -4,11 +4,11 @@ import { useMemo } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyEditorTabs } from "@formbricks/types/surveys";
type Tab = {
interface Tab {
id: TSurveyEditorTabs;
label: string;
icon: JSX.Element;
};
}
const tabs: Tab[] = [
{
@@ -34,11 +34,11 @@ interface QuestionsAudienceTabsProps {
isStylingTabVisible?: boolean;
}
export default function QuestionsAudienceTabs({
export const QuestionsAudienceTabs = ({
activeId,
setActiveId,
isStylingTabVisible,
}: QuestionsAudienceTabsProps) {
}: QuestionsAudienceTabsProps) => {
const tabsComputed = useMemo(() => {
if (isStylingTabVisible) {
return tabs;
@@ -68,4 +68,4 @@ export default function QuestionsAudienceTabs({
</nav>
</div>
);
}
};

View File

@@ -37,7 +37,7 @@ interface QuestionsViewProps {
isFormbricksCloud: boolean;
}
export default function QuestionsView({
export const QuestionsView = ({
activeQuestionId,
setActiveQuestionId,
localSurvey,
@@ -49,7 +49,7 @@ export default function QuestionsView({
selectedLanguageCode,
isMultiLanguageAllowed,
isFormbricksCloud,
}: QuestionsViewProps) {
}: QuestionsViewProps) => {
const internalQuestionIdMap = useMemo(() => {
return localSurvey.questions.reduce((acc, question) => {
acc[question.id] = createId();
@@ -362,4 +362,4 @@ export default function QuestionsView({
</div>
</div>
);
}
};

View File

@@ -27,7 +27,7 @@ interface SettingsViewProps {
isFormbricksCloud: boolean;
}
export default function SettingsView({
export const SettingsView = ({
environment,
localSurvey,
setLocalSurvey,
@@ -38,7 +38,7 @@ export default function SettingsView({
membershipRole,
isUserTargetingAllowed = false,
isFormbricksCloud,
}: SettingsViewProps) {
}: SettingsViewProps) => {
return (
<div className="mt-12 space-y-3 p-5">
<HowToSendCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} environment={environment} />
@@ -98,4 +98,4 @@ export default function SettingsView({
)}
</div>
);
}
};

View File

@@ -2,7 +2,12 @@
import { refetchProduct } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { LoadingSkeleton } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton";
import { QuestionsAudienceTabs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsStylingSettingsTabs";
import { QuestionsView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView";
import { SettingsView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView";
import { StylingView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/StylingView";
import { SurveyMenuBar } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar";
import { PreviewSurvey } from "@/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey";
import { useCallback, useEffect, useRef, useState } from "react";
import { createSegmentAction } from "@formbricks/ee/advancedTargeting/lib/actions";
@@ -16,12 +21,6 @@ import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyEditorTabs, TSurveyStyling } from "@formbricks/types/surveys";
import PreviewSurvey from "../../../components/PreviewSurvey";
import QuestionsAudienceTabs from "./QuestionsStylingSettingsTabs";
import QuestionsView from "./QuestionsView";
import SettingsView from "./SettingsView";
import SurveyMenuBar from "./SurveyMenuBar";
interface SurveyEditorProps {
survey: TSurvey;
product: TProduct;
@@ -232,8 +231,7 @@ export default function SurveyEditor({
<aside className="group hidden flex-1 flex-shrink-0 items-center justify-center overflow-hidden border-l border-slate-100 bg-slate-50 py-6 md:flex md:flex-col">
<PreviewSurvey
survey={localSurvey}
setActiveQuestionId={setActiveQuestionId}
activeQuestionId={activeQuestionId}
questionId={activeQuestionId}
product={localProduct}
environment={environment}
previewType={localSurvey.type === "web" ? "modal" : "fullwidth"}

View File

@@ -45,7 +45,7 @@ interface SurveyMenuBarProps {
setSelectedLanguageCode: (selectedLanguage: string) => void;
}
export default function SurveyMenuBar({
export const SurveyMenuBar = ({
localSurvey,
survey,
environment,
@@ -57,7 +57,7 @@ export default function SurveyMenuBar({
responseCount,
selectedLanguageCode,
setSelectedLanguageCode,
}: SurveyMenuBarProps) {
}: SurveyMenuBarProps) => {
const router = useRouter();
const [audiencePrompt, setAudiencePrompt] = useState(true);
const [isConfirmDialogOpen, setConfirmDialogOpen] = useState(false);
@@ -497,4 +497,4 @@ export default function SurveyMenuBar({
</div>
</>
);
}
};

View File

@@ -4,24 +4,23 @@ import Modal from "@/app/(app)/environments/[environmentId]/surveys/components/M
import TabOption from "@/app/(app)/environments/[environmentId]/surveys/components/TabOption";
import { MediaBackground } from "@/app/s/[surveyId]/components/MediaBackground";
import { Variants, motion } from "framer-motion";
import { ExpandIcon, MonitorIcon, RefreshCcwIcon, ShrinkIcon, SmartphoneIcon } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { ExpandIcon, MonitorIcon, ShrinkIcon, SmartphoneIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { TEnvironment } from "@formbricks/types/environment";
import type { TProduct } from "@formbricks/types/product";
import { TProductStyling } from "@formbricks/types/product";
import { TUploadFileConfig } from "@formbricks/types/storage";
import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys";
import { Button } from "@formbricks/ui/Button";
import { ClientLogo } from "@formbricks/ui/ClientLogo";
import { ResetProgressButton } from "@formbricks/ui/ResetProgressButton";
import { SurveyInline } from "@formbricks/ui/Survey";
type TPreviewType = "modal" | "fullwidth" | "email";
interface PreviewSurveyProps {
survey: TSurvey;
setActiveQuestionId: (id: string | null) => void;
activeQuestionId?: string | null;
questionId?: string | null;
previewType?: TPreviewType;
product: TProduct;
environment: TEnvironment;
@@ -58,16 +57,17 @@ const previewParentContainerVariant: Variants = {
},
};
export default function PreviewSurvey({
setActiveQuestionId,
activeQuestionId,
let setQuestionId = (_: string) => {};
export const PreviewSurvey = ({
questionId,
survey,
previewType,
product,
environment,
languageCode,
onFileUpload,
}: PreviewSurveyProps) {
}: PreviewSurveyProps) => {
const [isModalOpen, setIsModalOpen] = useState(true);
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false);
@@ -76,7 +76,6 @@ export default function PreviewSurvey({
const ContentRef = useRef<HTMLDivElement | null>(null);
const [shrink, setShrink] = useState(false);
const { productOverwrites } = survey || {};
const previewScreenVariants: Variants = {
expanded: {
right: "5%",
@@ -140,18 +139,31 @@ export default function PreviewSurvey({
return product.styling;
}, [product.styling, survey.styling]);
const updateQuestionId = useCallback(
(newQuestionId: string) => {
if (!newQuestionId || newQuestionId === "hidden" || newQuestionId === "multiLanguage") return;
if (newQuestionId === "start" && !survey.welcomeCard.enabled) return;
setQuestionId(newQuestionId);
},
[survey.welcomeCard.enabled]
);
useEffect(() => {
if (questionId) {
updateQuestionId(questionId);
}
}, [questionId, updateQuestionId]);
const onFinished = () => {
// close modal if there are no questions left
if (survey.type === "web" && !survey.thankYouCard.enabled) {
if (activeQuestionId === "end") {
setIsModalOpen(false);
setTimeout(() => {
setActiveQuestionId(survey.questions[0]?.id);
setIsModalOpen(true);
}, 500);
}
setIsModalOpen(false);
setTimeout(() => {
setQuestionId(survey.questions[0]?.id);
setIsModalOpen(true);
}, 500);
}
}, [activeQuestionId, survey.type, survey, setActiveQuestionId]);
};
// this useEffect is fo refreshing the survey preview only if user is switching between templates on survey templates page and hence we are checking for survey.id === "someUniqeId1" which is a common Id for all templates
useEffect(() => {
@@ -169,7 +181,7 @@ export default function PreviewSurvey({
setPreviewMode(storePreviewMode);
}, 10);
setActiveQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id);
setQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id);
};
useEffect(() => {
@@ -190,7 +202,7 @@ export default function PreviewSurvey({
if (!previewType) {
previewType = widgetSetupCompleted ? "modal" : "fullwidth";
if (!activeQuestionId) {
if (!questionId) {
return <></>;
}
}
@@ -219,7 +231,7 @@ export default function PreviewSurvey({
Preview
</p>
<div className="absolute right-0 top-0 m-2">
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
<ResetProgressButton onClick={resetQuestionProgress} />
</div>
<MediaBackground survey={survey} product={product} ContentRef={ContentRef} isMobilePreview>
{previewType === "modal" ? (
@@ -234,15 +246,17 @@ export default function PreviewSurvey({
background={styling?.cardBackgroundColor?.light}>
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.inAppSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
languageCode={languageCode}
onFileUpload={onFileUpload}
styling={styling}
isCardBorderVisible={!styling.highlightBorderColor?.light}
onClose={handlePreviewModalClose}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
onFinished={onFinished}
/>
</Modal>
) : (
@@ -255,13 +269,14 @@ export default function PreviewSurvey({
<div className="no-scrollbar z-10 w-full max-w-md overflow-y-auto rounded-lg border border-transparent">
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
onFileUpload={onFileUpload}
languageCode={languageCode}
responseCount={42}
styling={styling}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
/>
</div>
</div>
@@ -300,7 +315,7 @@ export default function PreviewSurvey({
}}
/>
)}
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
<ResetProgressButton onClick={resetQuestionProgress} />
</div>
</div>
</div>
@@ -317,15 +332,17 @@ export default function PreviewSurvey({
background={styling.cardBackgroundColor?.light}>
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.inAppSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
languageCode={languageCode}
onFileUpload={onFileUpload}
styling={styling}
isCardBorderVisible={!styling.highlightBorderColor?.light}
onClose={handlePreviewModalClose}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
onFinished={onFinished}
/>
</Modal>
) : (
@@ -338,14 +355,15 @@ export default function PreviewSurvey({
<div className="z-0 w-full max-w-md rounded-lg border-transparent">
<SurveyInline
survey={survey}
activeQuestionId={activeQuestionId || undefined}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={onFileUpload}
languageCode={languageCode}
responseCount={42}
styling={styling}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
/>
</div>
</MediaBackground>
@@ -369,16 +387,4 @@ export default function PreviewSurvey({
</div>
</div>
);
}
function ResetProgressButton({ resetQuestionProgress }) {
return (
<Button
variant="minimal"
className="py-0.2 mr-2 bg-white px-2 font-sans text-sm text-slate-500"
onClick={resetQuestionProgress}>
Restart
<RefreshCcwIcon className="ml-2 h-4 w-4" />
</Button>
);
}
};

View File

@@ -1,6 +1,6 @@
"use client";
import TemplateList from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList";
import { TemplateList } from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "react-hot-toast";

View File

@@ -1,5 +1,7 @@
"use client";
import { PreviewSurvey } from "@/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey";
import { TemplateList } from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList";
import { replacePresetPlaceholders } from "@/app/lib/templates";
import { useEffect, useState } from "react";
@@ -9,8 +11,6 @@ import type { TTemplate } from "@formbricks/types/templates";
import { TUser } from "@formbricks/types/user";
import { SearchBox } from "@formbricks/ui/SearchBox";
import PreviewSurvey from "../components/PreviewSurvey";
import TemplateList from "./TemplateList";
import { minimalSurvey, templates } from "./templates";
type TemplateContainerWithPreviewProps = {
@@ -73,10 +73,9 @@ export default function TemplateContainerWithPreview({
<div className="my-6 flex h-[90%] w-full flex-col items-center justify-center">
<PreviewSurvey
survey={{ ...minimalSurvey, ...activeTemplate.preset }}
activeQuestionId={activeQuestionId}
questionId={activeQuestionId}
product={product}
environment={environment}
setActiveQuestionId={setActiveQuestionId}
languageCode={"default"}
onFileUpload={async (file) => file.name}
/>

View File

@@ -17,25 +17,26 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formb
import { createSurveyAction } from "../actions";
import { customSurvey, templates, testTemplate } from "./templates";
type TemplateList = {
interface TemplateList {
environmentId: string;
user: TUser;
onTemplateClick: (template: TTemplate) => void;
environment: TEnvironment;
product: TProduct;
templateSearch?: string;
};
}
const ALL_CATEGORY_NAME = "All";
const RECOMMENDED_CATEGORY_NAME = "For you";
export default function TemplateList({
export const TemplateList = ({
environmentId,
user,
onTemplateClick,
product,
environment,
templateSearch,
}: TemplateList) {
}: TemplateList) => {
const router = useRouter();
const [activeTemplate, setActiveTemplate] = useState<TTemplate | null>(null);
const [loading, setLoading] = useState(false);
@@ -210,4 +211,4 @@ export default function TemplateList({
</div>
</main>
);
}
};

View File

@@ -3,7 +3,6 @@
import SurveyLinkUsed from "@/app/s/[surveyId]/components/SurveyLinkUsed";
import VerifyEmail from "@/app/s/[surveyId]/components/VerifyEmail";
import { getPrefillResponseData } from "@/app/s/[surveyId]/lib/prefilling";
import { RefreshCcwIcon } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
@@ -16,10 +15,12 @@ import { TUploadFileConfig } from "@formbricks/types/storage";
import { TSurvey } from "@formbricks/types/surveys";
import { ClientLogo } from "@formbricks/ui/ClientLogo";
import { ContentWrapper } from "@formbricks/ui/ContentWrapper";
import { ResetProgressButton } from "@formbricks/ui/ResetProgressButton";
import { SurveyInline } from "@formbricks/ui/Survey";
let setIsError = (_: boolean) => {};
let setIsResponseSendingFinished = (_: boolean) => {};
let setQuestionId = (_: string) => {};
interface LinkSurveyProps {
survey: TSurvey;
@@ -76,10 +77,6 @@ export default function LinkSurvey({
// pass in the responseId if the survey is a single use survey, ensures survey state is updated with the responseId
const [surveyState, setSurveyState] = useState(new SurveyState(survey.id, singleUseId, responseId, userId));
const [activeQuestionId, setActiveQuestionId] = useState<string>(
startAt && isStartAtValid ? startAt : survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id
);
const prefillResponseData: TResponseData | undefined = prefillAnswer
? getPrefillResponseData(survey.questions[0], survey, prefillAnswer, languageCode)
: undefined;
@@ -118,6 +115,7 @@ export default function LinkSurvey({
if (window.self === window.top) {
setAutofocus(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hiddenFieldsRecord = useMemo<Record<string, string | number | string[]> | null>(() => {
@@ -187,14 +185,9 @@ export default function LinkSurvey({
<div className="fixed 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 />
Survey Preview 👀
<button
type="button"
className="flex items-center rounded-full bg-slate-500 px-3 py-1 hover:bg-slate-400"
onClick={() =>
setActiveQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id)
}>
Restart <RefreshCcwIcon className="ml-2 h-4 w-4" />
</button>
<ResetProgressButton
onClick={() => setQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id)}
/>
</div>
)}
@@ -263,11 +256,13 @@ export default function LinkSurvey({
const uploadedUrl = await api.client.storage.uploadFile(file, params);
return uploadedUrl;
}}
onActiveQuestionChange={(questionId) => setActiveQuestionId(questionId)}
activeQuestionId={activeQuestionId}
autoFocus={autoFocus}
prefillResponseData={prefillResponseData}
responseCount={responseCount}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
}}
startAtQuestionId={startAt && isStartAtValid ? startAt : undefined}
/>
</ContentWrapper>
</div>

View File

@@ -1,4 +1,4 @@
export default function FormbricksBranding() {
export const FormbricksBranding = () => {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"
@@ -13,4 +13,4 @@ export default function FormbricksBranding() {
</p>
</a>
);
}
};

View File

@@ -10,7 +10,7 @@ interface ProgressBarProps {
questionId: string;
}
export default function ProgressBar({ survey, questionId }: ProgressBarProps) {
export const ProgressBar = ({ survey, questionId }: ProgressBarProps) => {
const currentQuestionIdx = useMemo(
() => survey.questions.findIndex((e) => e.id === questionId),
[survey, questionId]
@@ -43,4 +43,4 @@ export default function ProgressBar({ survey, questionId }: ProgressBarProps) {
}, [calculateProgress, survey]);
return <Progress progress={questionId === "end" ? 1 : progressArray[currentQuestionIdx]} />;
}
};

View File

@@ -1,4 +1,4 @@
import AddressQuestion from "@/components/questions/AddressQuestion";
import { AddressQuestion } from "@/components/questions/AddressQuestion";
import { CTAQuestion } from "@/components/questions/CTAQuestion";
import { CalQuestion } from "@/components/questions/CalQuestion";
import { ConsentQuestion } from "@/components/questions/ConsentQuestion";
@@ -32,7 +32,7 @@ interface QuestionConditionalProps {
isInIframe: boolean;
}
export default function QuestionConditional({
export const QuestionConditional = ({
question,
value,
onChange,
@@ -46,7 +46,7 @@ export default function QuestionConditional({
surveyId,
onFileUpload,
isInIframe,
}: QuestionConditionalProps) {
}: QuestionConditionalProps) => {
return question.type === TSurveyQuestionType.OpenText ? (
<OpenTextQuestion
key={question.id}
@@ -242,4 +242,4 @@ export default function QuestionConditional({
isInIframe={isInIframe}
/>
) : null;
}
};

View File

@@ -1,6 +1,9 @@
import FormbricksBranding from "@/components/general/FormbricksBranding";
import ProgressBar from "@/components/general/ProgressBar";
import { FormbricksBranding } from "@/components/general/FormbricksBranding";
import { ProgressBar } from "@/components/general/ProgressBar";
import { QuestionConditional } from "@/components/general/QuestionConditional";
import { ResponseErrorComponent } from "@/components/general/ResponseErrorComponent";
import { ThankYouCard } from "@/components/general/ThankYouCard";
import { WelcomeCard } from "@/components/general/WelcomeCard";
import { AutoCloseWrapper } from "@/components/wrappers/AutoCloseWrapper";
import { evaluateCondition } from "@/lib/logicEvaluator";
import { cn } from "@/lib/utils";
@@ -13,17 +16,11 @@ import { SurveyBaseProps } from "@formbricks/types/formbricksSurveys";
import type { TResponseData, TResponseTtc } from "@formbricks/types/responses";
import { TSurveyQuestion } from "@formbricks/types/surveys";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
import WelcomeCard from "./WelcomeCard";
export function Survey({
export const Survey = ({
survey,
styling,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onActiveQuestionChange = () => {},
onResponse = () => {},
onClose = () => {},
onFinished = () => {},
@@ -33,13 +30,15 @@ export function Survey({
languageCode,
getSetIsError,
getSetIsResponseSendingFinished,
getSetQuestionId,
onFileUpload,
responseCount,
isCardBorderVisible = true,
}: SurveyBaseProps) {
startAtQuestionId,
}: SurveyBaseProps) => {
const isInIframe = window.self !== window.top;
const [questionId, setQuestionId] = useState(
activeQuestionId || (survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id)
survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id
);
const [showError, setShowError] = useState(false);
// flag state to store whether response processing has been completed or not, we ignore this check for survey editor preview and link survey preview where getSetIsResponseSendingFinished is undefined
@@ -65,15 +64,6 @@ export function Survey({
const contentRef = useRef<HTMLDivElement | null>(null);
const showProgressBar = !styling.hideProgressBar;
useEffect(() => {
if (activeQuestionId === "hidden" || activeQuestionId === "multiLanguage") return;
if (activeQuestionId === "start" && !survey.welcomeCard.enabled) {
setQuestionId(survey?.questions[0]?.id);
return;
}
setQuestionId(activeQuestionId || (survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id));
}, [activeQuestionId, survey.questions, survey.welcomeCard.enabled]);
useEffect(() => {
// scroll to top when question changes
if (contentRef.current) {
@@ -87,6 +77,9 @@ export function Survey({
if (prefillResponseData) {
onSubmit(prefillResponseData, {}, true);
}
if (startAtQuestionId && !prefillResponseData) {
setQuestionId(startAtQuestionId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -98,6 +91,14 @@ export function Survey({
}
}, [getSetIsError]);
useEffect(() => {
if (getSetQuestionId) {
getSetQuestionId((value: string) => {
setQuestionId(value);
});
}
}, [getSetQuestionId]);
useEffect(() => {
if (getSetIsResponseSendingFinished) {
getSetIsResponseSendingFinished((value: boolean) => {
@@ -174,6 +175,11 @@ export function Survey({
}
}
}
// Code to handle case where prefilling and startAt, both are included
if (startAtQuestionId && isFormPrefilling && currIdxTemp === 0) {
// if isFormPrefilling enabled, then instead of going to the next question in sequence, we go to startAtQuestionId
return startAtQuestionId;
}
return questions[currIdxTemp + 1]?.id || "end";
};
@@ -184,7 +190,7 @@ export function Survey({
const onSubmit = (responseData: TResponseData, ttc: TResponseTtc, isFormPrefilling: Boolean = false) => {
const questionId = Object.keys(responseData)[0];
if (isFormPrefilling && questionId === survey.questions[0].id) {
if (isFormPrefilling) {
onChange(responseData);
}
setLoadingElement(true);
@@ -200,7 +206,6 @@ export function Survey({
// add to history
setHistory([...history, questionId]);
setLoadingElement(false);
onActiveQuestionChange(nextQuestionId);
};
const replaceRecallInfo = (text: string): string => {
@@ -256,7 +261,6 @@ export function Survey({
}
if (!prevQuestionId) throw new Error("Question not found");
setQuestionId(prevQuestionId);
onActiveQuestionChange(prevQuestionId);
};
const getCardContent = (): JSX.Element | undefined => {
@@ -348,4 +352,4 @@ export function Survey({
</AutoCloseWrapper>
</>
);
}
};

View File

@@ -8,14 +8,12 @@ import { Survey } from "./Survey";
export function SurveyModal({
survey,
isBrandingEnabled,
activeQuestionId,
getSetIsError,
placement,
clickOutside,
darkOverlay,
onDisplay,
getSetIsResponseSendingFinished,
onActiveQuestionChange,
onResponse,
onClose,
onFinished = () => {},
@@ -51,10 +49,8 @@ export function SurveyModal({
<Survey
survey={survey}
isBrandingEnabled={isBrandingEnabled}
activeQuestionId={activeQuestionId}
onDisplay={onDisplay}
getSetIsResponseSendingFinished={getSetIsResponseSendingFinished}
onActiveQuestionChange={onActiveQuestionChange}
onResponse={onResponse}
languageCode={languageCode}
onClose={close}

View File

@@ -22,7 +22,7 @@ interface ThankYouCardProps {
isInIframe: boolean;
}
export default function ThankYouCard({
export const ThankYouCard = ({
headline,
subheader,
redirectUrl,
@@ -35,7 +35,7 @@ export default function ThankYouCard({
replaceRecallInfo,
isResponseSendingFinished,
isInIframe,
}: ThankYouCardProps) {
}: ThankYouCardProps) => {
return (
<div className="text-center">
{imageUrl || videoUrl ? (
@@ -89,4 +89,4 @@ export default function ThankYouCard({
</div>
</div>
);
}
};

View File

@@ -57,7 +57,7 @@ const UsersIcon = () => {
);
};
export default function WelcomeCard({
export const WelcomeCard = ({
headline,
html,
fileUrl,
@@ -67,7 +67,7 @@ export default function WelcomeCard({
survey,
responseCount,
isInIframe,
}: WelcomeCardProps) {
}: WelcomeCardProps) => {
const calculateTimeToComplete = () => {
let idx = calculateElementIdx(survey, 0);
if (idx === 0.5) {
@@ -149,4 +149,4 @@ export default function WelcomeCard({
) : null}
</div>
);
}
};

View File

@@ -25,7 +25,7 @@ interface AddressQuestionProps {
isInIframe: boolean;
}
export default function AddressQuestion({
export const AddressQuestion = ({
question,
value,
onChange,
@@ -37,7 +37,7 @@ export default function AddressQuestion({
ttc,
setTtc,
isInIframe,
}: AddressQuestionProps) {
}: AddressQuestionProps) => {
const [startTime, setStartTime] = useState(performance.now());
const [hasFilled, setHasFilled] = useState(false);
const isMediaAvailable = question.imageUrl || question.videoUrl;
@@ -179,4 +179,4 @@ export default function AddressQuestion({
</div>
</form>
);
}
};

View File

@@ -7,14 +7,13 @@ export interface SurveyBaseProps {
survey: TSurvey;
styling: TSurveyStyling | TProductStyling;
isBrandingEnabled: boolean;
activeQuestionId?: string;
getSetIsError?: (getSetError: (value: boolean) => void) => void;
getSetIsResponseSendingFinished?: (getSetIsResponseSendingFinished: (value: boolean) => void) => void;
getSetQuestionId?: (getSetQuestionId: (value: string) => void) => void;
onDisplay?: () => void;
onResponse?: (response: TResponseUpdate) => void;
onFinished?: () => void;
onClose?: () => void;
onActiveQuestionChange?: (questionId: string) => void;
onRetry?: () => void;
autoFocus?: boolean;
isRedirectDisabled?: boolean;
@@ -23,6 +22,7 @@ export interface SurveyBaseProps {
onFileUpload: (file: File, config?: TUploadFileConfig) => Promise<string>;
responseCount?: number;
isCardBorderVisible?: boolean;
startAtQuestionId?: string;
}
export interface SurveyInlineProps extends SurveyBaseProps {

View File

@@ -0,0 +1,19 @@
import { Repeat2 } from "lucide-react";
import { Button } from "../Button";
interface ResetProgressButtonProps {
onClick: () => void;
}
export const ResetProgressButton = ({ onClick }: ResetProgressButtonProps) => {
return (
<Button
variant="minimal"
className="py-0.2 mr-2 bg-white px-2 font-sans text-sm text-slate-500"
onClick={onClick}>
Restart
<Repeat2 className="ml-2 h-4 w-4" />
</Button>
);
};