From 41443267c98ea1afeef355d32c442e2d46d18952 Mon Sep 17 00:00:00 2001 From: Johannes <72809645+jobenjada@users.noreply.github.com> Date: Fri, 19 May 2023 08:33:52 +0200 Subject: [PATCH] Revamp survey settings (#287) * improve survey settings flow --------- Co-authored-by: Matthias Nannt --- .../components/dummyUI/templates.ts | 12 +- .../best-practices/docs-feedback/index.mdx | 2 +- .../[environmentId]/AddProductModal.tsx | 6 +- .../[environmentId]/EnvironmentsNavbar.tsx | 16 +- .../[environmentId]/surveys/PreviewSurvey.tsx | 165 ++++++++++-------- .../[environmentId]/surveys/SurveyList.tsx | 12 +- .../surveys/[surveyId]/edit/AudienceView.tsx | 8 +- .../surveys/[surveyId]/edit/HowToSendCard.tsx | 110 ++++++++---- .../surveys/[surveyId]/edit/QuestionCard.tsx | 7 +- .../[surveyId]/edit/QuestionsAudienceTabs.tsx | 18 +- .../surveys/[surveyId]/edit/QuestionsView.tsx | 2 +- .../[surveyId]/edit/RecontactOptionsCard.tsx | 52 ++++-- .../[surveyId]/edit/ResponseOptionsCard.tsx | 7 +- .../surveys/[surveyId]/edit/SurveyEditor.tsx | 17 +- .../surveys/[surveyId]/edit/SurveyMenuBar.tsx | 85 ++++++--- .../[surveyId]/edit/WhenToSendCard.tsx | 58 ++++-- .../surveys/[surveyId]/edit/WhoToSendCard.tsx | 34 ++-- .../[surveyId]/summary/SummaryMetadata.tsx | 18 +- .../surveys/templates/TemplateList.tsx | 63 +++++-- .../surveys/templates/page.tsx | 58 +++--- .../surveys/templates/templates.ts | 12 +- apps/web/app/onboarding/Product.tsx | 2 +- apps/web/components/preview/Modal.tsx | 36 +--- .../shared/SurveyStatusDropdown.tsx | 29 +-- apps/web/lib/responses/responses.ts | 9 +- .../[environmentId]/product/index.ts | 6 +- .../migration.sql | 2 + packages/database/prisma/schema.prisma | 2 +- packages/ui/components/Button.tsx | 2 +- 29 files changed, 523 insertions(+), 327 deletions(-) create mode 100644 packages/database/prisma/migrations/20230517145313_change_brand_color_default/migration.sql diff --git a/apps/formbricks-com/components/dummyUI/templates.ts b/apps/formbricks-com/components/dummyUI/templates.ts index 5466aa6802..b575639868 100644 --- a/apps/formbricks-com/components/dummyUI/templates.ts +++ b/apps/formbricks-com/components/dummyUI/templates.ts @@ -32,8 +32,8 @@ const thankYouCardDefault = { }; export const customSurvey: Template = { - name: "Custom Survey", - description: "Create your survey from scratch.", + name: "Start from scratch", + description: "Create a survey without template.", icon: null, preset: { name: "New Survey", @@ -41,8 +41,8 @@ export const customSurvey: Template = { { id: createId(), type: "openText", - headline: "What's poppin?", - subheader: "This can help us improve your experience.", + headline: "Custom Survey", + subheader: "This is an example survey.", placeholder: "Type your answer here...", required: true, }, @@ -226,7 +226,7 @@ export const templates: Template[] = [ }, { id: createId(), - label: "in a Podcast", + label: "In a Podcast", }, ], }, @@ -336,7 +336,7 @@ export const templates: Template[] = [ }, { id: createId(), - label: "in a Podcast", + label: "In a Podcast", }, ], }, diff --git a/apps/formbricks-com/pages/docs/best-practices/docs-feedback/index.mdx b/apps/formbricks-com/pages/docs/best-practices/docs-feedback/index.mdx index 0793efe392..99237131a4 100644 --- a/apps/formbricks-com/pages/docs/best-practices/docs-feedback/index.mdx +++ b/apps/formbricks-com/pages/docs/best-practices/docs-feedback/index.mdx @@ -78,7 +78,7 @@ To get this running, you'll need a bit of time. Here are the steps we're going t to be identical to the frontend we're building in the next step. -6. Click on “Continue to Audience” or select the audience tab manually. Scroll down to “When to ask” and create a new Action: +6. Click on “Continue to Settings or select the audience tab manually. Scroll down to “When to ask” and create a new Action: { + setLoading(true); const newEnv = await createProduct(environmentId, data); router.push(`/environments/${newEnv.id}/`); setOpen(false); + setLoading(false); }; return ( @@ -58,7 +62,7 @@ export default function AddProductModal({ environmentId, open, setOpen }: AddPro }}> Cancel - diff --git a/apps/web/app/environments/[environmentId]/EnvironmentsNavbar.tsx b/apps/web/app/environments/[environmentId]/EnvironmentsNavbar.tsx index 695572808c..0dd64e3af7 100644 --- a/apps/web/app/environments/[environmentId]/EnvironmentsNavbar.tsx +++ b/apps/web/app/environments/[environmentId]/EnvironmentsNavbar.tsx @@ -303,7 +303,21 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
-

{truncate(environment?.product?.name, 20)}

+
+

{truncate(environment?.product?.name, 20)}

+ {!widgetSetupCompleted && ( + + + +
+
+ +

Your app is not connected to Formbricks.

+
+
+
+ )} +

Product

diff --git a/apps/web/app/environments/[environmentId]/surveys/PreviewSurvey.tsx b/apps/web/app/environments/[environmentId]/surveys/PreviewSurvey.tsx index 1a932f889a..e01e934ff0 100644 --- a/apps/web/app/environments/[environmentId]/surveys/PreviewSurvey.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/PreviewSurvey.tsx @@ -2,44 +2,55 @@ import Modal from "@/components/preview/Modal"; import Progress from "@/components/preview/Progress"; import QuestionConditional from "@/components/preview/QuestionConditional"; import ThankYouCard from "@/components/preview/ThankYouCard"; -import ContentWrapper from "@/components/shared/ContentWrapper"; +import { useEnvironment } from "@/lib/environments/environments"; import type { Question } from "@formbricks/types/questions"; import { Survey } from "@formbricks/types/surveys"; -import { ArrowPathIcon } from "@heroicons/react/24/solid"; import { useEffect, useState } from "react"; interface PreviewSurveyProps { - localSurvey?: Survey; setActiveQuestionId: (id: string | null) => void; activeQuestionId?: string | null; questions: Question[]; brandColor: string; + environmentId: string; + surveyType: Survey["type"]; + thankYouCard: Survey["thankYouCard"]; + previewType?: "modal" | "fullwidth" | "email"; } export default function PreviewSurvey({ - localSurvey, setActiveQuestionId, activeQuestionId, questions, brandColor, + environmentId, + surveyType, + thankYouCard, + previewType, }: PreviewSurveyProps) { const [isModalOpen, setIsModalOpen] = useState(true); const [progress, setProgress] = useState(0); // [0, 1] + const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false); + const { environment } = useEnvironment(environmentId); + const [lastActiveQuestionId, setLastActiveQuestionId] = useState(""); useEffect(() => { - if (activeQuestionId && localSurvey) { - setProgress(calculateProgress(localSurvey)); + if (activeQuestionId) { + setLastActiveQuestionId(activeQuestionId); + setProgress(calculateProgress(questions, activeQuestionId)); + } else if (lastActiveQuestionId) { + setProgress(calculateProgress(questions, lastActiveQuestionId)); } - function calculateProgress(survey) { - const elementIdx = survey.questions.findIndex((e) => e.id === activeQuestionId); - return elementIdx / survey.questions.length; + function calculateProgress(questions, id) { + const elementIdx = questions.findIndex((e) => e.id === id); + return elementIdx / questions.length; } - }, [activeQuestionId, localSurvey]); + }, [activeQuestionId, lastActiveQuestionId, questions]); useEffect(() => { // close modal if there are no questions left - if (localSurvey?.type === "web" && !localSurvey?.thankYouCard.enabled) { + if (surveyType === "web" && !thankYouCard.enabled) { if (activeQuestionId === "thank-you-card") { setIsModalOpen(false); setTimeout(() => { @@ -48,14 +59,16 @@ export default function PreviewSurvey({ }, 500); } } - }, [activeQuestionId, localSurvey, questions, setActiveQuestionId]); + }, [activeQuestionId, surveyType, questions, setActiveQuestionId, thankYouCard]); const gotoNextQuestion = () => { - const currentIndex = questions.findIndex((q) => q.id === activeQuestionId); + const currentQuestionId = activeQuestionId || lastActiveQuestionId; + const currentIndex = questions.findIndex((q) => q.id === currentQuestionId); + if (currentIndex < questions.length - 1) { setActiveQuestionId(questions[currentIndex + 1].id); } else { - if (localSurvey?.thankYouCard?.enabled) { + if (thankYouCard?.enabled) { setActiveQuestionId("thank-you-card"); } else { setIsModalOpen(false); @@ -63,7 +76,7 @@ export default function PreviewSurvey({ setActiveQuestionId(questions[0].id); setIsModalOpen(true); }, 500); - if (localSurvey?.thankYouCard?.enabled) { + if (thankYouCard?.enabled) { setActiveQuestionId("thank-you-card"); setProgress(1); } else { @@ -77,82 +90,94 @@ export default function PreviewSurvey({ } }; - const resetPreview = () => { + /* const resetPreview = () => { setIsModalOpen(false); setTimeout(() => { setActiveQuestionId(questions[0].id); setIsModalOpen(true); }, 500); }; + */ - if (!activeQuestionId) { - return null; + useEffect(() => { + if (environment && environment.widgetSetupCompleted) { + setWidgetSetupCompleted(true); + } else { + setWidgetSetupCompleted(false); + } + }, [environment]); + + if (!previewType) { + previewType = widgetSetupCompleted ? "modal" : "fullwidth"; } return ( - <> - {localSurvey?.type === "link" ? ( -
-
- - Preview -
-
- - {activeQuestionId == "thank-you-card" ? ( +
+
+
+
+
+
+
+

+ {previewType === "modal" &&

Your web app

} +

+
+ + {previewType === "modal" ? ( + + {(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? ( + + ) : ( + questions.map((question, idx) => + (activeQuestionId || lastActiveQuestionId) === question.id ? ( + + ) : null + ) + )} + + ) : ( +
+
+
+ {(activeQuestionId || lastActiveQuestionId) === "thank-you-card" ? ( ) : ( - questions.map( - (question, idx) => - activeQuestionId === question.id && ( - - ) + questions.map((question, idx) => + (activeQuestionId || lastActiveQuestionId) === question.id ? ( + + ) : null ) )} - +
-
-
+
+
- ) : ( - - {activeQuestionId == "thank-you-card" ? ( - - ) : ( - questions.map( - (question, idx) => - activeQuestionId === question.id && ( - - ) - ) - )} - )} - +
); } diff --git a/apps/web/app/environments/[environmentId]/surveys/SurveyList.tsx b/apps/web/app/environments/[environmentId]/surveys/SurveyList.tsx index e803803ef0..4c1192977e 100644 --- a/apps/web/app/environments/[environmentId]/surveys/SurveyList.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/SurveyList.tsx @@ -11,6 +11,7 @@ import { } from "@/components/shared/DropdownMenu"; import LoadingSpinner from "@/components/shared/LoadingSpinner"; import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator"; +import { useEnvironment } from "@/lib/environments/environments"; import { useProfile } from "@/lib/profile"; import { createSurvey, deleteSurvey, duplicateSurvey, useSurveys } from "@/lib/surveys/surveys"; import { Badge, ErrorComponent } from "@formbricks/ui"; @@ -33,6 +34,7 @@ export default function SurveysList({ environmentId }) { const router = useRouter(); const { surveys, mutateSurveys, isLoadingSurveys, isErrorSurveys } = useSurveys(environmentId); const { isLoadingProfile, isErrorProfile } = useProfile(); + const { environment } = useEnvironment(environmentId); const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isCreateSurveyLoading, setIsCreateSurveyLoading] = useState(false); @@ -46,8 +48,12 @@ export default function SurveysList({ environmentId }) { const newSurveyFromTemplate = async (template: Template) => { setIsCreateSurveyLoading(true); + const augmentedTemplate = { + ...template.preset, + type: environment?.widgetSetupCompleted ? "web" : "link", + }; try { - const survey = await createSurvey(environmentId, template.preset); + const survey = await createSurvey(environmentId, augmentedTemplate); router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`); } catch (e) { toast.error("An error occured creating a new survey"); @@ -89,7 +95,7 @@ export default function SurveysList({ environmentId }) { if (surveys.length === 0) { return ( -
+
{isCreateSurveyLoading ? ( ) : ( @@ -116,7 +122,7 @@ export default function SurveysList({ environmentId }) {
    +

    {product.name} /

    { const updatedSurvey = { ...localSurvey, name: e.target.value }; setLocalSurvey(updatedSurvey); }} - className="w-72" + className="w-72 border-white hover:border-slate-200 " /> -
    +
    + {localSurvey?.responseRate && ( +
    + + This survey received responses. To keep the data consistent, make changes with caution. +
    + )} +
    +
    -
    -
    {localSurvey.status === "draft" && audiencePrompt && ( )} {localSurvey.status === "draft" && !audiencePrompt && ( @@ -105,16 +141,23 @@ export default function SurveyMenuBar({ localSurvey.type === "web" && (localSurvey.triggers[0] === "" || localSurvey.triggers.length === 0) } - variant="highlight" + variant="darkCTA" loading={isMutatingSurvey} onClick={async () => { await triggerSurveyMutate({ ...localSurvey, status: "inProgress" }); router.push(`/environments/${environmentId}/surveys/${localSurvey.id}/summary?success=true`); }}> - Publish Survey + Publish )}
    + deleteSurveyAction(localSurvey)} + text="Do you want to delete this draft?" + />
); } diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhenToSendCard.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhenToSendCard.tsx index d9b8f3701a..7be3bf8c4b 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhenToSendCard.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhenToSendCard.tsx @@ -5,6 +5,7 @@ import { useEventClasses } from "@/lib/eventClasses/eventClasses"; import { cn } from "@formbricks/lib/cn"; import type { Survey } from "@formbricks/types/surveys"; import { + Badge, Button, Select, SelectContent, @@ -25,7 +26,7 @@ interface WhenToSendCardProps { } export default function WhenToSendCard({ environmentId, localSurvey, setLocalSurvey }: WhenToSendCardProps) { - const [open, setOpen] = useState(true); + const [open, setOpen] = useState(localSurvey.type === "web" ? true : false); const { eventClasses, isLoadingEventClasses, isErrorEventClasses, mutateEventClasses } = useEventClasses(environmentId); const [isAddEventModalOpen, setAddEventModalOpen] = useState(false); @@ -48,6 +49,12 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur setLocalSurvey(updatedSurvey); }; + useEffect(() => { + if (localSurvey.type === "link") { + setOpen(false); + } + }, [localSurvey.type]); + //create new empty trigger on page load, remove one click for user useEffect(() => { if (localSurvey.triggers.length === 0) { @@ -63,35 +70,60 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur return
Error
; } - if (localSurvey.type === "link") { + /* if (localSurvey.type === "link") { return null; - } + } */ return ( <> - -
+ onOpenChange={(openState) => { + if (localSurvey.type !== "link") { + setOpen(openState); + } + }} + className="w-full rounded-lg border border-slate-300 bg-white"> + +
{localSurvey.triggers.length === 0 || !localSurvey.triggers[0] ? ( -
+
) : ( - + )}
-

When to ask

+

Survey Trigger

Choose the actions which trigger the survey.

+ {localSurvey.type === "link" && ( +
+ +
+ )}
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhoToSendCard.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhoToSendCard.tsx index 7e14f20716..69d01c4c84 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhoToSendCard.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/WhoToSendCard.tsx @@ -34,6 +34,12 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv } }, [isLoadingAttributeClasses]); + useEffect(() => { + if (localSurvey.type === "link") { + setOpen(false); + } + }, [localSurvey.type]); + const addAttributeFilter = () => { const updatedSurvey = { ...localSurvey }; updatedSurvey.attributeFilters = [ @@ -66,26 +72,26 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv return
Error
; } - if (localSurvey.type === "link") { - return null; - } - return ( <> - + className="w-full rounded-lg border border-slate-300 bg-white"> +
-

Who to ask

+

Target Audience

Pre-segment your users with attributes filters.

@@ -98,21 +104,21 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv
{localSurvey.attributeFilters.length === 0 ? ( - + ) : ( - + )}

- Audience:{" "} + Current:{" "} {localSurvey.attributeFilters.length === 0 ? "All users" : "Filtered"}

{localSurvey.attributeFilters.length === 0 - ? "Currently, all users might see the survey." + ? "All users can see the survey." : "Only users who match the attribute filter will see the survey."}

diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx index cedac19272..53725ff50d 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx @@ -15,7 +15,7 @@ import { TooltipTrigger, } from "@formbricks/ui"; import { ShareIcon } from "@heroicons/react/24/outline"; -import { PencilSquareIcon } from "@heroicons/react/24/solid"; +import { PencilSquareIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/solid"; import { useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import toast from "react-hot-toast"; @@ -84,7 +84,10 @@ export default function SummaryMetadata({ surveyId, environmentId }) {
-

Response Rate

+

+ Response % + +

{survey.responseRate === null || survey.responseRate === 0 ? ( - @@ -102,8 +105,11 @@ export default function SummaryMetadata({ surveyId, environmentId }) { -

-

Completion Rate

+
+

+ Completion % + +

{responses.length === 0 ? ( - @@ -133,9 +139,9 @@ export default function SummaryMetadata({ surveyId, environmentId }) { )} - {environment.widgetSetupCompleted && ( + {environment.widgetSetupCompleted || survey.type === "link" ? ( - )} + ) : null}