diff --git a/apps/formbricks-com/next.config.mjs b/apps/formbricks-com/next.config.mjs index cd832549f4..ed975ef8ac 100644 --- a/apps/formbricks-com/next.config.mjs +++ b/apps/formbricks-com/next.config.mjs @@ -1,6 +1,6 @@ import nextMDX from "@next/mdx"; - import { withPlausibleProxy } from "next-plausible"; + import { recmaPlugins } from "./mdx/recma.mjs"; import { rehypePlugins } from "./mdx/rehype.mjs"; import { remarkPlugins } from "./mdx/remark.mjs"; @@ -170,6 +170,11 @@ const nextConfig = { destination: "/community", permanent: true, }, + { + source: "/signup", + destination: "https://app.formbricks.com/auth/signup", + permanent: true, + }, ]; }, async rewrites() { diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx index 16df171509..eedc4359ef 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx @@ -1,9 +1,11 @@ "use client"; import * as Collapsible from "@radix-ui/react-collapsible"; +import { useState } from "react"; import { cn } from "@formbricks/lib/cn"; import { TSurvey } from "@formbricks/types/surveys"; +import { Input } from "@formbricks/ui/Input"; import { Label } from "@formbricks/ui/Label"; import QuestionFormInput from "@formbricks/ui/QuestionFormInput"; import { Switch } from "@formbricks/ui/Switch"; @@ -23,6 +25,9 @@ export default function EditThankYouCard({ }: EditThankYouCardProps) { // const [open, setOpen] = useState(false); let open = activeQuestionId == "end"; + const [showThankYouCardCTA, setshowThankYouCardCTA] = useState( + localSurvey.thankYouCard.buttonLabel || localSurvey.thankYouCard.buttonLink ? true : false + ); const setOpen = (e) => { if (e) { setActiveQuestionId("end"); @@ -114,6 +119,59 @@ export default function EditThankYouCard({ /> +
+
+ { + if (showThankYouCardCTA) { + updateSurvey({ buttonLabel: undefined, buttonLink: undefined }); + } else { + updateSurvey({ + buttonLabel: "Create your own Survey", + buttonLink: "https://formbricks.com/signup", + }); + } + setshowThankYouCardCTA(!showThankYouCardCTA); + }} + /> + +
+ {showThankYouCardCTA && ( +
+
+ + updateSurvey({ buttonLabel: e.target.value })} + /> +
+
+ + updateSurvey({ buttonLink: e.target.value })} + /> +
+
+ )} +
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx index a05154c6ab..cbafddac25 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar.tsx @@ -18,7 +18,7 @@ import { Input } from "@formbricks/ui/Input"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip"; import { deleteSurveyAction, updateSurveyAction } from "../actions"; -import { validateQuestion } from "./Validation"; +import { isValidUrl, validateQuestion } from "./Validation"; interface SurveyMenuBarProps { localSurvey: TSurvey; @@ -122,6 +122,26 @@ export default function SurveyMenuBar({ return; } + const { thankYouCard } = localSurvey; + if (thankYouCard.enabled) { + const { buttonLabel, buttonLink } = thankYouCard; + + if (buttonLabel && !buttonLink) { + toast.error("Button Link missing on Thank you card."); + return; + } + + if (!buttonLabel && buttonLink) { + toast.error("Button Label missing on Thank you card."); + return; + } + + if (buttonLink && !isValidUrl(buttonLink)) { + toast.error("Invalid URL on Thank You card."); + return; + } + } + faultyQuestions = []; for (let index = 0; index < survey.questions.length; index++) { const question = survey.questions[index]; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Validation.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Validation.ts index 161527fc26..64a41644bd 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Validation.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Validation.ts @@ -37,3 +37,12 @@ const validateQuestion = (question) => { }; export { validateQuestion }; + +export const isValidUrl = (string: string): boolean => { + try { + new URL(string); + return true; + } catch (e) { + return false; + } +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts index 8e4b7a2e01..f73549de98 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts @@ -12,6 +12,8 @@ const thankYouCardDefault = { enabled: true, headline: "Thank you!", subheader: "We appreciate your feedback.", + buttonLabel: "Create your own Survey", + buttonLink: "https://formbricks.com/signup", }; const hiddenFieldsDefault: TSurveyHiddenFields = { diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts index 2c79f4ddd6..dbee579647 100644 --- a/packages/lib/survey/service.ts +++ b/packages/lib/survey/service.ts @@ -488,13 +488,16 @@ export const createSurvey = async (environmentId: string, surveyBody: TSurveyInp const actionClasses = await getActionClasses(environmentId); revalidateSurveyByActionClassId(actionClasses, surveyBody.triggers); } - // TODO: Create with triggers & attributeFilters delete surveyBody.triggers; delete surveyBody.attributeFilters; const data: Omit = { ...surveyBody, }; + if (surveyBody.type === "web" && data.thankYouCard) { + data.thankYouCard.buttonLabel = ""; + data.thankYouCard.buttonLink = ""; + } const survey = await prisma.survey.create({ data: { diff --git a/packages/surveys/src/components/general/Survey.tsx b/packages/surveys/src/components/general/Survey.tsx index 651b749d18..a7a4d41abe 100644 --- a/packages/surveys/src/components/general/Survey.tsx +++ b/packages/surveys/src/components/general/Survey.tsx @@ -217,6 +217,9 @@ export function Survey({ ? replaceRecallInfo(survey.thankYouCard.subheader) : "" } + buttonLabel={survey.thankYouCard.buttonLabel} + buttonLink={survey.thankYouCard.buttonLink} + imageUrl={survey.thankYouCard.imageUrl} redirectUrl={survey.redirectUrl} isRedirectDisabled={isRedirectDisabled} /> diff --git a/packages/surveys/src/components/general/ThankYouCard.tsx b/packages/surveys/src/components/general/ThankYouCard.tsx index 4e84c62681..d042248729 100644 --- a/packages/surveys/src/components/general/ThankYouCard.tsx +++ b/packages/surveys/src/components/general/ThankYouCard.tsx @@ -1,12 +1,18 @@ +import Button from "@/components/buttons/SubmitButton"; import Headline from "@/components/general/Headline"; +import QuestionImage from "@/components/general/QuestionImage"; import RedirectCountDown from "@/components/general/RedirectCountdown"; import Subheader from "@/components/general/Subheader"; +import { useEffect } from "preact/hooks"; interface ThankYouCardProps { headline?: string; subheader?: string; redirectUrl: string | null; isRedirectDisabled: boolean; + buttonLabel?: string; + buttonLink?: string; + imageUrl?: string; } export default function ThankYouCard({ @@ -14,9 +20,27 @@ export default function ThankYouCard({ subheader, redirectUrl, isRedirectDisabled, + buttonLabel, + buttonLink, + imageUrl, }: ThankYouCardProps) { + useEffect(() => { + if (!buttonLink) return; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter") { + window.location.href = buttonLink; + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [buttonLink]); + return (
+ {imageUrl && } +
+ {buttonLabel && ( +
+
+ )}
); diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts index 08aa06310e..8785a4a9e4 100644 --- a/packages/types/surveys.ts +++ b/packages/types/surveys.ts @@ -7,6 +7,9 @@ export const ZSurveyThankYouCard = z.object({ enabled: z.boolean(), headline: z.optional(z.string()), subheader: z.optional(z.string()), + buttonLabel: z.optional(z.string()), + buttonLink: z.optional(z.string()), + imageUrl: z.string().optional(), }); export enum TSurveyQuestionType { diff --git a/packages/ui/QuestionFormInput/index.tsx b/packages/ui/QuestionFormInput/index.tsx index dd404622cc..75e6124d78 100644 --- a/packages/ui/QuestionFormInput/index.tsx +++ b/packages/ui/QuestionFormInput/index.tsx @@ -63,7 +63,11 @@ const QuestionFormInput = ({ const fallbackInputRef = useRef(null); const inputRef = useRef(null); const [showImageUploader, setShowImageUploader] = useState( - questionId === "end" ? false : !!(question as TSurveyQuestion).imageUrl && type === "headline" + questionId === "end" + ? localSurvey.thankYouCard.imageUrl + ? true + : false + : !!(question as TSurveyQuestion).imageUrl ); const [showQuestionSelect, setShowQuestionSelect] = useState(false); const [showFallbackInput, setShowFallbackInput] = useState(false); @@ -244,17 +248,21 @@ const QuestionFormInput = ({
- {showImageUploader && ( + {showImageUploader && type === "headline" && ( { - if (updateQuestion && url) { + if (isThankYouCard && updateSurvey && url) { + updateSurvey({ imageUrl: url[0] }); + } else if (updateQuestion && url) { updateQuestion(questionIdx, { imageUrl: url[0] }); } }} - fileUrl={isThankYouCard ? "" : (question as TSurveyQuestion).imageUrl} + fileUrl={ + isThankYouCard ? localSurvey.thankYouCard.imageUrl : (question as TSurveyQuestion).imageUrl + } /> )}