From 711c15cbe98a46cd4d52e5dca0ce71358f01c282 Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Sun, 10 Sep 2023 14:06:55 +0900 Subject: [PATCH] chore: Introduce unified survey UI package @formbricks/surveys (#698) * add vite survey package * add renderSurvey method * add all survey components * First working version of renderSurvey * integrate survey package into survey preview * add survey modal functionality to formbricks-surveys * fix build errors and add new template types * add response queue * add simple formbricks-js integration * add local state management for surveys * add local storage to multiple choice and open text questions * add local state to other question types, layout fixes * Fix modal close button, clean js package code * add new calculate progress function * fix progressbar on thankyou card * fix churn survey branching in demo product * use tsup to bundle @formbricks/js * update survey positioning in link surveys * fix preview reset button in link survey * change logic for progress bar * update progressbar logic * update spacing * add conditional autofocus / disable for iframe * add userId to link survey * integrated email verification * moved token verification and reading to server component * ran pnpm format * added question prefilling * ran pnpm format * Moved question prefilling logic to Link Survey * Refactor types * centralize survey package props, fix build errors * fix userId in link survey * fix survey closed message * add redirect on complete, fix bugs * smaller bugfixes * smaller bugfixes * fix bugs * fix build errors * remove logs --------- Co-authored-by: Johannes Co-authored-by: Dhruwang --- .../people/[personId]/DeletePersonButton.tsx | 1 + .../people/[personId]/loading.tsx | 7 +- .../[environmentId]/surveys/PreviewSurvey.tsx | 387 ++------------ .../[environmentId]/surveys/SurveyStarter.tsx | 4 +- .../[surveyId]/edit/CTAQuestionForm.tsx | 56 +-- .../[surveyId]/edit/EditThankYouCard.tsx | 4 +- .../[surveyId]/edit/ResponseOptionsCard.tsx | 48 +- .../surveys/[surveyId]/edit/SurveyEditor.tsx | 6 +- .../surveys/templates/TemplateContainer.tsx | 12 +- .../surveys/templates/TemplateList.tsx | 8 +- .../surveys/templates/templates.ts | 31 +- apps/web/app/(app)/onboarding/Objective.tsx | 13 +- apps/web/app/(app)/onboarding/Product.tsx | 12 +- apps/web/app/(app)/onboarding/Role.tsx | 11 +- apps/web/app/(auth)/invite/page.tsx | 2 +- .../app/api/auth/[...nextauth]/authOptions.ts | 2 +- .../api/v1/client/people/getOrCreate/route.ts | 44 -- .../app/api/v1/users/reset-password/route.ts | 2 +- apps/web/app/api/v1/users/route.ts | 2 +- apps/web/app/s/[surveyId]/LegalFooter.tsx | 2 +- apps/web/app/s/[surveyId]/LinkSurvey.tsx | 197 +++----- apps/web/app/s/[surveyId]/SurveyInactive.tsx | 5 +- apps/web/app/s/[surveyId]/actions.ts | 2 +- apps/web/app/s/[surveyId]/helpers.ts | 21 + apps/web/app/s/[surveyId]/layout.tsx | 10 + apps/web/app/s/[surveyId]/page.tsx | 47 +- apps/web/app/s/[surveyId]/prefilling.ts | 116 +++++ apps/web/components/preview/BackButton.tsx | 18 - apps/web/components/preview/CTAQuestion.tsx | 71 --- .../components/preview/ConsentQuestion.tsx | 106 ---- apps/web/components/preview/Headline.tsx | 7 - apps/web/components/preview/HtmlBody.tsx | 10 - .../preview/MultipleChoiceMultiQuestion.tsx | 211 -------- .../preview/MultipleChoiceSingleQuestion.tsx | 172 ------- apps/web/components/preview/NPSQuestion.tsx | 116 ----- .../components/preview/OpenTextQuestion.tsx | 99 ---- apps/web/components/preview/Progress.tsx | 20 - .../preview/QuestionConditional.tsx | 104 ---- .../web/components/preview/RatingQuestion.tsx | 194 ------- .../components/preview/RedirectCountDown.tsx | 30 -- apps/web/components/preview/SubmitButton.tsx | 25 - apps/web/components/shared/Survey.tsx | 127 +++++ apps/web/lib/api/clientPerson.ts | 39 -- apps/web/lib/email.ts | 2 +- apps/web/lib/linkSurvey/linkSurvey.ts | 476 ------------------ apps/web/lib/templates.ts | 4 +- apps/web/package.json | 1 + .../teams/[teamId]/invite/[inviteId]/index.ts | 2 +- apps/web/tsconfig.json | 9 +- packages/js/app.html | 2 +- packages/js/package.json | 39 +- packages/js/src/App.tsx | 41 -- packages/js/src/components/BackButton.tsx | 21 - packages/js/src/components/CTAQuestion.tsx | 72 --- .../js/src/components/ConsentQuestion.tsx | 101 ---- .../js/src/components/FormbricksSignature.tsx | 17 - packages/js/src/components/Headline.tsx | 20 - .../MultipleChoiceMultiQuestion.tsx | 211 -------- .../MultipleChoiceSingleQuestion.tsx | 176 ------- packages/js/src/components/NPSQuestion.tsx | 124 ----- .../js/src/components/OpenTextQuestion.tsx | 99 ---- packages/js/src/components/Subheader.tsx | 9 - packages/js/src/components/SubmitButton.tsx | 20 - packages/js/src/components/SurveyView.tsx | 320 ------------ packages/js/src/components/ThankYouCard.tsx | 38 -- packages/js/src/index.ts | 2 +- packages/js/src/lib/init.ts | 3 - packages/js/src/lib/localStorage.ts | 24 - packages/js/src/lib/widget.ts | 51 +- packages/js/src/template.html | 35 -- packages/js/src/types/declaration.d.ts | 4 - packages/js/tsconfig.json | 65 +-- packages/js/tsup.config.ts | 11 + {apps/web => packages}/lib/jwt.ts | 0 packages/lib/responseQueue.ts | 83 +++ packages/lib/services/attributeClass.ts | 19 +- packages/lib/services/person.ts | 55 ++ packages/lib/services/survey.ts | 52 +- packages/lib/surveyState.ts | 65 +++ .../lib/utils/createDemoProductHelpers.ts | 4 +- packages/surveys/.eslintrc.cjs | 4 + packages/surveys/.gitignore | 24 + packages/surveys/LICENSE | 9 + packages/surveys/index.html | 13 + packages/surveys/package.json | 30 ++ packages/{js => surveys}/postcss.config.js | 4 +- .../src/components/AutoCloseWrapper.tsx | 60 +++ .../surveys/src/components/BackButton.tsx | 19 + .../surveys/src/components/CTAQuestion.tsx | 63 +++ .../src/components/ConsentQuestion.tsx | 78 +++ .../src/components}/FormbricksSignature.tsx | 4 +- packages/surveys/src/components/Headline.tsx | 16 + .../src/components/HtmlBody.tsx | 4 +- .../{js => surveys}/src/components/Modal.tsx | 113 +++-- .../MultipleChoiceMultiQuestion.tsx | 187 +++++++ .../MultipleChoiceSingleQuestion.tsx | 158 ++++++ .../surveys/src/components/NPSQuestion.tsx | 97 ++++ .../src/components/OpenTextQuestion.tsx | 88 ++++ .../src/components/Progress.tsx | 6 +- .../surveys/src/components/ProgressBar.tsx | 68 +++ .../src/components/QuestionConditional.tsx | 96 ++-- .../src/components/RatingQuestion.tsx | 107 ++-- .../src/components/RedirectCountdown.tsx | 44 ++ .../src/components/Smileys.tsx | 2 +- .../surveys/src/components}/Subheader.tsx | 2 +- .../surveys/src/components/SubmitButton.tsx | 27 + packages/surveys/src/components/Survey.tsx | 151 ++++++ .../surveys/src/components/SurveyInline.tsx | 32 ++ .../surveys/src/components/SurveyModal.tsx | 62 +++ .../surveys/src/components}/ThankYouCard.tsx | 24 +- packages/surveys/src/index.ts | 24 + packages/{js => surveys}/src/lib/cleanHtml.ts | 0 packages/surveys/src/lib/logicEvaluator.ts | 55 ++ packages/{js => surveys}/src/lib/styles.ts | 8 +- packages/{js => surveys}/src/lib/utils.ts | 9 +- .../src/styles/global.css} | 0 .../src => surveys/src/styles}/preflight.css | 0 packages/surveys/src/types/props.ts | 28 ++ packages/surveys/src/vite-env.d.ts | 1 + packages/{js => surveys}/tailwind.config.js | 5 +- packages/surveys/tsconfig.json | 27 + packages/surveys/tsconfig.node.json | 10 + packages/surveys/vite.config.ts | 17 + packages/tsconfig/package.json | 1 + packages/types/package.json | 1 + packages/types/v1/responses.ts | 7 + packages/types/v1/surveys.ts | 4 +- packages/types/v1/templates.ts | 28 ++ pnpm-lock.yaml | 156 +++++- 129 files changed, 2637 insertions(+), 4014 deletions(-) delete mode 100644 apps/web/app/api/v1/client/people/getOrCreate/route.ts create mode 100644 apps/web/app/s/[surveyId]/helpers.ts create mode 100644 apps/web/app/s/[surveyId]/layout.tsx create mode 100644 apps/web/app/s/[surveyId]/prefilling.ts delete mode 100644 apps/web/components/preview/BackButton.tsx delete mode 100644 apps/web/components/preview/CTAQuestion.tsx delete mode 100644 apps/web/components/preview/ConsentQuestion.tsx delete mode 100644 apps/web/components/preview/Headline.tsx delete mode 100644 apps/web/components/preview/HtmlBody.tsx delete mode 100644 apps/web/components/preview/MultipleChoiceMultiQuestion.tsx delete mode 100644 apps/web/components/preview/MultipleChoiceSingleQuestion.tsx delete mode 100644 apps/web/components/preview/NPSQuestion.tsx delete mode 100644 apps/web/components/preview/OpenTextQuestion.tsx delete mode 100644 apps/web/components/preview/Progress.tsx delete mode 100644 apps/web/components/preview/QuestionConditional.tsx delete mode 100644 apps/web/components/preview/RatingQuestion.tsx delete mode 100644 apps/web/components/preview/RedirectCountDown.tsx delete mode 100644 apps/web/components/preview/SubmitButton.tsx create mode 100644 apps/web/components/shared/Survey.tsx delete mode 100644 apps/web/lib/linkSurvey/linkSurvey.ts delete mode 100644 packages/js/src/App.tsx delete mode 100644 packages/js/src/components/BackButton.tsx delete mode 100644 packages/js/src/components/CTAQuestion.tsx delete mode 100644 packages/js/src/components/ConsentQuestion.tsx delete mode 100644 packages/js/src/components/FormbricksSignature.tsx delete mode 100644 packages/js/src/components/Headline.tsx delete mode 100644 packages/js/src/components/MultipleChoiceMultiQuestion.tsx delete mode 100644 packages/js/src/components/MultipleChoiceSingleQuestion.tsx delete mode 100644 packages/js/src/components/NPSQuestion.tsx delete mode 100644 packages/js/src/components/OpenTextQuestion.tsx delete mode 100644 packages/js/src/components/Subheader.tsx delete mode 100644 packages/js/src/components/SubmitButton.tsx delete mode 100644 packages/js/src/components/SurveyView.tsx delete mode 100644 packages/js/src/components/ThankYouCard.tsx delete mode 100644 packages/js/src/lib/localStorage.ts delete mode 100644 packages/js/src/template.html delete mode 100644 packages/js/src/types/declaration.d.ts create mode 100644 packages/js/tsup.config.ts rename {apps/web => packages}/lib/jwt.ts (100%) create mode 100644 packages/lib/responseQueue.ts create mode 100644 packages/lib/surveyState.ts create mode 100644 packages/surveys/.eslintrc.cjs create mode 100644 packages/surveys/.gitignore create mode 100644 packages/surveys/LICENSE create mode 100644 packages/surveys/index.html create mode 100644 packages/surveys/package.json rename packages/{js => surveys}/postcss.config.js (73%) create mode 100644 packages/surveys/src/components/AutoCloseWrapper.tsx create mode 100644 packages/surveys/src/components/BackButton.tsx create mode 100644 packages/surveys/src/components/CTAQuestion.tsx create mode 100644 packages/surveys/src/components/ConsentQuestion.tsx rename {apps/web/components/preview => packages/surveys/src/components}/FormbricksSignature.tsx (76%) create mode 100644 packages/surveys/src/components/Headline.tsx rename packages/{js => surveys}/src/components/HtmlBody.tsx (71%) rename packages/{js => surveys}/src/components/Modal.tsx (51%) create mode 100644 packages/surveys/src/components/MultipleChoiceMultiQuestion.tsx create mode 100644 packages/surveys/src/components/MultipleChoiceSingleQuestion.tsx create mode 100644 packages/surveys/src/components/NPSQuestion.tsx create mode 100644 packages/surveys/src/components/OpenTextQuestion.tsx rename packages/{js => surveys}/src/components/Progress.tsx (57%) create mode 100644 packages/surveys/src/components/ProgressBar.tsx rename packages/{js => surveys}/src/components/QuestionConditional.tsx (54%) rename packages/{js => surveys}/src/components/RatingQuestion.tsx (66%) create mode 100644 packages/surveys/src/components/RedirectCountdown.tsx rename packages/{js => surveys}/src/components/Smileys.tsx (99%) rename {apps/web/components/preview => packages/surveys/src/components}/Subheader.tsx (87%) create mode 100644 packages/surveys/src/components/SubmitButton.tsx create mode 100644 packages/surveys/src/components/Survey.tsx create mode 100644 packages/surveys/src/components/SurveyInline.tsx create mode 100644 packages/surveys/src/components/SurveyModal.tsx rename {apps/web/components/preview => packages/surveys/src/components}/ThankYouCard.tsx (66%) create mode 100644 packages/surveys/src/index.ts rename packages/{js => surveys}/src/lib/cleanHtml.ts (100%) create mode 100644 packages/surveys/src/lib/logicEvaluator.ts rename packages/{js => surveys}/src/lib/styles.ts (62%) rename packages/{js => surveys}/src/lib/utils.ts (80%) rename packages/{js/src/style.css => surveys/src/styles/global.css} (100%) rename packages/{js/src => surveys/src/styles}/preflight.css (100%) create mode 100644 packages/surveys/src/types/props.ts create mode 100644 packages/surveys/src/vite-env.d.ts rename packages/{js => surveys}/tailwind.config.js (83%) create mode 100644 packages/surveys/tsconfig.json create mode 100644 packages/surveys/tsconfig.node.json create mode 100644 packages/surveys/vite.config.ts create mode 100644 packages/types/v1/templates.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/DeletePersonButton.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/DeletePersonButton.tsx index 2e233bce4d8..5b0de95cbec 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/DeletePersonButton.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/DeletePersonButton.tsx @@ -22,6 +22,7 @@ export function DeletePersonButton({ environmentId, personId }: DeletePersonButt try { setIsDeletingPerson(true); await deletePersonAction(personId); + router.refresh(); router.push(`/environments/${environmentId}/people`); toast.success("Person deleted successfully."); } catch (error) { diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/loading.tsx index 084039fcdb0..460b1f6e010 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/loading.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/loading.tsx @@ -1,10 +1,11 @@ -import { ArrowsUpDownIcon, TrashIcon } from "@heroicons/react/24/outline"; import { - ActivityItemPopover, ActivityItemIcon, + ActivityItemPopover, } from "@/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivityItemComponents"; -import { BackIcon } from "@formbricks/ui"; import { TActivityFeedItem } from "@formbricks/types/v1/activity"; +import { BackIcon } from "@formbricks/ui"; +import { ArrowsUpDownIcon } from "@heroicons/react/24/outline"; +import { TrashIcon } from "lucide-react"; export default function Loading() { const unifiedList: TActivityFeedItem[] = [ diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx index 72327aa1db3..a25b8ef3af3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx @@ -1,335 +1,55 @@ "use client"; -import FormbricksSignature from "@/components/preview/FormbricksSignature"; import Modal from "@/components/preview/Modal"; -import Progress from "@/components/preview/Progress"; -import QuestionConditional from "@/components/preview/QuestionConditional"; import TabOption from "@/components/preview/TabOption"; -import ThankYouCard from "@/components/preview/ThankYouCard"; -import type { Logic, Question } from "@formbricks/types/questions"; +import { SurveyInline } from "@/components/shared/Survey"; import { Survey } from "@formbricks/types/surveys"; import type { TEnvironment } from "@formbricks/types/v1/environment"; import type { TProduct } from "@formbricks/types/v1/product"; +import { TSurvey } from "@formbricks/types/v1/surveys"; import { Button } from "@formbricks/ui"; import { ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline"; import { ComputerDesktopIcon, DevicePhoneMobileIcon } from "@heroicons/react/24/solid"; import { useEffect, useRef, useState } from "react"; + interface PreviewSurveyProps { + survey: TSurvey | Survey; setActiveQuestionId: (id: string | null) => void; activeQuestionId?: string | null; - questions: Question[]; - brandColor: string; environmentId: string; - surveyType: Survey["type"]; - thankYouCard: Survey["thankYouCard"]; - autoClose: Survey["autoClose"]; previewType?: "modal" | "fullwidth" | "email"; product: TProduct; environment: TEnvironment; } -function QuestionRenderer({ - activeQuestionId, - lastActiveQuestionId, - questions, - brandColor, - thankYouCard, - gotoNextQuestion, - showBackButton, - goToPreviousQuestion, - storedResponseValue, -}) { - return ( -
- {(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? ( - - ) : ( - questions.map((question, idx) => - (activeQuestionId || lastActiveQuestionId) === question.id ? ( - - ) : null - ) - )} -
- ); -} - -function PreviewModalContent({ - activeQuestionId, - lastActiveQuestionId, - questions, - brandColor, - thankYouCard, - gotoNextQuestion, - showBackButton, - goToPreviousQuestion, - storedResponseValue, - showFormbricksSignature, -}) { - return ( -
- - - {showFormbricksSignature && } -
- ); -} - export default function PreviewSurvey({ + survey, setActiveQuestionId, activeQuestionId, - questions, - brandColor, - surveyType, - thankYouCard, - autoClose, previewType, product, environment, }: PreviewSurveyProps) { const [isModalOpen, setIsModalOpen] = useState(true); - const [progress, setProgress] = useState(0); // [0, 1] const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false); - const [lastActiveQuestionId, setLastActiveQuestionId] = useState(""); - const [showFormbricksSignature, setShowFormbricksSignature] = useState(false); - const [finished, setFinished] = useState(false); - const [storedResponseValue, setStoredResponseValue] = useState(); - const [storedResponse, setStoredResponse] = useState>({}); const [previewMode, setPreviewMode] = useState("desktop"); - const showBackButton = progress !== 0 && !finished; const ContentRef = useRef(null); - useEffect(() => { - if (product) { - setShowFormbricksSignature(product.formbricksSignature); - } - }, [product]); - - const [countdownProgress, setCountdownProgress] = useState(1); - const startRef = useRef(performance.now()); - const frameRef = useRef(null); - const [countdownStop, setCountdownStop] = useState(false); - - const handleStopCountdown = () => { - if (frameRef.current !== null) { - cancelAnimationFrame(frameRef.current); - setCountdownStop(true); - } - }; - - useEffect(() => { - if (!autoClose) return; - if (frameRef.current !== null) { - cancelAnimationFrame(frameRef.current); - } - - const frame = () => { - if (!autoClose || !startRef.current) return; - - const timeout = autoClose * 1000; - const elapsed = performance.now() - startRef.current; - const remaining = Math.max(0, timeout - elapsed); - - setCountdownProgress(remaining / timeout); - - if (remaining > 0) { - frameRef.current = requestAnimationFrame(frame); - } else { - handleStopCountdown(); - setIsModalOpen(false); - // reopen the modal after 1 second - setTimeout(() => { - setIsModalOpen(true); - setActiveQuestionId(questions[0]?.id || ""); // set first question as active - }, 1500); - } - }; - - setCountdownStop(false); - setCountdownProgress(1); - startRef.current = performance.now(); - frameRef.current = requestAnimationFrame(frame); - - return () => { - if (frameRef.current !== null) { - cancelAnimationFrame(frameRef.current); - } - }; - }, [autoClose]); - - useEffect(() => { - if (ContentRef.current) { - // scroll to top whenever question changes - ContentRef.current.scrollTop = 0; - } - if (activeQuestionId !== "end") { - setFinished(false); - } - if (activeQuestionId) { - setLastActiveQuestionId(activeQuestionId); - setProgress(calculateProgress(questions, activeQuestionId)); - } else if (lastActiveQuestionId) { - setProgress(calculateProgress(questions, lastActiveQuestionId)); - } - - function calculateProgress(questions, activeQuestionId) { - if (activeQuestionId === "thank-you-card") return 1; - - const elementIdx = questions.findIndex((e) => e.id === activeQuestionId); - return elementIdx / questions.length; - } - }, [activeQuestionId, lastActiveQuestionId, questions]); - useEffect(() => { // close modal if there are no questions left - if (surveyType === "web" && !thankYouCard.enabled) { + if (survey.type === "web" && !survey.thankYouCard.enabled) { if (activeQuestionId === "thank-you-card") { setIsModalOpen(false); setTimeout(() => { - setActiveQuestionId(questions[0].id); - setIsModalOpen(true); - }, 500); - } - } - }, [activeQuestionId, surveyType, questions, setActiveQuestionId, thankYouCard]); - - function evaluateCondition(logic: Logic, responseValue: any): boolean { - switch (logic.condition) { - case "equals": - return ( - (Array.isArray(responseValue) && - responseValue.length === 1 && - responseValue.includes(logic.value)) || - responseValue.toString() === logic.value - ); - case "notEquals": - return responseValue !== logic.value; - case "lessThan": - return logic.value !== undefined && responseValue < logic.value; - case "lessEqual": - return logic.value !== undefined && responseValue <= logic.value; - case "greaterThan": - return logic.value !== undefined && responseValue > logic.value; - case "greaterEqual": - return logic.value !== undefined && responseValue >= logic.value; - case "includesAll": - return ( - Array.isArray(responseValue) && - Array.isArray(logic.value) && - logic.value.every((v) => responseValue.includes(v)) - ); - case "includesOne": - return ( - Array.isArray(responseValue) && - Array.isArray(logic.value) && - logic.value.some((v) => responseValue.includes(v)) - ); - case "accepted": - return responseValue === "accepted"; - case "clicked": - return responseValue === "clicked"; - case "submitted": - if (typeof responseValue === "string") { - return responseValue !== "dismissed" && responseValue !== "" && responseValue !== null; - } else if (Array.isArray(responseValue)) { - return responseValue.length > 0; - } else if (typeof responseValue === "number") { - return responseValue !== null; - } - return false; - case "skipped": - return ( - (Array.isArray(responseValue) && responseValue.length === 0) || - responseValue === "" || - responseValue === null || - responseValue === "dismissed" - ); - default: - return false; - } - } - - function getNextQuestion(answer: any): string { - // extract activeQuestionId from answer to make it work when form is collapsed. - const activeQuestionId = Object.keys(answer)[0]; - if (!activeQuestionId) return ""; - - const currentQuestionIndex = questions.findIndex((q) => q.id === activeQuestionId); - if (currentQuestionIndex === -1) throw new Error("Question not found"); - - const responseValue = answer[activeQuestionId]; - const currentQuestion = questions[currentQuestionIndex]; - - if (currentQuestion.logic && currentQuestion.logic.length > 0) { - for (let logic of currentQuestion.logic) { - if (!logic.destination) continue; - - if (evaluateCondition(logic, responseValue)) { - return logic.destination; - } - } - } - return questions[currentQuestionIndex + 1]?.id || "end"; - } - - const gotoNextQuestion = (data) => { - setStoredResponse({ ...storedResponse, ...data }); - const nextQuestionId = getNextQuestion(data); - setStoredResponseValue(storedResponse[nextQuestionId]); - if (nextQuestionId !== "end") { - setActiveQuestionId(nextQuestionId); - } else { - setFinished(true); - if (thankYouCard?.enabled) { - setActiveQuestionId("thank-you-card"); - setProgress(1); - } else { - setIsModalOpen(false); - setTimeout(() => { - setActiveQuestionId(questions[0].id); + setActiveQuestionId(survey.questions[0].id); setIsModalOpen(true); }, 500); } } - }; - - function goToPreviousQuestion(data: any) { - setStoredResponse({ ...storedResponse, ...data }); - const currentQuestionIndex = questions.findIndex((q) => q.id === activeQuestionId); - if (currentQuestionIndex === -1) throw new Error("Question not found"); - const previousQuestionId = questions[currentQuestionIndex - 1].id; - setStoredResponseValue(storedResponse[previousQuestionId]); - setActiveQuestionId(previousQuestionId); - } + }, [activeQuestionId, survey.type, survey, setActiveQuestionId]); function resetQuestionProgress() { - setProgress(0); - setActiveQuestionId(questions[0].id); - setStoredResponse({}); + setActiveQuestionId(survey.questions[0].id); } useEffect(() => { @@ -365,22 +85,14 @@ export default function PreviewSurvey({ placement={product.placement} highlightBorderColor={product.highlightBorderColor} previewMode="mobile"> - {!countdownStop && autoClose !== null && autoClose > 0 && ( - - )} - - ) : (
-
-
-
- - {showFormbricksSignature && } -
-
)} @@ -432,46 +134,29 @@ export default function PreviewSurvey({ placement={product.placement} highlightBorderColor={product.highlightBorderColor} previewMode="desktop"> - {!countdownStop && autoClose !== null && autoClose > 0 && ( - - )} - - ) : (
-
-
-
- - {showFormbricksSignature && } -
-
)} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/SurveyStarter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/SurveyStarter.tsx index 5572a70a02f..7878b3b755a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/SurveyStarter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/SurveyStarter.tsx @@ -1,10 +1,10 @@ "use client"; -import { Template } from "@/../../packages/types/templates"; import { createSurveyAction } from "./actions"; import TemplateList from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList"; import LoadingSpinner from "@/components/shared/LoadingSpinner"; import type { TEnvironment } from "@formbricks/types/v1/environment"; import type { TProduct } from "@formbricks/types/v1/product"; +import { TTemplate } from "@formbricks/types/v1/templates"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "react-hot-toast"; @@ -20,7 +20,7 @@ export default function SurveyStarter({ }) { const [isCreateSurveyLoading, setIsCreateSurveyLoading] = useState(false); const router = useRouter(); - const newSurveyFromTemplate = async (template: Template) => { + const newSurveyFromTemplate = async (template: TTemplate) => { setIsCreateSurveyLoading(true); const surveyType = environment?.widgetSetupCompleted ? "web" : "link"; const autoComplete = surveyType === "web" ? 50 : null; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/CTAQuestionForm.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/CTAQuestionForm.tsx index bd628fc9683..27321878ca1 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/CTAQuestionForm.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/CTAQuestionForm.tsx @@ -101,39 +101,37 @@ export default function CTAQuestionForm({ /> )} + - {question.buttonExternal && ( -
- -
- updateQuestion(questionIdx, { buttonUrl: e.target.value })} - /> -
+ {question.buttonExternal && ( +
+ +
+ updateQuestion(questionIdx, { buttonUrl: e.target.value })} + />
- )} -
+
+ )} -
- {!question.required && ( -
- -
- updateQuestion(questionIdx, { dismissButtonLabel: e.target.value })} - /> -
+ {!question.required && ( +
+ +
+ updateQuestion(questionIdx, { dismissButtonLabel: e.target.value })} + />
- )} -
+
+ )} ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/EditThankYouCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/EditThankYouCard.tsx index d03410c4e83..bea3cce96ce 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/EditThankYouCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/EditThankYouCard.tsx @@ -19,10 +19,10 @@ export default function EditThankYouCard({ activeQuestionId, }: EditThankYouCardProps) { // const [open, setOpen] = useState(false); - let open = activeQuestionId == "thank-you-card"; + let open = activeQuestionId == "end"; const setOpen = (e) => { if (e) { - setActiveQuestionId("thank-you-card"); + setActiveQuestionId("end"); } else { setActiveQuestionId(null); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx index 24e944218a4..cbb0d9694e3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx @@ -239,32 +239,32 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res {/* Redirect on completion */} + +
+
+

+ Redirect respondents here: +

+ handleRedirectUrlChange(e.target.value)} + /> +
+
+
+ {localSurvey.type === "link" && ( <> - -
-
-

- Redirect respondents here: -

- handleRedirectUrlChange(e.target.value)} - /> -
-
-
- {/* Adjust Survey Closed Message */}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx index 1055cf571f7..55daa0b79fb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx @@ -1,10 +1,10 @@ "use client"; import { useState } from "react"; -import type { Template } from "@formbricks/types/templates"; +import type { TTemplate } from "@formbricks/types/v1/templates"; import { useEffect } from "react"; import { replacePresetPlaceholders } from "@/lib/templates"; -import { templates } from "./templates"; +import { minimalSurvey, templates } from "./templates"; import PreviewSurvey from "../PreviewSurvey"; import TemplateList from "./TemplateList"; import type { TProduct } from "@formbricks/types/v1/product"; @@ -22,7 +22,7 @@ export default function TemplateContainerWithPreview({ product, environment, }: TemplateContainerWithPreviewProps) { - const [activeTemplate, setActiveTemplate] = useState