mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 02:10:12 -06:00
Compare commits
8 Commits
randomize-
...
update-v1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d237a99a | ||
|
|
a371bdaedd | ||
|
|
dbbd77a8eb | ||
|
|
c28de7c079 | ||
|
|
05f1068e01 | ||
|
|
7103ec9877 | ||
|
|
9cd7a25343 | ||
|
|
2d028d18e5 |
@@ -39,6 +39,7 @@ DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=pu
|
||||
# See optional configurations below if you want to disable these features.
|
||||
|
||||
MAIL_FROM=noreply@example.com
|
||||
MAIL_FROM_NAME=Formbricks
|
||||
SMTP_HOST=localhost
|
||||
SMTP_PORT=1025
|
||||
# Enable SMTP_SECURE_ENABLED for TLS (port 465)
|
||||
@@ -207,3 +208,4 @@ UNKEY_ROOT_KEY=
|
||||
# Enable Prometheus metrics
|
||||
# PROMETHEUS_ENABLED=
|
||||
# PROMETHEUS_EXPORTER_PORT=
|
||||
|
||||
|
||||
43
.github/workflows/release-helm-chart.yml
vendored
Normal file
43
.github/workflows/release-helm-chart.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Publish Helm Chart
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract release version
|
||||
run: echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Install YQ
|
||||
uses: dcarbone/install-yq-action@v1.3.1
|
||||
|
||||
- name: Update Chart.yaml with new version
|
||||
run: |
|
||||
yq -i ".version = \"${VERSION#v}\"" helm-chart/Chart.yaml
|
||||
yq -i ".appVersion = \"${VERSION}\"" helm-chart/Chart.yaml
|
||||
|
||||
- name: Package Helm chart
|
||||
run: |
|
||||
helm package ./helm-chart
|
||||
|
||||
- name: Push Helm chart to GitHub Container Registry
|
||||
run: |
|
||||
helm push formbricks-${VERSION#v}.tgz oci://ghcr.io/formbricks/helm-charts
|
||||
93
.github/workflows/terrafrom-plan-and-apply.yml
vendored
Normal file
93
.github/workflows/terrafrom-plan-and-apply.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
name: 'Terraform'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'infra/terraform/**'
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
terraform:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
|
||||
aws-region: "eu-central-1"
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v3
|
||||
|
||||
- name: Terraform Format
|
||||
id: fmt
|
||||
run: terraform fmt -check -recursive
|
||||
continue-on-error: true
|
||||
working-directory: infra/terraform
|
||||
|
||||
# - name: Post Format
|
||||
# if: always() && github.ref != 'refs/heads/main' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure')
|
||||
# uses: robburger/terraform-pr-commenter@v1
|
||||
# with:
|
||||
# commenter_type: fmt
|
||||
# commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }}
|
||||
# commenter_exitcode: ${{ steps.fmt.outputs.exitcode }}
|
||||
|
||||
- name: Terraform Init
|
||||
id: init
|
||||
run: terraform init
|
||||
working-directory: infra/terraform
|
||||
|
||||
# - name: Post Init
|
||||
# if: always() && github.ref != 'refs/heads/main' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
|
||||
# uses: robburger/terraform-pr-commenter@v1
|
||||
# with:
|
||||
# commenter_type: init
|
||||
# commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}
|
||||
# commenter_exitcode: ${{ steps.init.outputs.exitcode }}
|
||||
|
||||
- name: Terraform Validate
|
||||
id: validate
|
||||
run: terraform validate
|
||||
working-directory: infra/terraform
|
||||
|
||||
# - name: Post Validate
|
||||
# if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
|
||||
# uses: robburger/terraform-pr-commenter@v1
|
||||
# with:
|
||||
# commenter_type: validate
|
||||
# commenter_input: ${{ format('{0}{1}', steps.validate.outputs.stdout, steps.validate.outputs.stderr) }}
|
||||
# commenter_exitcode: ${{ steps.validate.outputs.exitcode }}
|
||||
|
||||
- name: Terraform Plan
|
||||
id: plan
|
||||
run: terraform plan -out .planfile
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Post PR comment
|
||||
uses: borchero/terraform-plan-comment@v2
|
||||
if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
planfile: .planfile
|
||||
working-directory: "infra/terraform"
|
||||
skip-comment: true
|
||||
|
||||
- name: Terraform Apply
|
||||
id: apply
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: terraform apply .planfile
|
||||
working-directory: "infra/terraform"
|
||||
|
||||
@@ -231,6 +231,7 @@ export const ProjectSettings = ({
|
||||
<p className="text-sm text-slate-400">{t("common.preview")}</p>
|
||||
<div className="z-0 h-3/4 w-3/4">
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={previewSurvey(projectName || "my Product", t)}
|
||||
styling={{ brandColor: { light: brandColor } }}
|
||||
isBrandingEnabled={false}
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
"use server";
|
||||
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
|
||||
import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper";
|
||||
import { z } from "zod";
|
||||
import { getSpreadsheetNameById } from "@formbricks/lib/googleSheet/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { TIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
|
||||
import { ZIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
|
||||
|
||||
export async function getSpreadsheetNameByIdAction(
|
||||
googleSheetIntegration: TIntegrationGoogleSheets,
|
||||
environmentId: string,
|
||||
spreadsheetId: string
|
||||
) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) throw new AuthorizationError("Not authorized");
|
||||
const ZGetSpreadsheetNameByIdAction = z.object({
|
||||
googleSheetIntegration: ZIntegrationGoogleSheets,
|
||||
environmentId: z.string(),
|
||||
spreadsheetId: z.string(),
|
||||
});
|
||||
|
||||
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
|
||||
if (!isAuthorized) throw new AuthorizationError("Not authorized");
|
||||
const integrationData = structuredClone(googleSheetIntegration);
|
||||
integrationData.config.data.forEach((data) => {
|
||||
data.createdAt = new Date(data.createdAt);
|
||||
export const getSpreadsheetNameByIdAction = authenticatedActionClient
|
||||
.schema(ZGetSpreadsheetNameByIdAction)
|
||||
.action(async ({ ctx, parsedInput }) => {
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId: await getOrganizationIdFromEnvironmentId(parsedInput.environmentId),
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
{
|
||||
type: "projectTeam",
|
||||
projectId: await getProjectIdFromEnvironmentId(parsedInput.environmentId),
|
||||
minPermission: "readWrite",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const integrationData = structuredClone(parsedInput.googleSheetIntegration);
|
||||
integrationData.config.data.forEach((data) => {
|
||||
data.createdAt = new Date(data.createdAt);
|
||||
});
|
||||
|
||||
return await getSpreadsheetNameById(integrationData, parsedInput.spreadsheetId);
|
||||
});
|
||||
return await getSpreadsheetNameById(integrationData, spreadsheetId);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isValidGoogleSheetsUrl,
|
||||
} from "@/app/(app)/environments/[environmentId]/integrations/google-sheets/lib/util";
|
||||
import GoogleSheetLogo from "@/images/googleSheetsLogo.png";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Checkbox } from "@/modules/ui/components/checkbox";
|
||||
@@ -115,11 +116,18 @@ export const AddIntegrationModal = ({
|
||||
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
|
||||
}
|
||||
const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl);
|
||||
const spreadsheetName = await getSpreadsheetNameByIdAction(
|
||||
const spreadsheetNameResponse = await getSpreadsheetNameByIdAction({
|
||||
googleSheetIntegration,
|
||||
environmentId,
|
||||
spreadsheetId
|
||||
);
|
||||
spreadsheetId,
|
||||
});
|
||||
|
||||
if (!spreadsheetNameResponse?.data) {
|
||||
const errorMessage = getFormattedErrorMessage(spreadsheetNameResponse);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const spreadsheetName = spreadsheetNameResponse.data;
|
||||
|
||||
setIsLinkingSheet(true);
|
||||
integrationData.spreadsheetId = spreadsheetId;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import {
|
||||
DEBUG,
|
||||
MAIL_FROM,
|
||||
MAIL_FROM_NAME,
|
||||
SMTP_AUTHENTICATED,
|
||||
SMTP_HOST,
|
||||
SMTP_PASSWORD,
|
||||
@@ -69,7 +70,7 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
const emailDefaults = {
|
||||
from: `Formbricks <${MAIL_FROM ?? "noreply@formbricks.com"}>`,
|
||||
from: `${MAIL_FROM_NAME ?? "Formbricks"} <${MAIL_FROM ?? "noreply@formbricks.com"}>`,
|
||||
};
|
||||
await transporter.sendMail({ ...emailDefaults, ...emailData });
|
||||
|
||||
|
||||
@@ -243,7 +243,6 @@ export const SurveyEditor = ({
|
||||
environment={environment}
|
||||
previewType={localSurvey.type === "app" ? "modal" : "fullwidth"}
|
||||
languageCode={selectedLanguageCode}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -170,14 +170,14 @@ export const LinkSurvey = ({
|
||||
PRIVACY_URL={PRIVACY_URL}
|
||||
isBrandingEnabled={project.linkSurveyBranding}>
|
||||
<SurveyInline
|
||||
apiHost={!isPreview ? webAppUrl : undefined}
|
||||
environmentId={!isPreview ? survey.environmentId : undefined}
|
||||
apiHost={webAppUrl}
|
||||
environmentId={survey.environmentId}
|
||||
isPreviewMode={isPreview}
|
||||
survey={survey}
|
||||
styling={determineStyling()}
|
||||
languageCode={languageCode}
|
||||
isBrandingEnabled={project.linkSurveyBranding}
|
||||
shouldResetQuestionId={false}
|
||||
onFileUpload={isPreview ? async (file) => `https://formbricks.com/${file.name}` : undefined}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus -- need it as focus behaviour is different in normal surveys and survey preview
|
||||
autoFocus={autoFocus}
|
||||
prefillResponseData={prefillValue}
|
||||
|
||||
@@ -84,7 +84,6 @@ export const TemplateContainerWithPreview = ({
|
||||
project={project}
|
||||
environment={environment}
|
||||
languageCode={"default"}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
@@ -9,9 +9,7 @@ import { useTranslate } from "@tolgee/react";
|
||||
import { Variants, motion } from "framer-motion";
|
||||
import { ExpandIcon, MonitorIcon, ShrinkIcon, SmartphoneIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { TJsFileUploadParams } from "@formbricks/types/js";
|
||||
import { TProjectStyling } from "@formbricks/types/project";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
import { TSurvey, TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/surveys/types";
|
||||
import { Modal } from "./components/modal";
|
||||
import { TabOption } from "./components/tab-option";
|
||||
@@ -25,7 +23,6 @@ interface PreviewSurveyProps {
|
||||
project: Project;
|
||||
environment: Pick<Environment, "id" | "appSetupCompleted">;
|
||||
languageCode: string;
|
||||
onFileUpload: (file: TJsFileUploadParams["file"], config?: TUploadFileConfig) => Promise<string>;
|
||||
}
|
||||
|
||||
let surveyNameTemp: string;
|
||||
@@ -66,7 +63,6 @@ export const PreviewSurvey = ({
|
||||
project,
|
||||
environment,
|
||||
languageCode,
|
||||
onFileUpload,
|
||||
}: PreviewSurveyProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
|
||||
@@ -265,11 +261,11 @@ export const PreviewSurvey = ({
|
||||
borderRadius={styling?.roundness ?? 8}
|
||||
background={styling?.cardBackgroundColor?.light}>
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={survey}
|
||||
isBrandingEnabled={project.inAppSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
languageCode={languageCode}
|
||||
onFileUpload={onFileUpload}
|
||||
styling={styling}
|
||||
isCardBorderVisible={!styling.highlightBorderColor?.light}
|
||||
onClose={handlePreviewModalClose}
|
||||
@@ -288,9 +284,9 @@ export const PreviewSurvey = ({
|
||||
</div>
|
||||
<div className="z-10 w-full max-w-md rounded-lg border border-transparent">
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "link" }}
|
||||
isBrandingEnabled={project.linkSurveyBranding}
|
||||
onFileUpload={onFileUpload}
|
||||
languageCode={languageCode}
|
||||
responseCount={42}
|
||||
styling={styling}
|
||||
@@ -367,11 +363,11 @@ export const PreviewSurvey = ({
|
||||
borderRadius={styling.roundness ?? 8}
|
||||
background={styling.cardBackgroundColor?.light}>
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={survey}
|
||||
isBrandingEnabled={project.inAppSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
languageCode={languageCode}
|
||||
onFileUpload={onFileUpload}
|
||||
styling={styling}
|
||||
isCardBorderVisible={!styling.highlightBorderColor?.light}
|
||||
onClose={handlePreviewModalClose}
|
||||
@@ -394,10 +390,10 @@ export const PreviewSurvey = ({
|
||||
</div>
|
||||
<div className="z-0 w-full max-w-4xl rounded-lg border-transparent">
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "link" }}
|
||||
isBrandingEnabled={project.linkSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={onFileUpload}
|
||||
languageCode={languageCode}
|
||||
responseCount={42}
|
||||
styling={styling}
|
||||
|
||||
@@ -162,6 +162,7 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
borderRadius={project.styling.roundness ?? 8}>
|
||||
<Fragment key={surveyFormKey}>
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "app" }}
|
||||
isBrandingEnabled={project.inAppSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
@@ -187,6 +188,7 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
key={surveyFormKey}
|
||||
className={`${project.logo?.url && !project.styling.isLogoHidden && !isFullScreenPreview ? "mt-12" : ""} z-0 w-full max-w-md rounded-lg p-4`}>
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "link" }}
|
||||
isBrandingEnabled={project.linkSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "3.3.1",
|
||||
"version": "3.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
|
||||
@@ -36,6 +36,7 @@ x-environment: &environment
|
||||
|
||||
# Email Configuration
|
||||
# MAIL_FROM:
|
||||
# MAIL_FROM_NAME:
|
||||
# SMTP_HOST:
|
||||
# SMTP_PORT:
|
||||
# SMTP_USER:
|
||||
|
||||
@@ -224,6 +224,9 @@ EOT
|
||||
echo -n "Enter your SMTP configured Email ID: "
|
||||
read mail_from
|
||||
|
||||
echo -n "Enter your SMTP configured Email Name: "
|
||||
read mail_from_name
|
||||
|
||||
echo -n "Enter your SMTP Host URL: "
|
||||
read smtp_host
|
||||
|
||||
@@ -244,6 +247,7 @@ EOT
|
||||
|
||||
else
|
||||
mail_from=""
|
||||
mail_from_name=""
|
||||
smtp_host=""
|
||||
smtp_port=""
|
||||
smtp_user=""
|
||||
@@ -270,6 +274,7 @@ EOT
|
||||
|
||||
if [[ -n $mail_from ]]; then
|
||||
sed -i "s|# MAIL_FROM:|MAIL_FROM: \"$mail_from\"|" docker-compose.yml
|
||||
sed -i "s|# MAIL_FROM_NAME:|MAIL_FROM_NAME: \"$mail_from_name\"|" docker-compose.yml
|
||||
sed -i "s|# SMTP_HOST:|SMTP_HOST: \"$smtp_host\"|" docker-compose.yml
|
||||
sed -i "s|# SMTP_PORT:|SMTP_PORT: \"$smtp_port\"|" docker-compose.yml
|
||||
sed -i "s|# SMTP_SECURE_ENABLED:|SMTP_SECURE_ENABLED: $smtp_secure_enabled|" docker-compose.yml
|
||||
|
||||
@@ -1039,6 +1039,7 @@ x-environment: &environment
|
||||
|
||||
# Email Configuration
|
||||
MAIL_FROM:
|
||||
MAIL_FROM_NAME:
|
||||
SMTP_HOST:
|
||||
SMTP_PORT:
|
||||
SMTP_SECURE_ENABLED:
|
||||
|
||||
@@ -33,6 +33,7 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| RATE_LIMITING_DISABLED | Disables rate limiting if set to 1. | optional | |
|
||||
| INVITE_DISABLED | Disables the ability for invited users to create an account if set to 1. | optional | |
|
||||
| MAIL_FROM | Email address to send emails from. | optional (required if email services are to be enabled) | |
|
||||
| MAIL_FROM_NAME | Email name/title to send emails from. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_HOST | Host URL of your SMTP server. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | |
|
||||
| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | |
|
||||
|
||||
@@ -33,6 +33,7 @@ To enable email functionality, configure the following environment variables:
|
||||
```bash
|
||||
# Basic SMTP Configuration
|
||||
MAIL_FROM=noreply@yourdomain.com
|
||||
MAIL_FROM_NAME=Formbricks
|
||||
SMTP_HOST=smtp.yourprovider.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your_username
|
||||
@@ -75,6 +76,7 @@ If you're using the one-click setup with Docker Compose, you can either:
|
||||
environment:
|
||||
# Email Configuration
|
||||
MAIL_FROM: noreply@yourdomain.com
|
||||
MAIL_FROM_NAME: Formbricks
|
||||
SMTP_HOST: smtp.yourprovider.com
|
||||
SMTP_PORT: 587
|
||||
SMTP_USER: your_username
|
||||
@@ -95,6 +97,7 @@ environment:
|
||||
|
||||
```bash
|
||||
MAIL_FROM=noreply@yourdomain.com
|
||||
MAIL_FROM_NAME=Formbricks
|
||||
SMTP_HOST=smtp.sendgrid.net
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=apikey
|
||||
@@ -105,6 +108,7 @@ SMTP_PASSWORD=your_sendgrid_api_key
|
||||
|
||||
```bash
|
||||
MAIL_FROM=noreply@yourdomain.com
|
||||
MAIL_FROM_NAME=Formbricks
|
||||
SMTP_HOST=email-smtp.us-east-1.amazonaws.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your_ses_access_key
|
||||
@@ -115,6 +119,7 @@ SMTP_PASSWORD=your_ses_secret_key
|
||||
|
||||
```bash
|
||||
MAIL_FROM=your_email@gmail.com
|
||||
MAIL_FROM_NAME=Formbricks
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your_email@gmail.com
|
||||
|
||||
@@ -5,8 +5,7 @@ description: A Helm chart for Formbricks with PostgreSQL, Redis
|
||||
type: application
|
||||
|
||||
# Helm chart Version
|
||||
version: 3.3.1
|
||||
appVersion: v3.3.1
|
||||
version: 0.0.0-dev
|
||||
|
||||
keywords:
|
||||
- formbricks
|
||||
@@ -18,7 +17,6 @@ maintainers:
|
||||
- name: Formbricks
|
||||
email: info@formbricks.com
|
||||
|
||||
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: "16.4.16"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# formbricks
|
||||
|
||||
  
|
||||
 
|
||||
|
||||
A Helm chart for Formbricks with PostgreSQL, Redis
|
||||
|
||||
|
||||
@@ -94,8 +94,12 @@ spec:
|
||||
protocol: {{ $config.protocol | default "TCP" | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.deployment.envFrom }}
|
||||
{{- if or .Values.deployment.envFrom (and .Values.externalSecret.enabled (index .Values.externalSecret.files "app-secrets")) }}
|
||||
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:
|
||||
@@ -122,42 +126,8 @@ spec:
|
||||
env:
|
||||
{{- if and (.Values.enterprise.enabled) (ne .Values.enterprise.licenseKey "") }}
|
||||
- name: ENTERPRISE_LICENSE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: ENTERPRISE_LICENSE_KEY
|
||||
{{- else if and (.Values.enterprise.enabled) (eq .Values.enterprise.licenseKey "") }}
|
||||
- name: ENTERPRISE_LICENSE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: ENTERPRISE_LICENSE_KEY
|
||||
value: {{ .Values.enterprise.licenseKey | quote }}
|
||||
{{- end }}
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: REDIS_URL
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: DATABASE_URL
|
||||
- name: CRON_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: CRON_SECRET
|
||||
- name: ENCRYPTION_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: ENCRYPTION_KEY
|
||||
- name: NEXTAUTH_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "formbricks.name" . }}-app-secrets
|
||||
key: NEXTAUTH_SECRET
|
||||
{{- range $key, $value := .Values.deployment.env }}
|
||||
- name: {{ include "formbricks.tplvalues.render" ( dict "value" $key "context" $ ) }}
|
||||
{{ include "formbricks.tplvalues.render" ( dict "value" $value "context" $ ) | indent 10 }}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
---
|
||||
{{- if .Capabilities.APIVersions.Has "autoscaling/v2/HorizontalPodAutoscaler" }}
|
||||
apiVersion: autoscaling/v2
|
||||
{{- else }}
|
||||
apiVersion: autoscaling/v2beta2
|
||||
{{- end }}
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ template "formbricks.name" . }}
|
||||
|
||||
@@ -54,9 +54,9 @@ deployment:
|
||||
|
||||
# Environment variables from ConfigMaps or Secrets
|
||||
envFrom:
|
||||
# app-secrets:
|
||||
# type: secret
|
||||
# nameSuffix: app-secrets
|
||||
# app-secrets:
|
||||
# type: secret
|
||||
# nameSuffix: app-secrets
|
||||
|
||||
# Environment variables passed to the app container
|
||||
env:
|
||||
|
||||
@@ -10,3 +10,11 @@ data "aws_eks_cluster_auth" "eks" {
|
||||
data "aws_ecrpublic_authorization_token" "token" {
|
||||
provider = aws.virginia
|
||||
}
|
||||
|
||||
data "aws_iam_roles" "administrator" {
|
||||
name_regex = "AWSReservedSSO_AdministratorAccess"
|
||||
}
|
||||
|
||||
data "aws_iam_roles" "github" {
|
||||
name_regex = "formbricks-prod-github"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ module "iam_github_oidc_role" {
|
||||
"repo:formbricks/*:*",
|
||||
]
|
||||
policies = {
|
||||
Administrator = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
|
||||
Administrator = "arn:aws:iam::aws:policy/AdministratorAccess"
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
|
||||
@@ -249,7 +249,7 @@ module "eks" {
|
||||
cluster_name = "${local.name}-eks"
|
||||
cluster_version = "1.32"
|
||||
|
||||
enable_cluster_creator_admin_permissions = true
|
||||
enable_cluster_creator_admin_permissions = false
|
||||
cluster_endpoint_public_access = true
|
||||
|
||||
cluster_addons = {
|
||||
@@ -271,6 +271,41 @@ module "eks" {
|
||||
}
|
||||
}
|
||||
|
||||
kms_key_administrators = [
|
||||
tolist(data.aws_iam_roles.github.arns)[0],
|
||||
tolist(data.aws_iam_roles.administrator.arns)[0]
|
||||
]
|
||||
|
||||
kms_key_users = [
|
||||
tolist(data.aws_iam_roles.github.arns)[0],
|
||||
tolist(data.aws_iam_roles.administrator.arns)[0]
|
||||
]
|
||||
|
||||
access_entries = {
|
||||
administrator = {
|
||||
principal_arn = tolist(data.aws_iam_roles.administrator.arns)[0]
|
||||
policy_associations = {
|
||||
Admin = {
|
||||
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
|
||||
access_scope = {
|
||||
type = "cluster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
github = {
|
||||
principal_arn = tolist(data.aws_iam_roles.github.arns)[0]
|
||||
policy_associations = {
|
||||
Admin = {
|
||||
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
|
||||
access_scope = {
|
||||
type = "cluster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vpc_id = module.vpc.vpc_id
|
||||
subnet_ids = module.vpc.private_subnets
|
||||
control_plane_subnet_ids = module.vpc.intra_subnets
|
||||
@@ -573,95 +608,69 @@ resource "helm_release" "formbricks" {
|
||||
|
||||
values = [
|
||||
<<-EOT
|
||||
postgresql:
|
||||
enabled: false
|
||||
redis:
|
||||
enabled: false
|
||||
ingress:
|
||||
postgresql:
|
||||
enabled: false
|
||||
redis:
|
||||
enabled: false
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: alb
|
||||
hosts:
|
||||
- host: "app.${local.domain}"
|
||||
paths:
|
||||
- path: /
|
||||
pathType: "Prefix"
|
||||
serviceName: "formbricks"
|
||||
annotations:
|
||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||
alb.ingress.kubernetes.io/target-type: ip
|
||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
|
||||
alb.ingress.kubernetes.io/ssl-redirect: "443"
|
||||
alb.ingress.kubernetes.io/certificate-arn: ${module.acm.acm_certificate_arn}
|
||||
alb.ingress.kubernetes.io/healthcheck-path: "/health"
|
||||
alb.ingress.kubernetes.io/group.name: formbricks
|
||||
alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
|
||||
secret:
|
||||
enabled: false
|
||||
rbac:
|
||||
enabled: true
|
||||
serviceAccount:
|
||||
enabled: true
|
||||
ingressClassName: alb
|
||||
hosts:
|
||||
- host: "app.${local.domain}"
|
||||
paths:
|
||||
- path: /
|
||||
pathType: "Prefix"
|
||||
serviceName: "formbricks"
|
||||
name: formbricks
|
||||
annotations:
|
||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||
alb.ingress.kubernetes.io/target-type: ip
|
||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
|
||||
alb.ingress.kubernetes.io/ssl-redirect: "443"
|
||||
alb.ingress.kubernetes.io/certificate-arn: ${module.acm.acm_certificate_arn}
|
||||
alb.ingress.kubernetes.io/healthcheck-path: "/health"
|
||||
alb.ingress.kubernetes.io/group.name: formbricks
|
||||
alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
|
||||
secret:
|
||||
enabled: false
|
||||
rbac:
|
||||
enabled: true
|
||||
serviceAccount:
|
||||
enabled: true
|
||||
name: formbricks
|
||||
annotations:
|
||||
eks.amazonaws.com/role-arn: ${module.formkey-aws-access.iam_role_arn}
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
deployment:
|
||||
image:
|
||||
repository: "ghcr.io/formbricks/formbricks-experimental"
|
||||
tag: "open-telemetry-for-prometheus"
|
||||
pullPolicy: Always
|
||||
env:
|
||||
S3_BUCKET_NAME:
|
||||
value: ${module.s3-bucket.s3_bucket_id}
|
||||
RATE_LIMITING_DISABLED:
|
||||
value: "1"
|
||||
envFrom:
|
||||
app-parameters:
|
||||
type: secret
|
||||
nameSuffix: {RELEASE.name}-app-parameters
|
||||
annotations:
|
||||
deployed_at: ${timestamp()}
|
||||
externalSecret:
|
||||
enabled: true # Enable/disable ExternalSecrets
|
||||
secretStore:
|
||||
name: aws-secrets-manager
|
||||
kind: ClusterSecretStore
|
||||
refreshInterval: "1h"
|
||||
files:
|
||||
app-parameters:
|
||||
dataFrom:
|
||||
key: "/prod/formbricks/env"
|
||||
secretStore:
|
||||
name: aws-parameter-store
|
||||
kind: ClusterSecretStore
|
||||
app-secrets:
|
||||
data:
|
||||
DATABASE_URL:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: DATABASE_URL
|
||||
REDIS_URL:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: REDIS_URL
|
||||
CRON_SECRET:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: CRON_SECRET
|
||||
ENCRYPTION_KEY:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: ENCRYPTION_KEY
|
||||
NEXTAUTH_SECRET:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: NEXTAUTH_SECRET
|
||||
ENTERPRISE_LICENSE_KEY:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/enterprise"
|
||||
property: ENTERPRISE_LICENSE_KEY
|
||||
EOT
|
||||
eks.amazonaws.com/role-arn: ${module.formkey-aws-access.iam_role_arn}
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
deployment:
|
||||
image:
|
||||
repository: "ghcr.io/formbricks/formbricks-experimental"
|
||||
tag: "open-telemetry-for-prometheus"
|
||||
pullPolicy: Always
|
||||
env:
|
||||
S3_BUCKET_NAME:
|
||||
value: ${module.s3-bucket.s3_bucket_id}
|
||||
RATE_LIMITING_DISABLED:
|
||||
value: "1"
|
||||
envFrom:
|
||||
app-env:
|
||||
type: secret
|
||||
nameSuffix: app-env
|
||||
annotations:
|
||||
deployed_at: ${timestamp()}
|
||||
externalSecret:
|
||||
enabled: true # Enable/disable ExternalSecrets
|
||||
secretStore:
|
||||
name: aws-secrets-manager
|
||||
kind: ClusterSecretStore
|
||||
refreshInterval: "1h"
|
||||
files:
|
||||
app-env:
|
||||
dataFrom:
|
||||
key: "prod/formbricks/environment"
|
||||
app-secrets:
|
||||
dataFrom:
|
||||
key: "prod/formbricks/secrets"
|
||||
EOT
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
# Generate random secrets for formbricks
|
||||
resource "random_password" "nextauth_secret" {
|
||||
length = 32
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "random_password" "encryption_key" {
|
||||
length = 32
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "random_password" "cron_secret" {
|
||||
length = 32
|
||||
special = false
|
||||
}
|
||||
|
||||
# Create the first AWS Secrets Manager secret for environment variables
|
||||
resource "aws_secretsmanager_secret" "formbricks_app_secrets" {
|
||||
name = "prod/formbricks/secrets"
|
||||
@@ -24,10 +8,7 @@ resource "aws_secretsmanager_secret" "formbricks_app_secrets" {
|
||||
resource "aws_secretsmanager_secret_version" "formbricks_app_secrets" {
|
||||
secret_id = aws_secretsmanager_secret.formbricks_app_secrets.id
|
||||
secret_string = jsonencode({
|
||||
NEXTAUTH_SECRET = random_password.nextauth_secret.result
|
||||
ENCRYPTION_KEY = random_password.encryption_key.result
|
||||
CRON_SECRET = random_password.cron_secret.result
|
||||
DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks"
|
||||
REDIS_URL = "rediss://:${random_password.valkey.result}@${module.elasticache.replication_group_primary_endpoint_address}:6379"
|
||||
DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks"
|
||||
REDIS_URL = "rediss://:${random_password.valkey.result}@${module.elasticache.replication_group_primary_endpoint_address}:6379"
|
||||
})
|
||||
}
|
||||
|
||||
7
packages/android/.gitignore
vendored
7
packages/android/.gitignore
vendored
@@ -1,12 +1,7 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
||||
@@ -83,6 +83,7 @@ export const SMTP_PASSWORD = env.SMTP_PASSWORD;
|
||||
export const SMTP_AUTHENTICATED = env.SMTP_AUTHENTICATED !== "0";
|
||||
export const SMTP_REJECT_UNAUTHORIZED_TLS = env.SMTP_REJECT_UNAUTHORIZED_TLS !== "0";
|
||||
export const MAIL_FROM = env.MAIL_FROM;
|
||||
export const MAIL_FROM_NAME = env.MAIL_FROM_NAME;
|
||||
|
||||
export const NEXTAUTH_SECRET = env.NEXTAUTH_SECRET;
|
||||
export const ITEMS_PER_PAGE = 30;
|
||||
|
||||
@@ -49,6 +49,7 @@ export const env = createEnv({
|
||||
INTERCOM_SECRET_KEY: z.string().optional(),
|
||||
IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
|
||||
MAIL_FROM: z.string().email().optional(),
|
||||
MAIL_FROM_NAME: z.string().optional(),
|
||||
NEXTAUTH_SECRET: z.string().min(1),
|
||||
NOTION_OAUTH_CLIENT_ID: z.string().optional(),
|
||||
NOTION_OAUTH_CLIENT_SECRET: z.string().optional(),
|
||||
@@ -173,6 +174,7 @@ export const env = createEnv({
|
||||
INTERCOM_SECRET_KEY: process.env.INTERCOM_SECRET_KEY,
|
||||
IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD,
|
||||
MAIL_FROM: process.env.MAIL_FROM,
|
||||
MAIL_FROM_NAME: process.env.MAIL_FROM_NAME,
|
||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
|
||||
@@ -36,6 +36,7 @@ interface VariableStackEntry {
|
||||
export function Survey({
|
||||
apiHost,
|
||||
environmentId,
|
||||
isPreviewMode = false,
|
||||
userId,
|
||||
contactId,
|
||||
mode,
|
||||
@@ -150,7 +151,6 @@ export function Survey({
|
||||
return localSurvey.questions[0]?.id;
|
||||
});
|
||||
const [showError, setShowError] = useState(false);
|
||||
// flag state to store whether response processing has been completed or not, we ignore this check for survey editor preview and link survey preview where getSetIsResponseSendingFinished is undefined
|
||||
const [isResponseSendingFinished, setIsResponseSendingFinished] = useState(
|
||||
!getSetIsResponseSendingFinished
|
||||
);
|
||||
@@ -182,6 +182,11 @@ export function Survey({
|
||||
};
|
||||
|
||||
const onFileUploadApi = async (file: TJsFileUploadParams["file"], params?: TUploadFileConfig) => {
|
||||
if (isPreviewMode) {
|
||||
// return mock url since an url is required for the preview
|
||||
return `https://example.com/${file.name}`;
|
||||
}
|
||||
|
||||
if (!apiClient) {
|
||||
throw new Error("apiClient not initialized");
|
||||
}
|
||||
@@ -206,6 +211,17 @@ export function Survey({
|
||||
}, [questionId]);
|
||||
|
||||
const createDisplay = useCallback(async () => {
|
||||
// Skip display creation in preview mode but still trigger the onDisplayCreated callback
|
||||
if (isPreviewMode) {
|
||||
if (onDisplayCreated) {
|
||||
onDisplayCreated();
|
||||
}
|
||||
if (onDisplay) {
|
||||
onDisplay();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiClient && surveyState && responseQueue) {
|
||||
try {
|
||||
const display = await apiClient.createDisplay({
|
||||
@@ -229,7 +245,17 @@ export function Survey({
|
||||
console.error("error creating display: ", err);
|
||||
}
|
||||
}
|
||||
}, [apiClient, surveyState, responseQueue, survey.id, userId, contactId, onDisplayCreated]);
|
||||
}, [
|
||||
apiClient,
|
||||
surveyState,
|
||||
responseQueue,
|
||||
survey.id,
|
||||
userId,
|
||||
contactId,
|
||||
onDisplayCreated,
|
||||
isPreviewMode,
|
||||
onDisplay,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// call onDisplay when component is mounted
|
||||
@@ -385,6 +411,32 @@ export function Survey({
|
||||
|
||||
const onResponseCreateOrUpdate = useCallback(
|
||||
(responseUpdate: TResponseUpdate) => {
|
||||
// Always trigger the onResponse callback even in preview mode
|
||||
if (!apiHost || !environmentId) {
|
||||
onResponse?.({
|
||||
data: responseUpdate.data,
|
||||
ttc: responseUpdate.ttc,
|
||||
finished: responseUpdate.finished,
|
||||
variables: responseUpdate.variables,
|
||||
language: responseUpdate.language,
|
||||
endingId: responseUpdate.endingId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip response creation in preview mode but still trigger the onResponseCreated callback
|
||||
if (isPreviewMode) {
|
||||
if (onResponseCreated) {
|
||||
onResponseCreated();
|
||||
}
|
||||
|
||||
// When in preview mode, set isResponseSendingFinished to true if the response is finished
|
||||
if (responseUpdate.finished) {
|
||||
setIsResponseSendingFinished(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (surveyState && responseQueue) {
|
||||
if (contactId) {
|
||||
surveyState.updateContactId(contactId);
|
||||
@@ -415,7 +467,20 @@ export function Survey({
|
||||
}
|
||||
}
|
||||
},
|
||||
[surveyState, responseQueue, contactId, userId, survey, action, hiddenFieldsRecord, onResponseCreated]
|
||||
[
|
||||
apiHost,
|
||||
environmentId,
|
||||
isPreviewMode,
|
||||
surveyState,
|
||||
responseQueue,
|
||||
contactId,
|
||||
userId,
|
||||
survey,
|
||||
action,
|
||||
hiddenFieldsRecord,
|
||||
onResponseCreated,
|
||||
onResponse,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -446,25 +511,14 @@ export function Survey({
|
||||
onChange(surveyResponseData);
|
||||
onChangeVariables(calculatedVariables);
|
||||
|
||||
if (apiHost && environmentId) {
|
||||
onResponseCreateOrUpdate({
|
||||
data: surveyResponseData,
|
||||
ttc: responsettc,
|
||||
finished,
|
||||
variables: calculatedVariables,
|
||||
language: selectedLanguage,
|
||||
endingId,
|
||||
});
|
||||
} else {
|
||||
onResponse?.({
|
||||
data: surveyResponseData,
|
||||
ttc: responsettc,
|
||||
finished,
|
||||
variables: calculatedVariables,
|
||||
language: selectedLanguage,
|
||||
endingId,
|
||||
});
|
||||
}
|
||||
onResponseCreateOrUpdate({
|
||||
data: surveyResponseData,
|
||||
ttc: responsettc,
|
||||
finished,
|
||||
variables: calculatedVariables,
|
||||
language: selectedLanguage,
|
||||
endingId,
|
||||
});
|
||||
|
||||
if (nextQuestionId) {
|
||||
setQuestionId(nextQuestionId);
|
||||
@@ -573,7 +627,7 @@ export function Survey({
|
||||
onBack={onBack}
|
||||
ttc={ttc}
|
||||
setTtc={setTtc}
|
||||
onFileUpload={apiHost && environmentId ? onFileUploadApi : onFileUpload!}
|
||||
onFileUpload={onFileUpload ?? onFileUploadApi}
|
||||
isFirstQuestion={question.id === localSurvey.questions[0]?.id}
|
||||
skipPrefilled={skipPrefilled}
|
||||
prefilledQuestionValue={getQuestionPrefillData(question.id, offset)}
|
||||
|
||||
@@ -45,22 +45,23 @@ export const getShuffledChoicesIds = (
|
||||
const otherOption = choices.find((choice) => {
|
||||
return choice.id === "other";
|
||||
});
|
||||
|
||||
const shuffledChoices = otherOption ? [...choices.filter((choice) => choice.id !== "other")] : [...choices];
|
||||
|
||||
if (shuffleOption === "all") {
|
||||
shuffle(shuffledChoices);
|
||||
} else if (shuffleOption === "exceptLast") {
|
||||
if (otherOption) {
|
||||
}
|
||||
if (shuffleOption === "exceptLast") {
|
||||
const lastElement = shuffledChoices.pop();
|
||||
if (lastElement) {
|
||||
shuffle(shuffledChoices);
|
||||
} else {
|
||||
const lastElement = shuffledChoices.pop();
|
||||
if (lastElement) {
|
||||
shuffle(shuffledChoices);
|
||||
shuffledChoices.push(lastElement);
|
||||
}
|
||||
shuffledChoices.push(lastElement);
|
||||
}
|
||||
}
|
||||
if (otherOption) shuffledChoices.push(otherOption);
|
||||
|
||||
if (otherOption) {
|
||||
shuffledChoices.push(otherOption);
|
||||
}
|
||||
|
||||
return shuffledChoices.map((choice) => choice.id);
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface SurveyModalProps extends SurveyBaseProps {
|
||||
export interface SurveyContainerProps extends Omit<SurveyBaseProps, "onFileUpload"> {
|
||||
apiHost?: string;
|
||||
environmentId?: string;
|
||||
isPreviewMode?: boolean;
|
||||
userId?: string;
|
||||
contactId?: string;
|
||||
onDisplayCreated?: () => void | Promise<void>;
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"IS_FORMBRICKS_CLOUD",
|
||||
"INTERCOM_SECRET_KEY",
|
||||
"MAIL_FROM",
|
||||
"MAIL_FROM_NAME",
|
||||
"NEXT_PUBLIC_LAYER_API_KEY",
|
||||
"NEXT_PUBLIC_DOCSEARCH_APP_ID",
|
||||
"NEXT_PUBLIC_DOCSEARCH_API_KEY",
|
||||
|
||||
Reference in New Issue
Block a user