From 8a7b16effc967eb3927ecea63da45ce764d0d78e Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:00:02 +0530 Subject: [PATCH] Add option to redirect after completed link survey (#408) * completed * resolved migration error * added redirect timer and url check * rename redirectLink to redirectUrl --------- Co-authored-by: Matthias Nannt --- .../surveys/[surveyId]/edit/AudienceView.tsx | 2 +- .../[surveyId]/edit/ResponseOptionsCard.tsx | 53 ++++++++++++++++++- apps/web/app/s/[surveyId]/LinkSurvey.tsx | 2 + .../components/preview/RedirectCountDown.tsx | 33 ++++++++++++ apps/web/components/preview/ThankYouCard.tsx | 5 +- apps/web/lib/linkSurvey/linkSurvey.ts | 18 ++++++- .../api/v1/client/surveys/[surveyId]/index.ts | 1 + packages/database/schema.prisma | 1 + packages/types/surveys.ts | 1 + packages/types/v1/surveys.ts | 1 + 10 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 apps/web/components/preview/RedirectCountDown.tsx diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/AudienceView.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/AudienceView.tsx index 6b978e32aa..e37b7165f4 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/AudienceView.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/AudienceView.tsx @@ -32,7 +32,7 @@ export default function AudienceView({ environmentId, localSurvey, setLocalSurve environmentId={environmentId} /> - + {localSurvey.type==="link" && } (""); + + const handleRedirectCheckMark = () => { + if (redirectToggle && localSurvey.redirectUrl) { + setRedirectToggle(false); + setRedirectUrl(null); + setLocalSurvey({ ...localSurvey, redirectUrl: null }); + return; + } + if (redirectToggle) { + setRedirectToggle(false); + return; + } + setRedirectToggle(true); + }; + + const handleRedirectUrlChange = (link: string) => { + setRedirectUrl(link); + setLocalSurvey({ ...localSurvey, redirectUrl: link }); + }; + + useEffect(() => { + if (localSurvey.redirectUrl) { + setRedirectUrl(localSurvey.redirectUrl); + setRedirectToggle(true); + } + }, []); const handleCheckMark = () => { if (autoComplete) { const updatedSurvey: Survey = { ...localSurvey, autoComplete: null }; @@ -84,6 +112,29 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res )} +
+
+ + +
+
+ {redirectToggle && ( + handleRedirectUrlChange(e.target.value)} + /> + )} +
+
diff --git a/apps/web/app/s/[surveyId]/LinkSurvey.tsx b/apps/web/app/s/[surveyId]/LinkSurvey.tsx index 63631090c6..d3030f7c87 100644 --- a/apps/web/app/s/[surveyId]/LinkSurvey.tsx +++ b/apps/web/app/s/[surveyId]/LinkSurvey.tsx @@ -30,6 +30,7 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) { progress, isPreview, lastQuestion, + initiateCountdown, restartSurvey, submitResponse, } = useLinkSurveyUtils(survey); @@ -68,6 +69,7 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) { headline={survey.thankYouCard.headline || "Thank you!"} subheader={survey.thankYouCard.subheader || "Your response has been recorded."} brandColor={survey.brandColor} + initiateCountdown={initiateCountdown} /> ) : ( diff --git a/apps/web/components/preview/RedirectCountDown.tsx b/apps/web/components/preview/RedirectCountDown.tsx new file mode 100644 index 0000000000..c3d2130048 --- /dev/null +++ b/apps/web/components/preview/RedirectCountDown.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { useEffect,useState } from 'react' + +export default function RedirectCountDown({ + initiateCountdown +}:{ + initiateCountdown:boolean|undefined +}) { + + const [timeRemaining, setTimeRemaining] = useState(3) + + useEffect(() => { + const interval = setInterval(() => { + setTimeRemaining((prevTime) => prevTime - 1); + }, 1000); + + if (timeRemaining === 0) { + clearInterval(interval); + } + + // Clean up the interval when the component is unmounted + return () => clearInterval(interval); + }, [timeRemaining]); + + return ( +
+ {initiateCountdown &&
+ You're redirected in + {timeRemaining} +
} +
+ ) +} diff --git a/apps/web/components/preview/ThankYouCard.tsx b/apps/web/components/preview/ThankYouCard.tsx index a7e984476b..21cee6bdc8 100644 --- a/apps/web/components/preview/ThankYouCard.tsx +++ b/apps/web/components/preview/ThankYouCard.tsx @@ -1,13 +1,15 @@ import Headline from "./Headline"; import Subheader from "./Subheader"; +import RedirectCountDown from "./RedirectCountDown"; interface ThankYouCardProps { headline: string; subheader: string; brandColor: string; + initiateCountdown?: boolean; } -export default function ThankYouCard({ headline, subheader, brandColor }: ThankYouCardProps) { +export default function ThankYouCard({ headline, subheader, brandColor,initiateCountdown }: ThankYouCardProps) { return (
@@ -31,6 +33,7 @@ export default function ThankYouCard({ headline, subheader, brandColor }: ThankY
+
); diff --git a/apps/web/lib/linkSurvey/linkSurvey.ts b/apps/web/lib/linkSurvey/linkSurvey.ts index f2d964b095..105bea711a 100644 --- a/apps/web/lib/linkSurvey/linkSurvey.ts +++ b/apps/web/lib/linkSurvey/linkSurvey.ts @@ -6,6 +6,7 @@ import { QuestionType, type Logic, type Question } from "@formbricks/types/quest import { TResponseInput } from "@formbricks/types/v1/responses"; import { useState, useEffect, useCallback } from "react"; import type { Survey } from "@formbricks/types/surveys"; +import { useRouter } from "next/navigation"; export const useLinkSurvey = (surveyId: string) => { const { data, error, mutate, isLoading } = useSWR(`/api/v1/client/surveys/${surveyId}`, fetcher); @@ -26,7 +27,8 @@ export const useLinkSurveyUtils = (survey: Survey) => { const [loadingElement, setLoadingElement] = useState(false); const [responseId, setResponseId] = useState(null); const [displayId, setDisplayId] = useState(null); - + const [initiateCountdown, setinitiateCountdown] = useState(false); + const router = useRouter(); const URLParams = new URLSearchParams(window.location.search); const isPreview = URLParams.get("preview") === "true"; const hasFirstQuestionPrefill = URLParams.has(survey.questions[0].id); @@ -135,9 +137,22 @@ export const useLinkSurveyUtils = (survey: Survey) => { } else { setProgress(1); setFinished(true); + if (survey.redirectUrl && Object.values(data)[0] !== "dismissed") { + handleRedirect(survey.redirectUrl); + } } }; + const handleRedirect = (url) => { + if (!url.startsWith("https://") && !url.startsWith("http://")) { + url = `https://${url}`; + } + setinitiateCountdown(true); + setTimeout(() => { + router.push(url); + }, 3000); + }; + const handlePrefilling = useCallback(async () => { try { if (hasFirstQuestionPrefill) { @@ -173,6 +188,7 @@ export const useLinkSurveyUtils = (survey: Survey) => { loadingElement, prefilling, lastQuestion, + initiateCountdown, submitResponse, restartSurvey, }; diff --git a/apps/web/pages/api/v1/client/surveys/[surveyId]/index.ts b/apps/web/pages/api/v1/client/surveys/[surveyId]/index.ts index ddd882d731..5a09024b34 100644 --- a/apps/web/pages/api/v1/client/surveys/[surveyId]/index.ts +++ b/apps/web/pages/api/v1/client/surveys/[surveyId]/index.ts @@ -27,6 +27,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) thankYouCard: true, environmentId: true, status: true, + redirectUrl: true, }, }); diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 37e9e2bd0d..7dbcd3a2ce 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -213,6 +213,7 @@ model Survey { createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") name String + redirectUrl String? type SurveyType @default(web) environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) environmentId String diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts index e9a2f9d97a..ef83b7e3ab 100644 --- a/packages/types/surveys.ts +++ b/packages/types/surveys.ts @@ -11,6 +11,7 @@ export interface Survey { createdAt: string; updatedAt: string; name: string; + redirectUrl: string | null; type: "web" | "email" | "link" | "mobile"; environmentId: string; status: "draft" | "inProgress" | "archived" | "paused" | "completed"; diff --git a/packages/types/v1/surveys.ts b/packages/types/v1/surveys.ts index 4619921515..e5f4e4f762 100644 --- a/packages/types/v1/surveys.ts +++ b/packages/types/v1/surveys.ts @@ -197,6 +197,7 @@ export const ZSurvey = z.object({ displayOption: z.enum(["displayOnce", "displayMultiple", "respondMultiple"]), autoClose: z.union([z.number(), z.null()]), triggers: z.array(ZEventClass), + redirectUrl: z.union([z.string(), z.null()]), recontactDays: z.union([z.number(), z.null()]), questions: ZSurveyQuestions, thankYouCard: ZSurveyThankYouCard,