mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 10:30:00 -06:00
Compare commits
7 Commits
poc-featur
...
typeerror-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
520c337748 | ||
|
|
7f5c93b629 | ||
|
|
8f6d27c1ef | ||
|
|
a37815b831 | ||
|
|
2b526a87ca | ||
|
|
047750967c | ||
|
|
a54356c3b0 |
@@ -58,7 +58,7 @@ async function handleEmailUpdate({
|
||||
payload.email = inputEmail;
|
||||
await updateBrevoCustomer({ id: ctx.user.id, email: inputEmail });
|
||||
} else {
|
||||
await sendVerificationNewEmail(ctx.user.id, inputEmail);
|
||||
await sendVerificationNewEmail(ctx.user.id, inputEmail, ctx.user.locale);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export const sendEmbedSurveyPreviewEmailAction = authenticatedActionClient
|
||||
ctx.user.email,
|
||||
emailHtml,
|
||||
survey.environmentId,
|
||||
ctx.user.locale,
|
||||
organizationLogoUrl || ""
|
||||
);
|
||||
});
|
||||
|
||||
@@ -215,7 +215,14 @@ export const POST = async (request: Request) => {
|
||||
}
|
||||
|
||||
const emailPromises = usersWithNotifications.map((user) =>
|
||||
sendResponseFinishedEmail(user.email, environmentId, survey, response, responseCount).catch((error) => {
|
||||
sendResponseFinishedEmail(
|
||||
user.email,
|
||||
user.locale,
|
||||
environmentId,
|
||||
survey,
|
||||
response,
|
||||
responseCount
|
||||
).catch((error) => {
|
||||
logger.error(
|
||||
{ error, url: request.url, userEmail: user.email },
|
||||
`Failed to send email to ${user.email}`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { getLocale } from "@/lingodotdev/language";
|
||||
import { getTranslate } from "./server";
|
||||
|
||||
@@ -11,6 +11,10 @@ vi.mock("@/lingodotdev/shared", () => ({
|
||||
}));
|
||||
|
||||
describe("lingodotdev server", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should get translate", async () => {
|
||||
vi.mocked(getLocale).mockResolvedValue("en-US");
|
||||
const translate = await getTranslate();
|
||||
@@ -22,4 +26,16 @@ describe("lingodotdev server", () => {
|
||||
const translate = await getTranslate();
|
||||
expect(translate).toBeDefined();
|
||||
});
|
||||
|
||||
test("should use provided locale instead of calling getLocale", async () => {
|
||||
const translate = await getTranslate("de-DE");
|
||||
expect(getLocale).not.toHaveBeenCalled();
|
||||
expect(translate).toBeDefined();
|
||||
});
|
||||
|
||||
test("should call getLocale when locale is not provided", async () => {
|
||||
vi.mocked(getLocale).mockResolvedValue("fr-FR");
|
||||
await getTranslate();
|
||||
expect(getLocale).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ICU from "i18next-icu";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import { initReactI18next } from "react-i18next/initReactI18next";
|
||||
import { DEFAULT_LOCALE } from "@/lib/constants";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { getLocale } from "@/lingodotdev/language";
|
||||
|
||||
const initI18next = async (lng: string) => {
|
||||
@@ -21,9 +22,9 @@ const initI18next = async (lng: string) => {
|
||||
return i18nInstance;
|
||||
};
|
||||
|
||||
export async function getTranslate() {
|
||||
const locale = await getLocale();
|
||||
export async function getTranslate(locale?: TUserLocale) {
|
||||
const resolvedLocale = locale ?? (await getLocale());
|
||||
|
||||
const i18nextInstance = await initI18next(locale);
|
||||
return i18nextInstance.getFixedT(locale);
|
||||
const i18nextInstance = await initI18next(resolvedLocale);
|
||||
return i18nextInstance.getFixedT(resolvedLocale);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const resetPasswordAction = actionClient.schema(ZResetPasswordAction).act
|
||||
ctx.auditLoggingCtx.oldObject = oldObject;
|
||||
ctx.auditLoggingCtx.newObject = updatedUser;
|
||||
|
||||
await sendPasswordResetNotifyEmail(updatedUser);
|
||||
await sendPasswordResetNotifyEmail({ email: updatedUser.email, locale: updatedUser.locale });
|
||||
return { success: true };
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,6 +69,7 @@ describe("invite", () => {
|
||||
creator: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
locale: "en-US",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -89,6 +90,7 @@ describe("invite", () => {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ export const getInvite = reactCache(async (inviteId: string): Promise<InviteWith
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -102,7 +102,12 @@ export const InvitePage = async (props: InvitePageProps) => {
|
||||
);
|
||||
}
|
||||
await deleteInvite(inviteId);
|
||||
await sendInviteAcceptedEmail(invite.creator.name ?? "", user?.name ?? "", invite.creator.email);
|
||||
await sendInviteAcceptedEmail(
|
||||
invite.creator.name ?? "",
|
||||
user?.name ?? "",
|
||||
invite.creator.email,
|
||||
invite.creator.locale
|
||||
);
|
||||
await updateUser(session.user.id, {
|
||||
notificationSettings: {
|
||||
...user.notificationSettings,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Invite } from "@prisma/client";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
|
||||
export interface InviteWithCreator
|
||||
extends Pick<Invite, "id" | "expiresAt" | "organizationId" | "role" | "teamIds"> {
|
||||
creator: {
|
||||
name: string | null;
|
||||
email: string;
|
||||
locale: TUserLocale;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { TwoFactorBackup } from "@/modules/ee/two-factor-auth/components/two-fac
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/components/form";
|
||||
import { PasswordInput } from "@/modules/ui/components/password-input";
|
||||
import { safeFormRequestSubmit } from "@/modules/ui/lib/utils";
|
||||
|
||||
const ZLoginForm = z.object({
|
||||
email: z.string().email(),
|
||||
@@ -236,7 +237,7 @@ export const LoginForm = ({
|
||||
// Add a slight delay before focusing the input field to ensure it's visible
|
||||
setTimeout(() => emailRef.current?.focus(), 100);
|
||||
} else if (formRef.current) {
|
||||
formRef.current.requestSubmit();
|
||||
safeFormRequestSubmit(formRef.current);
|
||||
}
|
||||
}}
|
||||
className="relative w-full justify-center"
|
||||
|
||||
@@ -127,7 +127,12 @@ async function handleInviteAcceptance(
|
||||
},
|
||||
});
|
||||
|
||||
await sendInviteAcceptedEmail(invite.creator.name ?? "", user.name, invite.creator.email);
|
||||
await sendInviteAcceptedEmail(
|
||||
invite.creator.name ?? "",
|
||||
user.name,
|
||||
invite.creator.email,
|
||||
invite.creator.locale
|
||||
);
|
||||
await deleteInvite(invite.id);
|
||||
}
|
||||
|
||||
@@ -168,7 +173,7 @@ async function handlePostUserCreation(
|
||||
}
|
||||
|
||||
if (!emailVerificationDisabled) {
|
||||
await sendVerificationEmail(user);
|
||||
await sendVerificationEmail({ id: user.id, email: user.email, locale: user.locale });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ describe("resendVerificationEmailAction", () => {
|
||||
const mockUser = {
|
||||
id: "user123",
|
||||
email: "test@example.com",
|
||||
emailVerified: null, // Not verified
|
||||
name: "Test User",
|
||||
locale: "en-US",
|
||||
};
|
||||
|
||||
const mockVerifiedUser = {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const resendVerificationEmailAction = actionClient.schema(ZResendVerifica
|
||||
};
|
||||
}
|
||||
ctx.auditLoggingCtx.userId = user.id;
|
||||
await sendVerificationEmail(user);
|
||||
await sendVerificationEmail({ id: user.id, email: user.email, locale: user.locale });
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
||||
@@ -121,6 +121,7 @@ export const sendTestEmailAction = authenticatedActionClient
|
||||
await sendEmailCustomizationPreviewEmail(
|
||||
ctx.user.email,
|
||||
ctx.user.name,
|
||||
ctx.user.locale,
|
||||
organization?.whitelabel?.logoUrl || ""
|
||||
);
|
||||
|
||||
|
||||
@@ -213,8 +213,8 @@ export async function PreviewEmailTemplate({
|
||||
{ "rounded-l-lg border-l": i === 0 },
|
||||
{ "rounded-r-lg": i === firstQuestion.range - 1 },
|
||||
firstQuestion.isColorCodingEnabled &&
|
||||
firstQuestion.scale === "number" &&
|
||||
`border border-t-[6px] border-t-${getRatingNumberOptionColor(firstQuestion.range, i + 1)}`,
|
||||
firstQuestion.scale === "number" &&
|
||||
`border border-t-[6px] border-t-${getRatingNumberOptionColor(firstQuestion.range, i + 1)}`,
|
||||
firstQuestion.scale === "star" && "border-transparent"
|
||||
)}
|
||||
href={`${urlWithPrefilling}${firstQuestion.id}=${(i + 1).toString()}`}
|
||||
@@ -288,7 +288,7 @@ export async function PreviewEmailTemplate({
|
||||
<Container className="mx-0 max-w-none">
|
||||
{firstQuestion.choices.map((choice) => (
|
||||
<Link
|
||||
className="border-input-border-color bg-input-color text-question-color rounded-custom mt-2 block border border-solid p-4 hover:bg-slate-100"
|
||||
className="border-input-border-color bg-input-color text-question-color rounded-custom mt-2 block border border-solid p-4"
|
||||
href={`${urlWithPrefilling}${firstQuestion.id}=${getLocalizedValue(choice.label, defaultLanguageCode)}`}
|
||||
key={choice.id}>
|
||||
{getLocalizedValue(choice.label, defaultLanguageCode)}
|
||||
|
||||
@@ -71,12 +71,12 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports
|
||||
...(SMTP_AUTHENTICATED
|
||||
? {
|
||||
auth: {
|
||||
type: "LOGIN",
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
}
|
||||
auth: {
|
||||
type: "LOGIN",
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
tls: {
|
||||
rejectUnauthorized: SMTP_REJECT_UNAUTHORIZED_TLS,
|
||||
@@ -97,9 +97,13 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
}
|
||||
};
|
||||
|
||||
export const sendVerificationNewEmail = async (id: string, email: string): Promise<boolean> => {
|
||||
export const sendVerificationNewEmail = async (
|
||||
id: string,
|
||||
email: string,
|
||||
locale: TUserLocale
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const token = createEmailChangeToken(id, email);
|
||||
const verifyLink = `${WEBAPP_URL}/verify-email-change?token=${encodeURIComponent(token)}`;
|
||||
|
||||
@@ -119,12 +123,14 @@ export const sendVerificationNewEmail = async (id: string, email: string): Promi
|
||||
export const sendVerificationEmail = async ({
|
||||
id,
|
||||
email,
|
||||
locale,
|
||||
}: {
|
||||
id: string;
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
try {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const token = createToken(id, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
@@ -154,7 +160,7 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(user.locale);
|
||||
const token = createToken(user.id, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
@@ -167,8 +173,11 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
});
|
||||
};
|
||||
|
||||
export const sendPasswordResetNotifyEmail = async (user: { email: string }): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
export const sendPasswordResetNotifyEmail = async (user: {
|
||||
email: string;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
const t = await getTranslate(user.locale);
|
||||
const html = await renderPasswordResetNotifyEmail({ t, ...legalProps });
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
@@ -201,9 +210,10 @@ export const sendInviteMemberEmail = async (
|
||||
export const sendInviteAcceptedEmail = async (
|
||||
inviterName: string,
|
||||
inviteeName: string,
|
||||
email: string
|
||||
email: string,
|
||||
inviterLocale?: TUserLocale
|
||||
): Promise<void> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(inviterLocale);
|
||||
const html = await renderInviteAcceptedEmail({ inviteeName, inviterName, t, ...legalProps });
|
||||
await sendEmail({
|
||||
to: email,
|
||||
@@ -214,12 +224,13 @@ export const sendInviteAcceptedEmail = async (
|
||||
|
||||
export const sendResponseFinishedEmail = async (
|
||||
email: string,
|
||||
locale: TUserLocale,
|
||||
environmentId: string,
|
||||
survey: TSurvey,
|
||||
response: TResponse,
|
||||
responseCount: number
|
||||
): Promise<void> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const personEmail = response.contactAttributes?.email;
|
||||
const organization = await getOrganizationByEnvironmentId(environmentId);
|
||||
|
||||
@@ -246,12 +257,12 @@ export const sendResponseFinishedEmail = async (
|
||||
to: email,
|
||||
subject: personEmail
|
||||
? t("emails.response_finished_email_subject_with_email", {
|
||||
personEmail,
|
||||
surveyName: survey.name,
|
||||
})
|
||||
personEmail,
|
||||
surveyName: survey.name,
|
||||
})
|
||||
: t("emails.response_finished_email_subject", {
|
||||
surveyName: survey.name,
|
||||
}),
|
||||
surveyName: survey.name,
|
||||
}),
|
||||
replyTo: personEmail?.toString() ?? MAIL_FROM,
|
||||
html,
|
||||
});
|
||||
@@ -261,9 +272,10 @@ export const sendEmbedSurveyPreviewEmail = async (
|
||||
to: string,
|
||||
innerHtml: string,
|
||||
environmentId: string,
|
||||
locale: TUserLocale,
|
||||
logoUrl?: string
|
||||
): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const html = await renderEmbedSurveyPreviewEmail({
|
||||
html: innerHtml,
|
||||
environmentId,
|
||||
@@ -281,9 +293,10 @@ export const sendEmbedSurveyPreviewEmail = async (
|
||||
export const sendEmailCustomizationPreviewEmail = async (
|
||||
to: string,
|
||||
userName: string,
|
||||
locale: TUserLocale,
|
||||
logoUrl?: string
|
||||
): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const emailHtmlBody = await renderEmailCustomizationPreviewEmail({
|
||||
userName,
|
||||
logoUrl,
|
||||
@@ -305,7 +318,7 @@ export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData):
|
||||
const singleUseId = data.suId;
|
||||
const logoUrl = data.logoUrl || "";
|
||||
const token = createTokenForLinkSurvey(surveyId, email);
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(data.locale);
|
||||
const getSurveyLink = (): string => {
|
||||
if (singleUseId) {
|
||||
return `${getPublicDomain()}/s/${surveyId}?verify=${encodeURIComponent(token)}&suId=${singleUseId}`;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ArrowLeft, MailIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { Toaster, toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -10,11 +10,13 @@ import { z } from "zod";
|
||||
import { TProjectStyling } from "@formbricks/types/project";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { isSurveyResponsePresentAction, sendLinkSurveyEmailAction } from "@/modules/survey/link/actions";
|
||||
import { getWebAppLocale } from "@/modules/survey/link/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/components/form";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
@@ -26,7 +28,7 @@ interface VerifyEmailProps {
|
||||
singleUseId?: string;
|
||||
languageCode: string;
|
||||
styling: TProjectStyling;
|
||||
locale: string;
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
const ZVerifyEmailInput = z.object({
|
||||
@@ -42,7 +44,18 @@ export const VerifyEmail = ({
|
||||
styling,
|
||||
locale,
|
||||
}: VerifyEmailProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
// Set i18n language based on survey language
|
||||
useEffect(() => {
|
||||
const webAppLocale = getWebAppLocale(languageCode, survey);
|
||||
if (i18n.language !== webAppLocale) {
|
||||
i18n.changeLanguage(webAppLocale).catch(() => {
|
||||
// If changeLanguage fails, fallback to default locale
|
||||
i18n.changeLanguage("en-US");
|
||||
});
|
||||
}
|
||||
}, [languageCode, survey, i18n]);
|
||||
const form = useForm<TVerifyEmailInput>({
|
||||
defaultValues: {
|
||||
email: "",
|
||||
@@ -175,7 +188,7 @@ export const VerifyEmail = ({
|
||||
{!emailSent && showPreviewQuestions && (
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{t("s.question_preview")}</p>
|
||||
<div className="mt-4 flex max-h-[50vh] w-full flex-col overflow-y-auto rounded-lg border border-slate-200 bg-slate-50 bg-opacity-20 p-4 text-slate-700">
|
||||
<div className="bg-opacity-20 mt-4 flex max-h-[50vh] w-full flex-col overflow-y-auto rounded-lg border border-slate-200 bg-slate-50 p-4 text-slate-700">
|
||||
{questions.map((question, index) => (
|
||||
<p
|
||||
key={index}
|
||||
|
||||
85
apps/web/modules/survey/link/lib/utils.test.ts
Normal file
85
apps/web/modules/survey/link/lib/utils.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getWebAppLocale } from "./utils";
|
||||
|
||||
describe("getWebAppLocale", () => {
|
||||
const createMockSurvey = (languages: TSurvey["languages"] = []): TSurvey => {
|
||||
return {
|
||||
id: "survey-1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test Survey",
|
||||
type: "link",
|
||||
environmentId: "env-1",
|
||||
createdBy: null,
|
||||
status: "draft",
|
||||
displayOption: "displayOnce",
|
||||
autoClose: null,
|
||||
triggers: [],
|
||||
recontactDays: null,
|
||||
displayLimit: null,
|
||||
welcomeCard: {
|
||||
enabled: false,
|
||||
headline: { default: "Welcome" },
|
||||
timeToFinish: false,
|
||||
showResponseCount: false,
|
||||
},
|
||||
questions: [],
|
||||
blocks: [],
|
||||
endings: [],
|
||||
hiddenFields: { enabled: false, fieldIds: [] },
|
||||
variables: [],
|
||||
styling: null,
|
||||
segment: null,
|
||||
languages,
|
||||
displayPercentage: null,
|
||||
isVerifyEmailEnabled: false,
|
||||
isSingleResponsePerEmailEnabled: false,
|
||||
singleUse: null,
|
||||
pin: null,
|
||||
projectOverwrites: null,
|
||||
surveyClosedMessage: null,
|
||||
followUps: [],
|
||||
delay: 0,
|
||||
autoComplete: null,
|
||||
showLanguageSwitch: null,
|
||||
recaptcha: null,
|
||||
isBackButtonHidden: false,
|
||||
isCaptureIpEnabled: false,
|
||||
slug: null,
|
||||
metadata: {},
|
||||
} as TSurvey;
|
||||
};
|
||||
|
||||
test("maps language codes to web app locales", () => {
|
||||
const survey = createMockSurvey();
|
||||
expect(getWebAppLocale("en", survey)).toBe("en-US");
|
||||
expect(getWebAppLocale("de", survey)).toBe("de-DE");
|
||||
expect(getWebAppLocale("pt-BR", survey)).toBe("pt-BR");
|
||||
});
|
||||
|
||||
test("handles 'default' languageCode by finding default language in survey", () => {
|
||||
const survey = createMockSurvey([
|
||||
{
|
||||
language: {
|
||||
id: "lang1",
|
||||
code: "de",
|
||||
alias: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
projectId: "proj1",
|
||||
},
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(getWebAppLocale("default", survey)).toBe("de-DE");
|
||||
});
|
||||
|
||||
test("falls back to en-US when language is not supported", () => {
|
||||
const survey = createMockSurvey();
|
||||
expect(getWebAppLocale("default", survey)).toBe("en-US");
|
||||
expect(getWebAppLocale("xx", survey)).toBe("en-US");
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,54 @@
|
||||
// Prefilling logic has been moved to @/modules/survey/link/lib/prefill
|
||||
// This file is kept for any future utility functions
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
/**
|
||||
* Maps survey language codes to web app locale codes.
|
||||
* Falls back to "en-US" if the language is not available in web app locales.
|
||||
*/
|
||||
export const getWebAppLocale = (languageCode: string, survey: TSurvey): string => {
|
||||
// Map of common 2-letter language codes to web app locale codes
|
||||
const languageToLocaleMap: Record<string, string> = {
|
||||
en: "en-US",
|
||||
de: "de-DE",
|
||||
pt: "pt-BR", // Default to Brazilian Portuguese
|
||||
"pt-BR": "pt-BR",
|
||||
"pt-PT": "pt-PT",
|
||||
fr: "fr-FR",
|
||||
nl: "nl-NL",
|
||||
zh: "zh-Hans-CN", // Default to Simplified Chinese
|
||||
"zh-Hans": "zh-Hans-CN",
|
||||
"zh-Hans-CN": "zh-Hans-CN",
|
||||
"zh-Hant": "zh-Hant-TW",
|
||||
"zh-Hant-TW": "zh-Hant-TW",
|
||||
ro: "ro-RO",
|
||||
ja: "ja-JP",
|
||||
es: "es-ES",
|
||||
sv: "sv-SE",
|
||||
ru: "ru-RU",
|
||||
};
|
||||
|
||||
let codeToMap = languageCode;
|
||||
|
||||
// If languageCode is "default", get the default language from survey
|
||||
if (languageCode === "default") {
|
||||
const defaultLanguage = survey.languages?.find((lang) => lang.default);
|
||||
if (defaultLanguage) {
|
||||
codeToMap = defaultLanguage.language.code;
|
||||
} else {
|
||||
return "en-US";
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's already a web app locale code
|
||||
if (languageToLocaleMap[codeToMap]) {
|
||||
return languageToLocaleMap[codeToMap];
|
||||
}
|
||||
|
||||
// Try to find a match by base language code (e.g., "pt-BR" -> "pt")
|
||||
const baseCode = codeToMap.split("-")[0].toLowerCase();
|
||||
if (languageToLocaleMap[baseCode]) {
|
||||
return languageToLocaleMap[baseCode];
|
||||
}
|
||||
|
||||
// Fallback to English if language is not supported
|
||||
return "en-US";
|
||||
};
|
||||
|
||||
@@ -4,3 +4,27 @@ import { twMerge } from "tailwind-merge";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely requests form submission with validation.
|
||||
* Provides a fallback for browsers that don't support requestSubmit() (iOS Safari < 16.0).
|
||||
* @param form The form element to submit
|
||||
*/
|
||||
export function safeFormRequestSubmit(form: HTMLFormElement): void {
|
||||
// Check if requestSubmit is supported (iOS Safari 16.0+, all modern browsers)
|
||||
if (typeof form.requestSubmit === "function") {
|
||||
form.requestSubmit();
|
||||
} else {
|
||||
// Fallback for older browsers (iOS Safari < 16.0)
|
||||
// reportValidity() triggers native validation UI
|
||||
if (!form.reportValidity()) {
|
||||
return;
|
||||
}
|
||||
// Dispatch submit event manually to trigger form submission handlers
|
||||
const submitEvent = new Event("submit", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
form.dispatchEvent(submitEvent);
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"lucide-react": "0.507.0",
|
||||
"markdown-it": "14.1.0",
|
||||
"mime-types": "3.0.1",
|
||||
"next": "16.1.1",
|
||||
"next": "16.1.3",
|
||||
"next-auth": "4.24.12",
|
||||
"next-safe-action": "7.10.8",
|
||||
"node-fetch": "3.3.2",
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "map-pin"
|
||||
src="https://app.formbricks.com/s/m8w91e8wi52pdao8un1f4twu"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "check"
|
||||
src="https://app.formbricks.com/s/orxp15pca6x2nfr3v8pttpwm"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "address-book"
|
||||
src="https://app.formbricks.com/s/z2zjoonfeythx5n6z5qijbsg"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "calendar"
|
||||
src="https://app.formbricks.com/s/rk844spc8ffls25vzkxzzhse"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -15,7 +15,7 @@ icon: "upload"
|
||||
src="https://app.formbricks.com/s/oo4e6vva48w0trn01ht8krwo"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -12,7 +12,7 @@ Free text questions allow respondents to enter a custom answer. Displays a title
|
||||
src="https://app.formbricks.com/s/cm2b2eftv000012b0l3htbu0a"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -11,7 +11,7 @@ The values range from 0 to a user-defined maximum (e.g., 0 to X). The selection
|
||||
src="https://app.formbricks.com/s/obqeey0574jig4lo2gqyv51e"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -10,7 +10,7 @@ icon: "presentation-screen"
|
||||
src="https://app.formbricks.com/s/vqmpasmnt5qcpsa4enheips0"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "ranking-star"
|
||||
src="https://app.formbricks.com/s/z6s84x9wbyk0yqqtfaz238px"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -11,7 +11,7 @@ Rating questions allow respondents to rate questions on a scale. Displays a titl
|
||||
src="https://app.formbricks.com/s/cx7u4n6hwvc3nztuk4vdezl9"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
@@ -38,8 +38,35 @@ Select the icon to be used for the rating scale. The options include: stars, num
|
||||
|
||||
### Range
|
||||
|
||||
Select the range of the rating scale. the options include: 3, 4, 5, 7 or 10. The default is 5.
|
||||
Select the range of the rating scale. the options include: 3, 4, 5, 6, 7 or 10. The default is 5.
|
||||
|
||||
### Labels
|
||||
|
||||
Add labels for the lower and upper bounds of the rating scale. The default is "Not good" and "Very good".
|
||||
|
||||
## CSAT Summary
|
||||
|
||||
After collecting responses, rating questions display a CSAT (Customer Satisfaction) score with a visual traffic light indicator to help you quickly assess satisfaction levels:
|
||||
|
||||
- 🟢 **Green** (> 80%): High satisfaction - your users are very satisfied
|
||||
- 🟠 **Orange** (55-80%): Moderate satisfaction - there's room for improvement
|
||||
- 🔴 **Red** (< 55%): Low satisfaction - immediate attention needed
|
||||
|
||||
<Note>The traffic light indicator appears automatically in the survey summary view, giving you instant feedback on user satisfaction without needing to dig into the data.</Note>
|
||||
|
||||
### How CSAT is Calculated
|
||||
|
||||
The CSAT percentage represents the proportion of respondents who gave a "satisfied" rating. What counts as "satisfied" depends on your selected range:
|
||||
|
||||
| Range | Satisfied Ratings | Examples |
|
||||
|-------|------------------|----------|
|
||||
| 3 | Highest rating only | ⭐⭐⭐ |
|
||||
| 4 | Top 2 ratings | ⭐⭐⭐ or ⭐⭐⭐⭐ |
|
||||
| 5 | Top 2 ratings | ⭐⭐⭐⭐ or ⭐⭐⭐⭐⭐ |
|
||||
| 6 | Top 2 ratings | 5 or 6 |
|
||||
| 7 | Top 2 ratings | 6 or 7 |
|
||||
| 10 | Top 3 ratings | 8, 9, or 10 |
|
||||
|
||||
<Note>
|
||||
**Pro Tip:** For most use cases, a 5-point scale with star or smiley icons provides the best balance between granularity and user experience. Users find it easy to understand and quick to complete.
|
||||
</Note>
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: "calendar-check"
|
||||
src="https://app.formbricks.com/s/hx08x27c2aghywh57rroe6fi"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -12,7 +12,7 @@ Multi select questions allow respondents to select several answers from a list.
|
||||
src="https://app.formbricks.com/s/jhyo6lwzf6eh3fyplhlp7h5f"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -17,7 +17,7 @@ Picture selection questions allow respondents to select one or more images from
|
||||
src="https://app.formbricks.com/s/xtgmwxlk7jxxr4oi6ym7odki"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -12,7 +12,7 @@ Single select questions allow respondents to select one answer from a list. Disp
|
||||
src="https://app.formbricks.com/s/wybd3v3cxpdfve4472fu3lhi"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -11,7 +11,7 @@ It consists of a title (can be Question or Short Note) and a description, which
|
||||
src="https://app.formbricks.com/s/k3p7r7riyy504u4zziqat8zj"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "90vh",
|
||||
height: "600px",
|
||||
maxHeight: "100vh",
|
||||
width: "100%",
|
||||
border: 0,
|
||||
|
||||
@@ -14,7 +14,7 @@ Recontact options are the last layer of the logic that determines if a survey is
|
||||
|
||||
3. **Recontact Options:** Should the survey be shown (again) to this user? That's dependent on:
|
||||
|
||||
- Did the user see any survey recently (meaning, has Global Waiting Time passed)?
|
||||
- Did the user see any survey recently (meaning, has Survey Cooldown passed)?
|
||||
|
||||
- Did the user see this specific survey already?
|
||||
|
||||
@@ -50,13 +50,13 @@ Available Recontact Options include:
|
||||
|
||||

|
||||
|
||||
## Project-wide Global Waiting Time
|
||||
## Project-wide Survey Cooldown
|
||||
|
||||
The Global Waiting Time is a universal blocker to make sure that no user sees too many surveys. This is particularly helpful when several teams of large organisations use Formbricks at the same time.
|
||||
The Survey Cooldown is a universal blocker to make sure that no user sees too many surveys. This is particularly helpful when several teams of large organisations use Formbricks at the same time.
|
||||
|
||||
<Note>The default Global Waiting Time is set to 7 days.</Note>
|
||||
<Note>The default Survey Cooldown is set to 7 days.</Note>
|
||||
|
||||
To adjust the Global Waiting Time:
|
||||
To adjust the Survey Cooldown:
|
||||
|
||||
1. Visit Formbricks Settings
|
||||
|
||||
@@ -68,9 +68,9 @@ To adjust the Global Waiting Time:
|
||||
|
||||

|
||||
|
||||
## Overriding Global Waiting Time for a Specific Survey
|
||||
## Overriding Survey Cooldown for a Specific Survey
|
||||
|
||||
For specific surveys, you may need to override the default waiting time. Below is how you can do that:
|
||||
For specific surveys, you may need to override the default cooldown. Below is how you can do that:
|
||||
|
||||
1. In the Survey Editor, access the Settings Tab.
|
||||
|
||||
@@ -80,11 +80,11 @@ For specific surveys, you may need to override the default waiting time. Below i
|
||||
|
||||
4. Set a custom recontact period:
|
||||
|
||||
- **Always Show Survey**: Displays the survey whenever triggered, ignoring the waiting time.
|
||||
- **Always Show Survey**: Displays the survey whenever triggered, ignoring the cooldown.
|
||||
|
||||
- **Wait `X` days before showing this survey again**: Sets a specific interval before the survey can be shown again.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"dependencies": {
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"next": "16.1.1"
|
||||
"next": "16.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@azure/identity": "4.13.0",
|
||||
|
||||
@@ -188,7 +188,6 @@ function Calendar({
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
// @ts-expect-error - React types version mismatch - the project uses React 19 types, but some Radix UI packages (react-day-picker) bundle their own older React types, creating incompatible Ref type definitions
|
||||
Root: CalendarRoot,
|
||||
Chevron: CalendarChevron,
|
||||
DayButton: CalendarDayButton,
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface ProgressProps extends Omit<React.ComponentProps<"div">, "childr
|
||||
function Progress({ className, value, ...props }: Readonly<ProgressProps>): React.JSX.Element {
|
||||
const progressValue: number = typeof value === "number" ? value : 0;
|
||||
return (
|
||||
// @ts-expect-error - React types version mismatch - the project uses React 19 types, but some Radix UI packages (@radix-ui/react-progress) bundle their own older React types, creating incompatible Ref type definitions
|
||||
<ProgressPrimitive.Root
|
||||
data-slot="progress"
|
||||
value={progressValue}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"i18next": "25.5.2",
|
||||
"i18next-icu": "2.4.0",
|
||||
"isomorphic-dompurify": "2.33.0",
|
||||
"preact": "10.26.10",
|
||||
"preact": "10.28.2",
|
||||
"react-i18next": "15.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { ElementConditional } from "@/components/general/element-conditional";
|
||||
import { ScrollableContainer } from "@/components/wrappers/scrollable-container";
|
||||
import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, safeFormRequestSubmit } from "@/lib/utils";
|
||||
|
||||
interface BlockConditionalProps {
|
||||
block: TSurveyBlock;
|
||||
@@ -141,7 +141,7 @@ export function BlockConditional({
|
||||
response.length < rankingElement.choices.length);
|
||||
|
||||
if (hasIncompleteRanking) {
|
||||
form.requestSubmit();
|
||||
safeFormRequestSubmit(form);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -174,7 +174,7 @@ export function BlockConditional({
|
||||
element.type === TSurveyElementTypeEnum.ContactInfo
|
||||
) {
|
||||
if (!form.checkValidity()) {
|
||||
form.requestSubmit();
|
||||
safeFormRequestSubmit(form);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -191,14 +191,14 @@ export function BlockConditional({
|
||||
response &&
|
||||
hasUnansweredRows(response, element)
|
||||
) {
|
||||
form.requestSubmit();
|
||||
safeFormRequestSubmit(form);
|
||||
return false;
|
||||
}
|
||||
|
||||
// For other element types, check if required fields are empty
|
||||
// CTA elements should not block navigation even if marked required (as they are informational)
|
||||
if (element.type !== TSurveyElementTypeEnum.CTA && element.required && isEmptyResponse(response)) {
|
||||
form.requestSubmit();
|
||||
safeFormRequestSubmit(form);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ export function BlockConditional({
|
||||
block.elements.forEach((element) => {
|
||||
const form = elementFormRefs.current.get(element.id);
|
||||
if (form) {
|
||||
form.requestSubmit();
|
||||
safeFormRequestSubmit(form);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -96,7 +96,9 @@ export const StackedCard = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(el) => (cardRefs.current[dynamicQuestionIndex] = el)}
|
||||
ref={(el) => {
|
||||
cardRefs.current[dynamicQuestionIndex] = el;
|
||||
}}
|
||||
id={`questionCard-${dynamicQuestionIndex}`}
|
||||
data-testid={`questionCard-${dynamicQuestionIndex}`}
|
||||
key={dynamicQuestionIndex}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getMimeType,
|
||||
getShuffledChoicesIds,
|
||||
getShuffledRowIndices,
|
||||
safeFormRequestSubmit,
|
||||
} from "./utils";
|
||||
|
||||
// Mock crypto.getRandomValues for deterministic shuffle tests
|
||||
@@ -327,3 +328,54 @@ describe("findBlockByElementId", () => {
|
||||
expect(block).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("safeFormRequestSubmit", () => {
|
||||
let mockForm: HTMLFormElement;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock form element
|
||||
mockForm = document.createElement("form");
|
||||
});
|
||||
|
||||
test("should call requestSubmit when it's supported", () => {
|
||||
// Mock requestSubmit as a function
|
||||
const requestSubmitSpy = vi.fn();
|
||||
mockForm.requestSubmit = requestSubmitSpy;
|
||||
|
||||
safeFormRequestSubmit(mockForm);
|
||||
|
||||
expect(requestSubmitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should use fallback when requestSubmit is not supported", () => {
|
||||
// Remove requestSubmit to simulate iOS Safari 15.5
|
||||
mockForm.requestSubmit = undefined as unknown as typeof mockForm.requestSubmit;
|
||||
|
||||
const reportValiditySpy = vi.spyOn(mockForm, "reportValidity").mockReturnValue(true);
|
||||
const dispatchEventSpy = vi.spyOn(mockForm, "dispatchEvent");
|
||||
|
||||
safeFormRequestSubmit(mockForm);
|
||||
|
||||
expect(reportValiditySpy).toHaveBeenCalled();
|
||||
expect(dispatchEventSpy).toHaveBeenCalled();
|
||||
|
||||
// Verify the submit event was dispatched with correct properties
|
||||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
expect(dispatchedEvent.type).toBe("submit");
|
||||
expect(dispatchedEvent.bubbles).toBe(true);
|
||||
expect(dispatchedEvent.cancelable).toBe(true);
|
||||
});
|
||||
|
||||
test("should not dispatch event when reportValidity returns false", () => {
|
||||
// Remove requestSubmit to simulate iOS Safari 15.5
|
||||
mockForm.requestSubmit = undefined as unknown as typeof mockForm.requestSubmit;
|
||||
|
||||
const reportValiditySpy = vi.spyOn(mockForm, "reportValidity").mockReturnValue(false);
|
||||
const dispatchEventSpy = vi.spyOn(mockForm, "dispatchEvent");
|
||||
|
||||
safeFormRequestSubmit(mockForm);
|
||||
|
||||
expect(reportValiditySpy).toHaveBeenCalled();
|
||||
expect(dispatchEventSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,3 +275,27 @@ export const getFirstElementIdInBlock = (
|
||||
const block = survey.blocks.find((b) => b.id === blockId);
|
||||
return block?.elements[0]?.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely requests form submission with validation.
|
||||
* Provides a fallback for browsers that don't support requestSubmit() (iOS Safari < 16.0).
|
||||
* @param form The form element to submit
|
||||
*/
|
||||
export const safeFormRequestSubmit = (form: HTMLFormElement): void => {
|
||||
// Check if requestSubmit is supported (iOS Safari 16.0+, all modern browsers)
|
||||
if (typeof form.requestSubmit === "function") {
|
||||
form.requestSubmit();
|
||||
} else {
|
||||
// Fallback for older browsers (iOS Safari < 16.0)
|
||||
// reportValidity() triggers native validation UI
|
||||
if (!form.reportValidity()) {
|
||||
return;
|
||||
}
|
||||
// Dispatch submit event manually to trigger form submission handlers
|
||||
const submitEvent = new Event("submit", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
form.dispatchEvent(submitEvent);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from "zod";
|
||||
import { ZUserLocale } from "./user";
|
||||
|
||||
export const ZLinkSurveyEmailData = z.object({
|
||||
surveyId: z.string(),
|
||||
email: z.string(),
|
||||
suId: z.string().optional(),
|
||||
surveyName: z.string(),
|
||||
locale: z.string(),
|
||||
locale: ZUserLocale,
|
||||
logoUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
140
pnpm-lock.yaml
generated
140
pnpm-lock.yaml
generated
@@ -21,8 +21,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
next:
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
specifier: 16.1.3
|
||||
version: 16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
@@ -279,7 +279,7 @@ importers:
|
||||
version: 1.2.6(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@sentry/nextjs':
|
||||
specifier: 10.5.0
|
||||
version: 10.5.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.99.8(esbuild@0.25.11))
|
||||
version: 10.5.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.99.8(esbuild@0.25.11))
|
||||
'@t3-oss/env-nextjs':
|
||||
specifier: 0.13.4
|
||||
version: 0.13.4(arktype@2.1.29)(typescript@5.8.3)(zod@3.24.4)
|
||||
@@ -368,14 +368,14 @@ importers:
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1
|
||||
next:
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
specifier: 16.1.3
|
||||
version: 16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next-auth:
|
||||
specifier: 4.24.12
|
||||
version: 4.24.12(patch_hash=43pqaaqjvqhdw6jmcjbeq3fjse)(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.11)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
version: 4.24.12(patch_hash=43pqaaqjvqhdw6jmcjbeq3fjse)(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.11)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next-safe-action:
|
||||
specifier: 7.10.8
|
||||
version: 7.10.8(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.24.4)
|
||||
version: 7.10.8(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.24.4)
|
||||
node-fetch:
|
||||
specifier: 3.3.2
|
||||
version: 3.3.2
|
||||
@@ -982,8 +982,8 @@ importers:
|
||||
specifier: 2.33.0
|
||||
version: 2.33.0
|
||||
preact:
|
||||
specifier: 10.26.10
|
||||
version: 10.26.10
|
||||
specifier: 10.28.2
|
||||
version: 10.28.2
|
||||
react-i18next:
|
||||
specifier: 15.7.3
|
||||
version: 15.7.3(i18next@25.5.2(typescript@5.8.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.8.3)
|
||||
@@ -1002,13 +1002,13 @@ importers:
|
||||
version: link:../types
|
||||
'@preact/preset-vite':
|
||||
specifier: 2.10.1
|
||||
version: 2.10.1(@babel/core@7.28.5)(preact@10.26.10)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))
|
||||
version: 2.10.1(@babel/core@7.28.5)(preact@10.28.2)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))
|
||||
'@tailwindcss/postcss':
|
||||
specifier: 4.1.17
|
||||
version: 4.1.17
|
||||
'@testing-library/preact':
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4(preact@10.26.10)
|
||||
version: 3.2.4(preact@10.28.2)
|
||||
'@types/react':
|
||||
specifier: 19.1.4
|
||||
version: 19.1.4
|
||||
@@ -2789,8 +2789,8 @@ packages:
|
||||
'@next/env@16.0.9':
|
||||
resolution: {integrity: sha512-6284pl8c8n9PQidN63qjPVEu1uXXKjnmbmaLebOzIfTrSXdGiAPsIMRi4pk/+v/ezqweE1/B8bFqiAAfC6lMXg==}
|
||||
|
||||
'@next/env@16.1.1':
|
||||
resolution: {integrity: sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==}
|
||||
'@next/env@16.1.3':
|
||||
resolution: {integrity: sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==}
|
||||
|
||||
'@next/eslint-plugin-next@15.3.2':
|
||||
resolution: {integrity: sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==}
|
||||
@@ -2801,8 +2801,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-arm64@16.1.1':
|
||||
resolution: {integrity: sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==}
|
||||
'@next/swc-darwin-arm64@16.1.3':
|
||||
resolution: {integrity: sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
@@ -2813,8 +2813,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.1.1':
|
||||
resolution: {integrity: sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==}
|
||||
'@next/swc-darwin-x64@16.1.3':
|
||||
resolution: {integrity: sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
@@ -2825,8 +2825,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.1.1':
|
||||
resolution: {integrity: sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==}
|
||||
'@next/swc-linux-arm64-gnu@16.1.3':
|
||||
resolution: {integrity: sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2837,8 +2837,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.1':
|
||||
resolution: {integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==}
|
||||
'@next/swc-linux-arm64-musl@16.1.3':
|
||||
resolution: {integrity: sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
@@ -2849,8 +2849,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.1':
|
||||
resolution: {integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==}
|
||||
'@next/swc-linux-x64-gnu@16.1.3':
|
||||
resolution: {integrity: sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2861,8 +2861,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.1':
|
||||
resolution: {integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==}
|
||||
'@next/swc-linux-x64-musl@16.1.3':
|
||||
resolution: {integrity: sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
@@ -2873,8 +2873,8 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.1':
|
||||
resolution: {integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==}
|
||||
'@next/swc-win32-arm64-msvc@16.1.3':
|
||||
resolution: {integrity: sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
@@ -2885,8 +2885,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.1.1':
|
||||
resolution: {integrity: sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==}
|
||||
'@next/swc-win32-x64-msvc@16.1.3':
|
||||
resolution: {integrity: sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -8703,8 +8703,8 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
next@16.1.1:
|
||||
resolution: {integrity: sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==}
|
||||
next@16.1.3:
|
||||
resolution: {integrity: sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -9190,12 +9190,12 @@ packages:
|
||||
peerDependencies:
|
||||
preact: '>=10'
|
||||
|
||||
preact@10.26.10:
|
||||
resolution: {integrity: sha512-sqdfdSa8AZeJ+wfMYjFImIRTnhfyPSLCH+LEb1+BoRUDKLnE6AnvZeClx3Bkj2Q9nn44GFAefOKIx5oc54q93A==}
|
||||
|
||||
preact@10.26.6:
|
||||
resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==}
|
||||
|
||||
preact@10.28.2:
|
||||
resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -13791,7 +13791,7 @@ snapshots:
|
||||
|
||||
'@next/env@16.0.9': {}
|
||||
|
||||
'@next/env@16.1.1': {}
|
||||
'@next/env@16.1.3': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.3.2':
|
||||
dependencies:
|
||||
@@ -13800,49 +13800,49 @@ snapshots:
|
||||
'@next/swc-darwin-arm64@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-arm64@16.1.1':
|
||||
'@next/swc-darwin-arm64@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.1.1':
|
||||
'@next/swc-darwin-x64@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.1.1':
|
||||
'@next/swc-linux-arm64-gnu@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.1':
|
||||
'@next/swc-linux-arm64-musl@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.1':
|
||||
'@next/swc-linux-x64-gnu@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.1':
|
||||
'@next/swc-linux-x64-musl@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.1':
|
||||
'@next/swc-win32-arm64-msvc@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.0.9':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.1.1':
|
||||
'@next/swc-win32-x64-msvc@16.1.3':
|
||||
optional: true
|
||||
|
||||
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
|
||||
@@ -14312,12 +14312,12 @@ snapshots:
|
||||
dependencies:
|
||||
playwright: 1.56.1
|
||||
|
||||
'@preact/preset-vite@2.10.1(@babel/core@7.28.5)(preact@10.26.10)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
|
||||
'@preact/preset-vite@2.10.1(@babel/core@7.28.5)(preact@10.28.2)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5)
|
||||
'@prefresh/vite': 2.4.11(preact@10.26.10)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))
|
||||
'@prefresh/vite': 2.4.11(preact@10.28.2)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.28.5)
|
||||
debug: 4.4.3
|
||||
@@ -14332,20 +14332,20 @@ snapshots:
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2': {}
|
||||
|
||||
'@prefresh/core@1.5.9(preact@10.26.10)':
|
||||
'@prefresh/core@1.5.9(preact@10.28.2)':
|
||||
dependencies:
|
||||
preact: 10.26.10
|
||||
preact: 10.28.2
|
||||
|
||||
'@prefresh/utils@1.2.1': {}
|
||||
|
||||
'@prefresh/vite@2.4.11(preact@10.26.10)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
|
||||
'@prefresh/vite@2.4.11(preact@10.28.2)(vite@6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@prefresh/babel-plugin': 0.5.2
|
||||
'@prefresh/core': 1.5.9(preact@10.26.10)
|
||||
'@prefresh/core': 1.5.9(preact@10.28.2)
|
||||
'@prefresh/utils': 1.2.1
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
preact: 10.26.10
|
||||
preact: 10.28.2
|
||||
vite: 6.4.1(@types/node@22.15.18)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -15700,7 +15700,7 @@ snapshots:
|
||||
|
||||
'@sentry/core@10.5.0': {}
|
||||
|
||||
'@sentry/nextjs@10.5.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.99.8(esbuild@0.25.11))':
|
||||
'@sentry/nextjs@10.5.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.99.8(esbuild@0.25.11))':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.38.0
|
||||
@@ -15713,7 +15713,7 @@ snapshots:
|
||||
'@sentry/vercel-edge': 10.5.0
|
||||
'@sentry/webpack-plugin': 4.6.1(encoding@0.1.13)(webpack@5.99.8(esbuild@0.25.11))
|
||||
chalk: 3.0.0
|
||||
next: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next: 16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
resolve: 1.22.8
|
||||
rollup: 4.54.0
|
||||
stacktrace-parser: 0.1.11
|
||||
@@ -16561,10 +16561,10 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/preact@3.2.4(preact@10.26.10)':
|
||||
'@testing-library/preact@3.2.4(preact@10.28.2)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 8.20.1
|
||||
preact: 10.26.10
|
||||
preact: 10.28.2
|
||||
|
||||
'@testing-library/react@16.3.0(@testing-library/dom@8.20.1)(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
@@ -20620,13 +20620,13 @@ snapshots:
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
next-auth@4.24.12(patch_hash=43pqaaqjvqhdw6jmcjbeq3fjse)(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.11)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
next-auth@4.24.12(patch_hash=43pqaaqjvqhdw6jmcjbeq3fjse)(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.11)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@panva/hkdf': 1.2.1
|
||||
cookie: 0.7.2
|
||||
jose: 4.15.9
|
||||
next: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next: 16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
oauth: 0.9.15
|
||||
openid-client: 5.7.1
|
||||
preact: 10.26.6
|
||||
@@ -20637,9 +20637,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
nodemailer: 7.0.11
|
||||
|
||||
next-safe-action@7.10.8(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.24.4):
|
||||
next-safe-action@7.10.8(next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@3.24.4):
|
||||
dependencies:
|
||||
next: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next: 16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
@@ -20670,9 +20670,9 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
next@16.1.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@next/env': 16.1.1
|
||||
'@next/env': 16.1.3
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.9.11
|
||||
caniuse-lite: 1.0.30001762
|
||||
@@ -20681,14 +20681,14 @@ snapshots:
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
styled-jsx: 5.1.6(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.1.1
|
||||
'@next/swc-darwin-x64': 16.1.1
|
||||
'@next/swc-linux-arm64-gnu': 16.1.1
|
||||
'@next/swc-linux-arm64-musl': 16.1.1
|
||||
'@next/swc-linux-x64-gnu': 16.1.1
|
||||
'@next/swc-linux-x64-musl': 16.1.1
|
||||
'@next/swc-win32-arm64-msvc': 16.1.1
|
||||
'@next/swc-win32-x64-msvc': 16.1.1
|
||||
'@next/swc-darwin-arm64': 16.1.3
|
||||
'@next/swc-darwin-x64': 16.1.3
|
||||
'@next/swc-linux-arm64-gnu': 16.1.3
|
||||
'@next/swc-linux-arm64-musl': 16.1.3
|
||||
'@next/swc-linux-x64-gnu': 16.1.3
|
||||
'@next/swc-linux-x64-musl': 16.1.3
|
||||
'@next/swc-win32-arm64-msvc': 16.1.3
|
||||
'@next/swc-win32-x64-msvc': 16.1.3
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@playwright/test': 1.56.1
|
||||
sharp: 0.34.5
|
||||
@@ -21197,10 +21197,10 @@ snapshots:
|
||||
preact: 10.26.6
|
||||
pretty-format: 3.8.0
|
||||
|
||||
preact@10.26.10: {}
|
||||
|
||||
preact@10.26.6: {}
|
||||
|
||||
preact@10.28.2: {}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
|
||||
Reference in New Issue
Block a user