Compare commits

...

10 Commits

Author SHA1 Message Date
Matti Nannt
9573ae19e6 fix(security): upgrade next and lodash to fix vulnerabilities (#7179)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 06:51:37 +00:00
Matti Nannt
7b3f841c5e fix(security): upgrade qs to fix DoS vulnerability (#7178)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 05:53:59 +00:00
Dhruwang Jariwala
8f7d225d6a fix: jerky animation behaviour (#7158) 2026-01-23 12:26:57 +00:00
Anshuman Pandey
094b6dedba fix: fixes response card UI for cta question (#7157) 2026-01-23 10:29:01 +00:00
Anshuman Pandey
36f0be07c4 fix: handle server errors in survey publish flow (#7156) 2026-01-23 08:54:11 +00:00
Bhagya Amarasinghe
e079055a43 fix(helm): DB migration job (#7152) 2026-01-23 07:58:54 +00:00
Bhagya Amarasinghe
9ae9a3a9fc fix(helm): update ExternalSecret API version to v1 (#7153) 2026-01-23 07:03:50 +00:00
Dhruwang Jariwala
b4606c0113 fix: nps & rating rtl UI (#7154) 2026-01-23 06:46:41 +00:00
Dhruwang Jariwala
6be654ab60 fix: language variants not working for app surveys (#7151) 2026-01-23 06:46:21 +00:00
dependabot[bot]
95c2e24416 chore(deps): bump the npm_and_yarn group across 2 directories with 1 update (#7149)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matti Nannt <matti@formbricks.com>
2026-01-22 12:06:12 +00:00
34 changed files with 469 additions and 315 deletions

View File

@@ -23,7 +23,7 @@
"@tailwindcss/vite": "4.1.18",
"@typescript-eslint/parser": "8.53.0",
"@vitejs/plugin-react": "5.1.2",
"esbuild": "0.27.2",
"esbuild": "0.25.12",
"eslint-plugin-react-refresh": "0.4.26",
"eslint-plugin-storybook": "10.1.11",
"prop-types": "15.8.1",

View File

@@ -3,6 +3,7 @@
import { CheckCircle2Icon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TResponseWithQuotas } from "@formbricks/types/responses";
import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/constants";
import { TSurvey } from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
import { getLocalizedValue } from "@/lib/i18n/utils";
@@ -67,6 +68,16 @@ export const SingleResponseCardBody = ({
<VerifiedEmail responseData={response.data} />
)}
{elements.map((question) => {
// Skip CTA elements without external buttons only if they have no response data
// This preserves historical data from when buttonExternal was true
if (
question.type === TSurveyElementTypeEnum.CTA &&
!question.buttonExternal &&
!response.data[question.id]
) {
return null;
}
const skipped = skippedQuestions.find((skippedQuestionElement) =>
skippedQuestionElement.includes(question.id)
);

View File

@@ -425,11 +425,19 @@ export const SurveyMenuBar = ({
const segment = await handleSegmentUpdate();
clearSurveyLocalStorage();
await updateSurveyAction({
const publishResult = await updateSurveyAction({
...localSurvey,
status,
segment,
});
if (!publishResult?.data) {
const errorMessage = getFormattedErrorMessage(publishResult);
toast.error(errorMessage);
setIsSurveyPublishing(false);
return;
}
setIsSurveyPublishing(false);
// Set flag to prevent beforeunload warning during navigation
isSuccessfullySavedRef.current = true;
@@ -467,7 +475,7 @@ export const SurveyMenuBar = ({
/>
</div>
<div className="mt-3 flex items-center gap-2 sm:mt-0 sm:ml-4">
<div className="mt-3 flex items-center gap-2 sm:ml-4 sm:mt-0">
<AutoSaveIndicator isDraft={localSurvey.status === "draft"} lastSaved={lastAutoSaved} />
{!isStorageConfigured && (
<div>

View File

@@ -225,10 +225,10 @@ export const PreviewSurvey = ({
)}>
{previewMode === "mobile" && (
<>
<p className="absolute top-0 left-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
<p className="absolute left-0 top-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
Preview
</p>
<div className="absolute top-0 right-0 m-2">
<div className="absolute right-0 top-0 m-2">
<ResetProgressButton onClick={resetProgress} />
</div>
<MediaBackground
@@ -259,12 +259,13 @@ export const PreviewSurvey = ({
setBlockId = f;
}}
onFinished={onFinished}
placement={placement}
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</Modal>
) : (
<div className="flex h-full w-full flex-col justify-center px-1">
<div className="absolute top-5 left-5">
<div className="absolute left-5 top-5">
{!styling.isLogoHidden && (
<ClientLogo
environmentId={environment.id}
@@ -363,6 +364,7 @@ export const PreviewSurvey = ({
}}
onFinished={onFinished}
isSpamProtectionEnabled={isSpamProtectionEnabled}
placement={placement}
/>
</Modal>
) : (
@@ -371,7 +373,7 @@ export const PreviewSurvey = ({
styling={styling}
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
isEditorView>
<div className="absolute top-5 left-5">
<div className="absolute left-5 top-5">
{!styling.isLogoHidden && (
<ClientLogo
environmentId={environment.id}

View File

@@ -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.

View File

@@ -97,11 +97,11 @@
"jiti": "2.4.2",
"jsonwebtoken": "9.0.2",
"lexical": "0.36.2",
"lodash": "4.17.21",
"lodash": "4.17.23",
"lucide-react": "0.507.0",
"markdown-it": "14.1.0",
"mime-types": "3.0.1",
"next": "16.1.3",
"next": "16.1.6",
"next-auth": "4.24.12",
"next-safe-action": "7.10.8",
"node-fetch": "3.3.2",
@@ -158,7 +158,7 @@
"autoprefixer": "10.4.21",
"cross-env": "10.0.0",
"dotenv": "16.5.0",
"esbuild": "0.25.11",
"esbuild": "0.25.12",
"postcss": "8.5.3",
"resize-observer-polyfill": "1.5.1",
"ts-node": "10.9.2",

View File

@@ -47,8 +47,13 @@ run_with_timeout() {
}
echo "🗃️ Running database migrations..."
run_with_timeout 300 "database migration" node packages/database/dist/scripts/apply-migrations.js
# Check if migrations should be skipped (e.g., when using Helm migration job)
if [ "${SKIP_STARTUP_MIGRATION:-false}" = "true" ]; then
echo "⏭️ Skipping startup migrations (handled by migration job)"
else
echo "🗃️ Running database migrations..."
run_with_timeout 300 "database migration" node packages/database/dist/scripts/apply-migrations.js
fi
echo "🗃️ Running SAML database setup..."
run_with_timeout 60 "SAML database setup" node packages/database/dist/scripts/create-saml-database.js

View File

@@ -127,6 +127,10 @@ spec:
{{- end }}
{{- end }}
env:
{{- if .Values.migration.enabled }}
- name: SKIP_STARTUP_MIGRATION
value: "true"
{{- end }}
{{- range $key, $value := .Values.deployment.env }}
- name: {{ include "formbricks.tplvalues.render" ( dict "value" $key "context" $ ) }}
{{- if kindIs "string" $value }}

View File

@@ -1,7 +1,7 @@
{{- if (.Values.externalSecret).enabled }}
{{- range $nameSuffix, $data := .Values.externalSecret.files }}
---
apiVersion: external-secrets.io/v1beta1
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: {{ template "formbricks.name" $ }}-{{ $nameSuffix }}

View File

@@ -0,0 +1,95 @@
{{- if .Values.migration.enabled }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "formbricks.name" . }}-migration
labels:
{{- include "formbricks.labels" . | nindent 4 }}
annotations:
# ArgoCD sync hooks
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
argocd.argoproj.io/sync-wave: "-1"
{{- if .Values.migration.annotations }}
{{- toYaml .Values.migration.annotations | nindent 4 }}
{{- end }}
spec:
ttlSecondsAfterFinished: {{ .Values.migration.ttlSecondsAfterFinished | default 300 }}
backoffLimit: {{ .Values.migration.backoffLimit | default 3 }}
template:
metadata:
labels:
{{- include "formbricks.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: migration
spec:
restartPolicy: Never
{{- if .Values.deployment.nodeSelector }}
nodeSelector:
{{- toYaml .Values.deployment.nodeSelector | nindent 8 }}
{{- end }}
{{- if .Values.deployment.tolerations }}
tolerations:
{{- toYaml .Values.deployment.tolerations | nindent 8 }}
{{- end }}
{{- if .Values.deployment.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.deployment.imagePullSecrets | nindent 8 }}
{{- end }}
{{- if .Values.rbac.serviceAccount.enabled }}
serviceAccountName: {{ .Values.rbac.serviceAccount.name | default (include "formbricks.name" .) }}
{{- end }}
{{- if .Values.deployment.securityContext }}
securityContext:
{{- toYaml .Values.deployment.securityContext | nindent 8 }}
{{- end }}
containers:
- name: migration
image: {{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion | default "latest" }}
imagePullPolicy: {{ .Values.deployment.image.pullPolicy }}
command:
- node
- packages/database/dist/scripts/apply-migrations.js
{{- if or .Values.deployment.envFrom (or (and .Values.externalSecret.enabled (index .Values.externalSecret.files "app-secrets")) .Values.secret.enabled) }}
envFrom:
{{- if or .Values.secret.enabled (and .Values.externalSecret.enabled (index .Values.externalSecret.files "app-secrets")) }}
- secretRef:
name: {{ template "formbricks.name" . }}-app-secrets
{{- end }}
{{- range $value := .Values.deployment.envFrom }}
{{- if (eq .type "configmap") }}
- configMapRef:
{{- if .name }}
name: {{ include "formbricks.tplvalues.render" ( dict "value" $value.name "context" $ ) }}
{{- else if .nameSuffix }}
name: {{ template "formbricks.name" $ }}-{{ include "formbricks.tplvalues.render" ( dict "value" $value.nameSuffix "context" $ ) }}
{{- else }}
name: {{ template "formbricks.name" $ }}
{{- end }}
{{- end }}
{{- if (eq .type "secret") }}
- secretRef:
{{- if .name }}
name: {{ include "formbricks.tplvalues.render" ( dict "value" $value.name "context" $ ) }}
{{- else if .nameSuffix }}
name: {{ template "formbricks.name" $ }}-{{ include "formbricks.tplvalues.render" ( dict "value" $value.nameSuffix "context" $ ) }}
{{- else }}
name: {{ template "formbricks.name" $ }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
env:
{{- range $key, $value := .Values.deployment.env }}
- name: {{ include "formbricks.tplvalues.render" ( dict "value" $key "context" $ ) }}
{{- if kindIs "string" $value }}
value: {{ include "formbricks.tplvalues.render" ( dict "value" $value "context" $ ) | quote }}
{{- else }}
{{- toYaml $value | nindent 14 }}
{{- end }}
{{- end }}
{{- if .Values.migration.resources }}
resources:
{{- toYaml .Values.migration.resources | nindent 12 }}
{{- end }}
{{- end }}

View File

@@ -28,6 +28,32 @@ enterprise:
enabled: false
licenseKey: ""
##########################################################
# Database Migration Job Configuration Helm
##########################################################
migration:
# Enable migration job for ArgoCD deployments
# When enabled, migrations run as a PreSync hook before the deployment
# and the startup migration in the container is skipped
enabled: true
# Additional annotations for the migration job
annotations: {}
# Time to keep the job after completion (seconds)
ttlSecondsAfterFinished: 300
# Number of retries before marking the job as failed
backoffLimit: 3
# Resource requests and limits for the migration job
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
cpu: "100m"
##########################################################
# Deployment Configuration
##########################################################

View File

@@ -46,7 +46,7 @@
"dependencies": {
"react": "19.2.3",
"react-dom": "19.2.3",
"next": "16.1.3"
"next": "16.1.6"
},
"devDependencies": {
"@azure/identity": "4.13.0",
@@ -89,13 +89,14 @@
"node-forge": ">=1.3.2",
"tar-fs": "2.1.4",
"typeorm": ">=0.3.26",
"systeminformation": "5.27.14"
"systeminformation": "5.27.14",
"qs": ">=6.14.1"
},
"comments": {
"overrides": "Security fixes for transitive dependencies. Remove when upstream packages update: axios (CVE-2025-58754) - awaiting @boxyhq/saml-jackson update | node-forge (Dependabot #230) - awaiting @boxyhq/saml-jackson update | tar-fs (Dependabot #205) - awaiting upstream dependency updates | typeorm (Dependabot #223) - awaiting @boxyhq/saml-jackson update | systeminformation (Dependabot #241) - awaiting @opentelemetry/host-metrics update"
"overrides": "Security fixes for transitive dependencies. Remove when upstream packages update: axios (CVE-2025-58754) - awaiting @boxyhq/saml-jackson update | node-forge (Dependabot #230) - awaiting @boxyhq/saml-jackson update | tar-fs (Dependabot #205) - awaiting upstream dependency updates | typeorm (Dependabot #223) - awaiting @boxyhq/saml-jackson update | systeminformation (Dependabot #241) - awaiting @opentelemetry/host-metrics update | qs (Dependabot #245) - awaiting googleapis-common and stripe updates"
},
"patchedDependencies": {
"next-auth@4.24.12": "patches/next-auth@4.24.12.patch"
}
}
}
}

View File

@@ -170,7 +170,7 @@ export const getLanguageCode = (survey: TEnvironmentStateSurvey, language?: stri
const selectedLanguage = survey.languages.find((surveyLanguage) => {
return (
surveyLanguage.language.code === language.toLowerCase() ||
surveyLanguage.language.code.toLowerCase() === language.toLowerCase() ||
surveyLanguage.language.alias?.toLowerCase() === language.toLowerCase()
);
});

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { ElementError } from "@/components/general/element-error";
import { ElementHeader } from "@/components/general/element-header";
import { Label } from "@/components/general/label";
import { cn } from "@/lib/utils";
import { cn, getRTLScaleOptionClasses } from "@/lib/utils";
interface NPSProps {
/** Unique identifier for the element container */
@@ -97,18 +97,9 @@ function NPS({
const isLast = number === 10; // Last option is 10
const isFirst = number === 0; // First option is 0
// Determine border radius and border classes
// Use right border for all items to create separators, left border only on first item
let borderRadiusClasses = "";
let borderClasses = "border-t border-b border-r";
if (isFirst) {
borderRadiusClasses = dir === "rtl" ? "rounded-r-input" : "rounded-l-input";
borderClasses = "border-t border-b border-l border-r";
} else if (isLast) {
borderRadiusClasses = dir === "rtl" ? "rounded-l-input" : "rounded-r-input";
// Last item keeps right border for rounded corner
}
// Use CSS logical properties for RTL-aware borders and border radius
// The fieldset's dir attribute automatically handles direction
const { borderRadiusClasses, borderClasses } = getRTLScaleOptionClasses(isFirst, isLast);
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- label is interactive
@@ -145,7 +136,7 @@ function NPS({
setHoveredValue(null);
}}>
{colorCoding ? (
<div className={cn("absolute top-0 left-0 h-[6px] w-full", getNPSOptionColor(number))} />
<div className={cn("absolute left-0 top-0 h-[6px] w-full", getNPSOptionColor(number))} />
) : null}
<input
type="radio"
@@ -183,7 +174,7 @@ function NPS({
{/* NPS Options */}
<div className="relative">
<ElementError errorMessage={errorMessage} dir={dir} />
<fieldset className="w-full px-[2px]">
<fieldset className="w-full px-[2px]" dir={dir}>
<legend className="sr-only">NPS rating options</legend>
<div className="flex w-full">{npsOptions.map((number) => renderNPSOption(number))}</div>

View File

@@ -15,7 +15,7 @@ import {
TiredFace,
WearyFace,
} from "@/components/general/smileys";
import { cn } from "@/lib/utils";
import { cn, getRTLScaleOptionClasses } from "@/lib/utils";
/**
* Get smiley color class based on range and index
@@ -220,18 +220,9 @@ function Rating({
const isLast = totalLength === number;
const isFirst = number === 1;
// Determine border radius and border classes
// Use right border for all items to create separators, left border only on first item
let borderRadiusClasses = "";
let borderClasses = "border-t border-b border-r";
if (isFirst) {
borderRadiusClasses = dir === "rtl" ? "rounded-r-input" : "rounded-l-input";
borderClasses = "border-t border-b border-l border-r";
} else if (isLast) {
borderRadiusClasses = dir === "rtl" ? "rounded-l-input" : "rounded-r-input";
// Last item keeps right border for rounded corner
}
// Use CSS logical properties for RTL-aware borders and border radius
// The parent div's dir attribute automatically handles direction
const { borderRadiusClasses, borderClasses } = getRTLScaleOptionClasses(isFirst, isLast);
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- label is interactive
@@ -269,7 +260,7 @@ function Rating({
}}>
{colorCoding ? (
<div
className={cn("absolute top-0 left-0 h-[6px] w-full", getRatingNumberOptionColor(range, number))}
className={cn("absolute left-0 top-0 h-[6px] w-full", getRatingNumberOptionColor(range, number))}
/>
) : null}
<input
@@ -418,7 +409,7 @@ function Rating({
{/* Rating Options */}
<div className="relative">
<ElementError errorMessage={errorMessage} dir={dir} />
<fieldset className="w-full">
<fieldset className="w-full" dir={dir}>
<legend className="sr-only">Rating options</legend>
<div className="flex w-full px-[2px]">
{ratingOptions.map((number, index) => {

View File

@@ -35,3 +35,29 @@ export const stripInlineStyles = (html: string): string => {
KEEP_CONTENT: true,
});
};
/**
* Generate RTL-aware border radius and border classes for rating/NPS scale options
* Uses CSS logical properties that automatically adapt to text direction
* @param isFirst - Whether this is the first item in the scale
* @param isLast - Whether this is the last item in the scale
* @returns Object containing borderRadiusClasses and borderClasses
*/
export const getRTLScaleOptionClasses = (
isFirst: boolean,
isLast: boolean
): { borderRadiusClasses: string; borderClasses: string } => {
const borderRadiusClasses = cn(
isFirst &&
"[border-start-start-radius:var(--fb-input-border-radius)] [border-end-start-radius:var(--fb-input-border-radius)]",
isLast &&
"[border-start-end-radius:var(--fb-input-border-radius)] [border-end-end-radius:var(--fb-input-border-radius)]"
);
const borderClasses = cn(
"border-t border-b border-e", // block borders (top/bottom) and inline-end border
isFirst && "border-s" // inline-start border for first item
);
return { borderRadiusClasses, borderClasses };
};

View File

@@ -76,6 +76,7 @@ export function Survey({
isSpamProtectionEnabled,
dir = "auto",
setDir,
placement,
}: SurveyContainerProps) {
let apiClient: ApiClient | null = null;
@@ -743,7 +744,7 @@ export function Survey({
return (
<>
{localSurvey.type !== "link" ? (
<div className="bg-survey-bg flex h-6 justify-end pt-2 pr-2">
<div className="bg-survey-bg flex h-6 justify-end pr-2 pt-2">
<SurveyCloseButton onClose={onClose} />
</div>
) : null}
@@ -916,6 +917,7 @@ export function Survey({
setBlockId={setBlockId}
shouldResetBlockId={shouldResetQuestionId}
fullSizeCards={fullSizeCards}
placement={placement}
/>
);
}

View File

@@ -2,6 +2,7 @@ import { MutableRef } from "preact/hooks";
import { useEffect, useMemo, useState } from "preact/hooks";
import { JSX } from "preact/jsx-runtime";
import React from "react";
import { type TPlacement } from "@formbricks/types/common";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TCardArrangementOptions } from "@formbricks/types/styling";
@@ -17,6 +18,7 @@ interface StackedCardProps {
cardWidth: number;
hovered: boolean;
cardArrangement: TCardArrangementOptions;
placement: TPlacement;
}
export const StackedCard = ({
@@ -31,17 +33,24 @@ export const StackedCard = ({
cardWidth,
hovered,
cardArrangement,
placement,
}: StackedCardProps) => {
const isHidden = offset < 0;
const [delayedOffset, setDelayedOffset] = useState<number>(offset);
const [contentOpacity, setContentOpacity] = useState<number>(0);
const currentCardHeight = offset === 0 ? "auto" : offset < 0 ? "initial" : cardHeight;
const getBottomStyles = () => {
const getTopBottomStyles = () => {
if (survey.type !== "link")
return {
bottom: 0,
};
if (placement === "bottomLeft" || placement === "bottomRight") {
return {
bottom: 0,
};
} else if (placement === "topLeft" || placement === "topRight") {
return {
top: 0,
};
}
};
const getDummyCardContent = () => {
@@ -111,7 +120,7 @@ export const StackedCard = ({
pointerEvents: offset === 0 ? "auto" : "none",
...borderStyles,
...straightCardArrangementStyles,
...getBottomStyles(),
...getTopBottomStyles(),
}}
className="pointer rounded-custom bg-survey-bg absolute inset-x-0 overflow-hidden">
<div

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import type { JSX } from "react";
import { type TPlacement } from "@formbricks/types/common";
import { type TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { type TProjectStyling } from "@formbricks/types/project";
import { type TCardArrangementOptions } from "@formbricks/types/styling";
@@ -19,6 +20,7 @@ interface StackedCardsContainerProps {
setBlockId: (blockId: string) => void;
shouldResetBlockId?: boolean;
fullSizeCards: boolean;
placement?: TPlacement;
}
export function StackedCardsContainer({
@@ -30,6 +32,7 @@ export function StackedCardsContainer({
setBlockId,
shouldResetBlockId = true,
fullSizeCards = false,
placement = "bottomRight",
}: Readonly<StackedCardsContainerProps>) {
const [hovered, setHovered] = useState(false);
const highlightBorderColor = survey.styling?.overwriteThemeStyling
@@ -179,6 +182,7 @@ export function StackedCardsContainer({
cardWidth={cardWidth}
hovered={hovered}
cardArrangement={cardArrangement}
placement={placement}
/>
);
})

View File

@@ -36,18 +36,35 @@ export const renderSurvey = (props: SurveyContainerProps) => {
throw new Error(`renderSurvey: Element with id ${containerId} not found.`);
}
const { placement, darkOverlay, onClose, clickOutside, ...surveyInlineProps } = props;
// if survey type is link, we don't pass the placement, darkOverlay, clickOutside, onClose
if (props.survey.type === "link") {
const { placement, darkOverlay, onClose, clickOutside, ...surveyInlineProps } = props;
render(
h(
I18nProvider,
{ language },
h(RenderSurvey, {
...surveyInlineProps,
})
),
element
);
render(
h(
I18nProvider,
{ language },
h(RenderSurvey, {
...surveyInlineProps,
})
),
element
);
} else {
// For non-link surveys, pass placement through so it can be used in StackedCard
const { darkOverlay, onClose, clickOutside, ...surveyInlineProps } = props;
render(
h(
I18nProvider,
{ language },
h(RenderSurvey, {
...surveyInlineProps,
})
),
element
);
}
} else {
const modalContainer = document.createElement("div");
modalContainer.id = "formbricks-modal-container";

460
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff