mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-01 19:59:35 -05:00
Add Email Verification option to Link Surveys (#762)
* completed frontend * Adds email verifaction for Link Surveys * remove console.log * run pnpm format * rename userId to verify * add loading state * fix type names * add types to prisma --------- Co-authored-by: Dhruwang Jariwala <dhruwang@Dhruwangs-MacBook-Pro.local> Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
eae8e2db24
commit
b00beadf2e
@@ -4,7 +4,6 @@
|
||||
</a>
|
||||
<h3 align="center">Formbricks</h3>
|
||||
|
||||
|
||||
<p align="center">
|
||||
The Open Source Survey & Experience Management solution for fast growing companies
|
||||
<br />
|
||||
|
||||
@@ -95,8 +95,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
{
|
||||
name: "Rivet",
|
||||
description:
|
||||
"Open-source solution to deploy, scale, and operate your multiplayer game.",
|
||||
description: "Open-source solution to deploy, scale, and operate your multiplayer game.",
|
||||
href: "https://rivet.gg",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -67,4 +67,4 @@ export default {
|
||||
},
|
||||
},
|
||||
plugins: [typographyPlugin, headlessuiPlugin, forms],
|
||||
} satisfies Config;
|
||||
} satisfies Config;
|
||||
|
||||
@@ -163,6 +163,10 @@ export async function duplicateSurveyAction(environmentId: string, surveyId: str
|
||||
surveyClosedMessage: existingSurvey.surveyClosedMessage
|
||||
? JSON.parse(JSON.stringify(existingSurvey.surveyClosedMessage))
|
||||
: prismaClient.JsonNull,
|
||||
|
||||
verifyEmail: existingSurvey.verifyEmail
|
||||
? JSON.parse(JSON.stringify(existingSurvey.verifyEmail))
|
||||
: prismaClient.JsonNull,
|
||||
},
|
||||
});
|
||||
return newSurvey;
|
||||
@@ -290,6 +294,7 @@ export async function copyToOtherEnvironmentAction(
|
||||
},
|
||||
},
|
||||
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
|
||||
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
|
||||
},
|
||||
});
|
||||
return newSurvey;
|
||||
|
||||
+76
@@ -20,10 +20,17 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
|
||||
const [redirectUrl, setRedirectUrl] = useState<string | null>("");
|
||||
const [surveyClosedMessageToggle, setSurveyClosedMessageToggle] = useState(false);
|
||||
const [verifyEmailToggle, setVerifyEmailToggle] = useState(false);
|
||||
|
||||
const [surveyClosedMessage, setSurveyClosedMessage] = useState({
|
||||
heading: "Survey Completed",
|
||||
subheading: "This free & open-source survey has been closed",
|
||||
});
|
||||
|
||||
const [verifyEmailSurveyDetails, setVerifyEmailSurveyDetails] = useState({
|
||||
name: "",
|
||||
subheading: "",
|
||||
});
|
||||
const [closeOnDate, setCloseOnDate] = useState<Date>();
|
||||
|
||||
const handleRedirectCheckMark = () => {
|
||||
@@ -63,6 +70,14 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyEmailToogle = () => {
|
||||
setVerifyEmailToggle((prev) => !prev);
|
||||
|
||||
if (verifyEmailToggle && localSurvey.verifyEmail) {
|
||||
setLocalSurvey({ ...localSurvey, verifyEmail: null });
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseOnDateChange = (date: Date) => {
|
||||
const equivalentDate = date?.getDate();
|
||||
date?.setUTCHours(0, 0, 0, 0);
|
||||
@@ -88,6 +103,22 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
setLocalSurvey({ ...localSurvey, surveyClosedMessage: message });
|
||||
};
|
||||
|
||||
const handleVerifyEmailSurveyDetailsChange = ({
|
||||
name,
|
||||
subheading,
|
||||
}: {
|
||||
name?: string;
|
||||
subheading?: string;
|
||||
}) => {
|
||||
const message = {
|
||||
name: name || verifyEmailSurveyDetails.name,
|
||||
subheading: subheading || verifyEmailSurveyDetails.subheading,
|
||||
};
|
||||
|
||||
setVerifyEmailSurveyDetails(message);
|
||||
setLocalSurvey({ ...localSurvey, verifyEmail: message });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (localSurvey.redirectUrl) {
|
||||
setRedirectUrl(localSurvey.redirectUrl);
|
||||
@@ -102,6 +133,14 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
setSurveyClosedMessageToggle(true);
|
||||
}
|
||||
|
||||
if (localSurvey.verifyEmail) {
|
||||
setVerifyEmailSurveyDetails({
|
||||
name: localSurvey.verifyEmail.name!,
|
||||
subheading: localSurvey.verifyEmail.subheading!,
|
||||
});
|
||||
setVerifyEmailToggle(true);
|
||||
}
|
||||
|
||||
if (localSurvey.closeOnDate) {
|
||||
setCloseOnDate(localSurvey.closeOnDate);
|
||||
setSurveyCloseOnDateToggle(true);
|
||||
@@ -257,6 +296,43 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
</div>
|
||||
</div>
|
||||
</AdvancedOptionToggle>
|
||||
|
||||
<AdvancedOptionToggle
|
||||
htmlId="verifyEmailBeforeSubmission"
|
||||
isChecked={verifyEmailToggle}
|
||||
onToggle={handleVerifyEmailToogle}
|
||||
title="Verify email before submission"
|
||||
description="Only let people with a real email respond."
|
||||
childBorder={true}>
|
||||
<div className="flex w-full items-center space-x-1 p-4 pb-4">
|
||||
<div className="w-full cursor-pointer items-center bg-slate-50">
|
||||
<p className="text-md font-semibold">How it works</p>
|
||||
<p className="mb-4 mt-2 text-sm text-slate-500">
|
||||
Respondants will receive the survey link via email.
|
||||
</p>
|
||||
<Label htmlFor="headline">Survey Name (Public)</Label>
|
||||
<Input
|
||||
autoFocus
|
||||
id="heading"
|
||||
className="mb-4 mt-2 bg-white"
|
||||
name="heading"
|
||||
placeholder="Job Application Form"
|
||||
defaultValue={verifyEmailSurveyDetails.name}
|
||||
onChange={(e) => handleVerifyEmailSurveyDetailsChange({ name: e.target.value })}
|
||||
/>
|
||||
|
||||
<Label htmlFor="headline">Subheader (Public)</Label>
|
||||
<Input
|
||||
className="mt-2 bg-white"
|
||||
id="subheading"
|
||||
name="subheading"
|
||||
placeholder="Thanks for applying as a full stack engineer"
|
||||
defaultValue={verifyEmailSurveyDetails.subheading}
|
||||
onChange={(e) => handleVerifyEmailSurveyDetailsChange({ subheading: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AdvancedOptionToggle>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+3
-1
@@ -129,6 +129,7 @@ export default function SurveyMenuBar({
|
||||
toast.error("Please enter a valid URL for redirecting respondents");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -164,7 +165,8 @@ export default function SurveyMenuBar({
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
toast.error(`Error saving changes`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { TSurvey } from "@formbricks/types/v1/surveys";
|
||||
import Loading from "@/app/s/[surveyId]/loading";
|
||||
import { TProduct } from "@formbricks/types/v1/product";
|
||||
import VerifyEmail from "@/app/s/[surveyId]/VerifyEmail";
|
||||
import { verifyTokenAction } from "@/app/s/[surveyId]/actions";
|
||||
|
||||
interface LinkSurveyProps {
|
||||
survey: TSurvey;
|
||||
@@ -40,6 +42,34 @@ export default function LinkSurvey({ survey, product }: LinkSurveyProps) {
|
||||
// Create a reference to the top element
|
||||
const topRef = useRef<HTMLDivElement>(null);
|
||||
const [autoFocus, setAutofocus] = useState(false);
|
||||
const URLParams = new URLSearchParams(typeof window !== "undefined" ? window.location.search : "");
|
||||
const [shouldRenderVerifyEmail, setShouldRenderVerifyEmail] = useState(false);
|
||||
const [isTokenValid, setIsTokenValid] = useState(true);
|
||||
|
||||
const checkVerifyToken = async (verifyToken: string): Promise<boolean> => {
|
||||
try {
|
||||
const result = await verifyTokenAction(verifyToken, survey.id);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (survey.verifyEmail) {
|
||||
setShouldRenderVerifyEmail(true);
|
||||
}
|
||||
const verifyToken = URLParams.get("verify");
|
||||
if (verifyToken) {
|
||||
checkVerifyToken(verifyToken)
|
||||
.then((result) => {
|
||||
setIsTokenValid(result);
|
||||
setShouldRenderVerifyEmail(!result); // Set shouldRenderVerifyEmail based on result
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error checking verify token:", error);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Not in an iframe, enable autofocus on input fields.
|
||||
useEffect(() => {
|
||||
@@ -63,6 +93,13 @@ export default function LinkSurvey({ survey, product }: LinkSurveyProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldRenderVerifyEmail) {
|
||||
if (!isTokenValid) {
|
||||
return <VerifyEmail survey={survey} isErrorComponent={true} />;
|
||||
}
|
||||
return <VerifyEmail survey={survey} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { EnvelopeIcon } from "@heroicons/react/24/solid";
|
||||
import { Button, Input } from "@formbricks/ui";
|
||||
import { Toaster, toast } from "react-hot-toast";
|
||||
import { sendLinkSurveyEmailAction } from "@/app/s/[surveyId]/actions";
|
||||
import { TSurvey } from "@formbricks/types/v1/surveys";
|
||||
|
||||
export default function VerifyEmail({
|
||||
survey,
|
||||
isErrorComponent,
|
||||
}: {
|
||||
survey: TSurvey;
|
||||
isErrorComponent?: boolean;
|
||||
}) {
|
||||
const [showPreviewQuestions, setShowPreviewQuestions] = useState(false);
|
||||
const [email, setEmail] = useState<string | null>(null);
|
||||
const [emailSent, setEmailSent] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const validateEmail = (inputEmail) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputEmail);
|
||||
|
||||
const submitEmail = async (email) => {
|
||||
setIsLoading(true);
|
||||
if (!validateEmail(email)) {
|
||||
toast.error("Please enter a valid email");
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
surveyId: survey.id,
|
||||
email: email,
|
||||
surveyData: survey.verifyEmail,
|
||||
};
|
||||
try {
|
||||
await sendLinkSurveyEmailAction(data);
|
||||
setEmailSent(true);
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handlePreviewClick = () => {
|
||||
setShowPreviewQuestions(!showPreviewQuestions);
|
||||
};
|
||||
|
||||
const handleGoBackClick = () => {
|
||||
setShowPreviewQuestions(false);
|
||||
setEmailSent(false);
|
||||
};
|
||||
|
||||
if (isErrorComponent) {
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center bg-slate-50">
|
||||
<span className="h-24 w-24 rounded-full bg-slate-300 p-6 text-5xl">🤔</span>
|
||||
<p className="mt-8 text-4xl font-bold">This looks fishy.</p>
|
||||
<p className="mt-4 cursor-pointer text-sm text-slate-400" onClick={handleGoBackClick}>
|
||||
Please try again with the original link
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center bg-slate-50">
|
||||
<Toaster />
|
||||
{!emailSent && !showPreviewQuestions && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<EnvelopeIcon className="h-24 w-24 rounded-full bg-slate-300 p-6 text-white" />
|
||||
<p className="mt-8 text-4xl font-bold">Verify your email to respond.</p>
|
||||
<p className="mt-2 text-slate-400">To respond to this survey please verify your email.</p>
|
||||
<div className="mt-6 flex w-full space-x-2">
|
||||
<Input
|
||||
type="string"
|
||||
className="h-full"
|
||||
placeholder="user@gmail.com"
|
||||
value={email || ""}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Button variant="darkCTA" onClick={() => submitEmail(email)} loading={isLoading}>
|
||||
Verify
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mt-6 cursor-pointer text-sm text-slate-400" onClick={handlePreviewClick}>
|
||||
Just curious? Preview survey questions.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!emailSent && showPreviewQuestions && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<p className="text-4xl font-bold">Question Preview</p>
|
||||
<div className="mt-4 flex w-full flex-col justify-center rounded-lg border border-slate-200 p-8">
|
||||
{survey.questions.map((question, index) => (
|
||||
<p key={index} className="my-1">{`${index + 1}. ${question.headline}`}</p>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-6 cursor-pointer text-sm text-slate-400" onClick={handlePreviewClick}>
|
||||
Want to respond? Verify email.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{emailSent && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h1 className="mt-8 text-4xl font-bold">Survey sent successfully</h1>
|
||||
<p className="mt-4 text-center text-slate-400">
|
||||
We sent an email to <span className="font-semibold italic">{email}</span>. Please click the link
|
||||
in the email to take your survey.
|
||||
</p>
|
||||
<p className="mt-6 cursor-pointer text-sm text-slate-400" onClick={handleGoBackClick}>
|
||||
Go Back
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
"use server";
|
||||
|
||||
interface LinkSurveyEmailData {
|
||||
surveyId: string;
|
||||
email: string;
|
||||
surveyData?: {
|
||||
name?: string;
|
||||
subheading?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
import { sendLinkSurveyToVerifiedEmail } from "@/lib/email";
|
||||
import { verifyTokenForLinkSurvey } from "@/lib/jwt";
|
||||
|
||||
export async function sendLinkSurveyEmailAction(data: LinkSurveyEmailData) {
|
||||
if (!data.surveyData) {
|
||||
throw new Error("No survey data provided");
|
||||
}
|
||||
return await sendLinkSurveyToVerifiedEmail(data);
|
||||
}
|
||||
export async function verifyTokenAction(token: string, surveyId: string): Promise<boolean> {
|
||||
return await verifyTokenForLinkSurvey(token, surveyId);
|
||||
}
|
||||
+21
-1
@@ -4,7 +4,7 @@ import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { Question } from "@formbricks/types/questions";
|
||||
import { TResponse } from "@formbricks/types/v1/responses";
|
||||
import { withEmailTemplate } from "./email-template";
|
||||
import { createInviteToken, createToken } from "./jwt";
|
||||
import { createInviteToken, createToken, createTokenForLinkSurvey } from "./jwt";
|
||||
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
@@ -56,6 +56,26 @@ export const sendVerificationEmail = async (user) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const sendLinkSurveyToVerifiedEmail = async (data) => {
|
||||
const surveyId = data.surveyId;
|
||||
const email = data.email;
|
||||
const surveyData = data.surveyData;
|
||||
const token = createTokenForLinkSurvey(surveyId, email);
|
||||
const surveyLink = `${WEBAPP_URL}/s/${surveyId}?verify=${encodeURIComponent(token)}`;
|
||||
await sendEmail({
|
||||
to: data.email,
|
||||
subject: "Your Formbricks Survey",
|
||||
html: withEmailTemplate(`<h1>Hey 👋</h1>
|
||||
Thanks for validating your email. Here is your Survey.<br/><br/>
|
||||
<strong>${surveyData.name}</strong>
|
||||
<p>${surveyData.subheading}</p>
|
||||
<a class="button" href="${surveyLink}">Take survey</a><br/>
|
||||
<br/>
|
||||
All the best,<br/>
|
||||
Your Formbricks Team 🤍`),
|
||||
});
|
||||
};
|
||||
|
||||
export const sendForgotPasswordEmail = async (user) => {
|
||||
const token = createToken(user.id, user.email, {
|
||||
expiresIn: "1d",
|
||||
|
||||
@@ -5,6 +5,21 @@ import { env } from "@/env.mjs";
|
||||
export function createToken(userId, userEmail, options = {}) {
|
||||
return jwt.sign({ id: userId }, env.NEXTAUTH_SECRET + userEmail, options);
|
||||
}
|
||||
export function createTokenForLinkSurvey(surveyId, userEmail) {
|
||||
return jwt.sign({ email: userEmail }, env.NEXTAUTH_SECRET + surveyId);
|
||||
}
|
||||
|
||||
export function verifyTokenForLinkSurvey(token, surveyId): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
jwt.verify(token, env.NEXTAUTH_SECRET + surveyId, function (err) {
|
||||
if (err) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyToken(token, userEmail = "") {
|
||||
if (!userEmail) {
|
||||
|
||||
+1
@@ -146,6 +146,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
},
|
||||
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
|
||||
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+1
@@ -66,6 +66,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
},
|
||||
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
|
||||
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -97,6 +97,10 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
if (!body.surveyClosedMessage) {
|
||||
body.surveyClosedMessage = prismaClient.JsonNull;
|
||||
}
|
||||
|
||||
if (!body.verifyEmail) {
|
||||
body.verifyEmail = prismaClient.JsonNull;
|
||||
}
|
||||
}
|
||||
|
||||
if (body.triggers) {
|
||||
@@ -226,6 +230,10 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
data.surveyClosedMessage = prismaClient.JsonNull;
|
||||
}
|
||||
|
||||
if (data.verifyEmail === null) {
|
||||
data.verifyEmail = prismaClient.JsonNull;
|
||||
}
|
||||
|
||||
const prismaRes = await prisma.survey.update({
|
||||
where: { id: surveyId },
|
||||
data,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { TActionClassNoCodeConfig } from "@formbricks/types/v1/actionClasses";
|
||||
import { TResponsePersonAttributes, TResponseData, TResponseMeta } from "@formbricks/types/v1/responses";
|
||||
import { TSurveyClosedMessage, TSurveyQuestions, TSurveyThankYouCard } from "@formbricks/types/v1/surveys";
|
||||
import {
|
||||
TSurveyClosedMessage,
|
||||
TSurveyQuestions,
|
||||
TSurveyThankYouCard,
|
||||
TSurveyVerifyEmail,
|
||||
} from "@formbricks/types/v1/surveys";
|
||||
import { TUserNotificationSettings } from "@formbricks/types/v1/users";
|
||||
|
||||
declare global {
|
||||
@@ -13,6 +18,7 @@ declare global {
|
||||
export type SurveyQuestions = TSurveyQuestions;
|
||||
export type SurveyThankYouCard = TSurveyThankYouCard;
|
||||
export type SurveyClosedMessage = TSurveyClosedMessage;
|
||||
export type SurveyVerifyEmail = TSurveyVerifyEmail;
|
||||
export type UserNotificationSettings = TUserNotificationSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Survey" ADD COLUMN "verifyEmail" JSONB;
|
||||
@@ -220,11 +220,9 @@ model Survey {
|
||||
environmentId String
|
||||
status SurveyStatus @default(draft)
|
||||
/// @zod.custom(imports.ZSurveyQuestions)
|
||||
/// @zod.custom(imports.ZSurveyQuestions)
|
||||
/// [SurveyQuestions]
|
||||
questions Json @default("[]")
|
||||
/// @zod.custom(imports.ZSurveyThankYouCard)
|
||||
/// @zod.custom(imports.ZSurveyThankYouCard)
|
||||
/// [SurveyThankYouCard]
|
||||
thankYouCard Json @default("{\"enabled\": false}")
|
||||
responses Response[]
|
||||
@@ -240,6 +238,9 @@ model Survey {
|
||||
/// @zod.custom(imports.ZSurveyClosedMessage)
|
||||
/// [SurveyClosedMessage]
|
||||
surveyClosedMessage Json?
|
||||
/// @zod.custom(imports.ZSurveyVerifyEmail)
|
||||
/// [SurveyVerifyEmail]
|
||||
verifyEmail Json?
|
||||
}
|
||||
|
||||
model Event {
|
||||
|
||||
@@ -5,6 +5,11 @@ export { ZActionClassNoCodeConfig } from "@formbricks/types/v1/actionClasses";
|
||||
|
||||
export { ZResponseData, ZResponsePersonAttributes, ZResponseMeta } from "@formbricks/types/v1/responses";
|
||||
|
||||
export { ZSurveyQuestions, ZSurveyThankYouCard, ZSurveyClosedMessage } from "@formbricks/types/v1/surveys";
|
||||
export {
|
||||
ZSurveyQuestions,
|
||||
ZSurveyThankYouCard,
|
||||
ZSurveyClosedMessage,
|
||||
ZSurveyVerifyEmail,
|
||||
} from "@formbricks/types/v1/surveys";
|
||||
|
||||
export { ZUserNotificationSettings } from "@formbricks/types/v1/users";
|
||||
|
||||
@@ -77,6 +77,7 @@ export const selectSurvey = {
|
||||
closeOnDate: true,
|
||||
delay: true,
|
||||
autoComplete: true,
|
||||
verifyEmail: true,
|
||||
redirectUrl: true,
|
||||
triggers: {
|
||||
select: {
|
||||
@@ -152,6 +153,7 @@ export const getSurveyWithAnalytics = cache(
|
||||
const survey = ZSurveyWithAnalytics.parse(transformedSurvey);
|
||||
return survey;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2)); // log the detailed error information
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface SurveyClosedMessage {
|
||||
subheading?: string;
|
||||
}
|
||||
|
||||
export interface VerifyEmail {
|
||||
name?: string;
|
||||
subheading?: string;
|
||||
}
|
||||
|
||||
export interface Survey {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
@@ -32,6 +37,7 @@ export interface Survey {
|
||||
delay: number;
|
||||
autoComplete: number | null;
|
||||
surveyClosedMessage: SurveyClosedMessage | null;
|
||||
verifyEmail: VerifyEmail | null;
|
||||
closeOnDate: Date | null;
|
||||
_count: { responses: number | null } | null;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,15 @@ export const ZSurveyClosedMessage = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const ZSurveyVerifyEmail = z
|
||||
.object({
|
||||
name: z.optional(z.string()),
|
||||
subheading: z.optional(z.string()),
|
||||
})
|
||||
.optional();
|
||||
|
||||
export type TSurveyVerifyEmail = z.infer<typeof ZSurveyVerifyEmail>;
|
||||
|
||||
export type TSurveyThankYouCard = z.infer<typeof ZSurveyThankYouCard>;
|
||||
|
||||
export type TSurveyClosedMessage = z.infer<typeof ZSurveyThankYouCard>;
|
||||
@@ -239,6 +248,7 @@ export const ZSurvey = z.object({
|
||||
autoComplete: z.union([z.number(), z.null()]),
|
||||
closeOnDate: z.date().nullable(),
|
||||
surveyClosedMessage: ZSurveyClosedMessage,
|
||||
verifyEmail: ZSurveyVerifyEmail.nullable(),
|
||||
});
|
||||
|
||||
export type TSurvey = z.infer<typeof ZSurvey>;
|
||||
|
||||
Reference in New Issue
Block a user