mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-23 00:10:55 -05:00
Compare commits
12 Commits
v3.4.1
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d1167ca80 | ||
|
|
480fd8b729 | ||
|
|
d294dfb9e8 | ||
|
|
d8e286082a | ||
|
|
a8f42ff429 | ||
|
|
133b4072bb | ||
|
|
e8687ca854 | ||
|
|
714ff94c9b | ||
|
|
1a1ed296f8 | ||
|
|
4233321ee2 | ||
|
|
9a75b7f145 | ||
|
|
717c092e2c |
10
.env.example
10
.env.example
@@ -97,9 +97,6 @@ PASSWORD_RESET_DISABLED=1
|
||||
# Organization Invite. Disable the ability for invited users to create an account.
|
||||
# INVITE_DISABLED=1
|
||||
|
||||
# Docker cron jobs. Disable the supercronic cron jobs in the Docker image (useful for cluster setups).
|
||||
# DOCKER_CRON_ENABLED=1
|
||||
|
||||
##########
|
||||
# Other #
|
||||
##########
|
||||
@@ -188,7 +185,7 @@ ENTERPRISE_LICENSE_KEY=
|
||||
UNSPLASH_ACCESS_KEY=
|
||||
|
||||
# The below is used for Next Caching (uses In-Memory from Next Cache if not provided)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
# REDIS_URL=redis://localhost:6379
|
||||
|
||||
# The below is used for Rate Limiting (uses In-Memory LRU Cache if not provided) (You can use a service like Webdis for this)
|
||||
# REDIS_HTTP_URL:
|
||||
@@ -207,8 +204,3 @@ UNKEY_ROOT_KEY=
|
||||
|
||||
# NEXT_PUBLIC_INTERCOM_APP_ID=
|
||||
# INTERCOM_SECRET_KEY=
|
||||
|
||||
# Enable Prometheus metrics
|
||||
# PROMETHEUS_ENABLED=
|
||||
# PROMETHEUS_EXPORTER_PORT=
|
||||
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Bug report
|
||||
description: "Found a bug? Please fill out the sections below. \U0001F44D"
|
||||
type: bug
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue-summary
|
||||
|
||||
10
.github/workflows/apply-issue-labels-to-pr.yml
vendored
10
.github/workflows/apply-issue-labels-to-pr.yml
vendored
@@ -5,9 +5,6 @@ on:
|
||||
types:
|
||||
- opened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
label_on_pr:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -18,13 +15,8 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Apply labels from linked issue to PR
|
||||
uses: actions/github-script@211cb3fefb35a799baa5156f9321bb774fe56294 # v5.2.0
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
7
.github/workflows/build-web.yml
vendored
7
.github/workflows/build-web.yml
vendored
@@ -12,12 +12,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Build & Cache Web Binaries
|
||||
|
||||
13
.github/workflows/chromatic.yml
vendored
13
.github/workflows/chromatic.yml
vendored
@@ -11,24 +11,19 @@ jobs:
|
||||
name: Run Chromatic
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
- name: Run Chromatic
|
||||
uses: chromaui/action@c93e0bc3a63aa176e14a75b61a31847cbfdd341c # latest
|
||||
uses: chromaui/action@latest
|
||||
with:
|
||||
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
|
||||
@@ -18,11 +18,6 @@ jobs:
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: cURL request
|
||||
if: ${{ env.APP_URL && env.CRON_SECRET }}
|
||||
run: |
|
||||
|
||||
7
.github/workflows/cron-weeklySummary.yml
vendored
7
.github/workflows/cron-weeklySummary.yml
vendored
@@ -7,9 +7,6 @@ on:
|
||||
schedule:
|
||||
# Runs “At 08:00 on Monday.” (see https://crontab.guru)
|
||||
- cron: "0 8 * * 1"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cron-weeklySummary:
|
||||
permissions:
|
||||
@@ -19,10 +16,6 @@ jobs:
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: cURL request
|
||||
if: ${{ env.APP_URL && env.CRON_SECRET }}
|
||||
run: |
|
||||
|
||||
27
.github/workflows/dependency-review.yml
vendored
27
.github/workflows/dependency-review.yml
vendored
@@ -1,27 +0,0 @@
|
||||
# Dependency Review Action
|
||||
#
|
||||
# This Action will scan dependency manifest files that change as part of a Pull Request,
|
||||
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
|
||||
# Once installed, if the workflow run is marked as required,
|
||||
# PRs introducing known-vulnerable packages will be blocked from merging.
|
||||
#
|
||||
# Source repository: https://github.com/actions/dependency-review-action
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
|
||||
17
.github/workflows/e2e.yml
vendored
17
.github/workflows/e2e.yml
vendored
@@ -43,21 +43,16 @@ jobs:
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
@@ -117,7 +112,7 @@ jobs:
|
||||
|
||||
- name: Azure login
|
||||
if: env.AZURE_ENABLED == 'true'
|
||||
uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
@@ -135,14 +130,14 @@ jobs:
|
||||
run: |
|
||||
pnpm test:e2e
|
||||
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: app-logs
|
||||
|
||||
10
.github/workflows/labeler.yml
vendored
10
.github/workflows/labeler.yml
vendored
@@ -4,9 +4,6 @@ on:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: Pull Request Labeler
|
||||
@@ -15,12 +12,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
||||
|
||||
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
@@ -12,11 +12,6 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
|
||||
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -50,10 +50,6 @@ jobs:
|
||||
checks: write
|
||||
statuses: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: fail if conditional jobs failed
|
||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
||||
run: exit 1
|
||||
|
||||
62
.github/workflows/prepare-release.yml
vendored
Normal file
62
.github/workflows/prepare-release.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Prepare release
|
||||
run-name: Prepare release ${{ inputs.next_version }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
next_version:
|
||||
required: true
|
||||
type: string
|
||||
description: "Version name"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
prepare_release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --local user.email "github-actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
cd apps/web
|
||||
pnpm version ${{ inputs.next_version }} --no-workspaces-update
|
||||
|
||||
- name: Commit changes and create a branch
|
||||
run: |
|
||||
branch_name="release-v${{ inputs.next_version }}"
|
||||
git checkout -b "$branch_name"
|
||||
git add .
|
||||
git commit -m "chore: release v${{ inputs.next_version }}"
|
||||
git push origin "$branch_name"
|
||||
|
||||
- name: Create pull request
|
||||
run: |
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "release-v${{ inputs.next_version }}" \
|
||||
--title "chore: bump version to v${{ inputs.next_version }}" \
|
||||
--body "This PR contains the changes for the v${{ inputs.next_version }} release."
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
13
.github/workflows/release-changesets.yml
vendored
13
.github/workflows/release-changesets.yml
vendored
@@ -26,28 +26,23 @@ jobs:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v2.5.2
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd # v2.2.4
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
# This expects you to have a script called release which does a build for your packages and calls changeset publish
|
||||
publish: pnpm release
|
||||
|
||||
@@ -17,9 +17,6 @@ env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -31,28 +28,23 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 # v1.6.0
|
||||
uses: depot/setup-action@v1
|
||||
|
||||
# Install the cosign tool except on PR
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@v3 # v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -62,7 +54,7 @@ jobs:
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@v5 # v5.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
@@ -70,7 +62,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: tw0fqmsx3c
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
|
||||
32
.github/workflows/release-docker-github.yml
vendored
32
.github/workflows/release-docker-github.yml
vendored
@@ -20,9 +20,6 @@ env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,40 +31,23 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- name: Get Release Tag
|
||||
id: extract_release_tag
|
||||
run: |
|
||||
TAG=${{ github.ref }}
|
||||
TAG=${TAG#refs/tags/v}
|
||||
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
||||
|
||||
- name: Update package.json version
|
||||
run: |
|
||||
sed -i "s/\"version\": \"0.0.0\"/\"version\": \"${{ env.RELEASE_TAG }}\"/" ./apps/web/package.json
|
||||
cat ./apps/web/package.json | grep version
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 # v1.6.0
|
||||
uses: depot/setup-action@v1
|
||||
|
||||
# Install the cosign tool except on PR
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@v3 # v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -77,7 +57,7 @@ jobs:
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@v5 # v5.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
@@ -85,7 +65,7 @@ jobs:
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: tw0fqmsx3c
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
|
||||
35
.github/workflows/release-docker.yml
vendored
35
.github/workflows/release-docker.yml
vendored
@@ -5,9 +5,6 @@ on:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release-image-on-dockerhub:
|
||||
name: Release on Dockerhub
|
||||
@@ -19,13 +16,17 @@ jobs:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Get Release Tag
|
||||
id: extract_release_tag
|
||||
@@ -34,22 +35,8 @@ jobs:
|
||||
TAG=${TAG#refs/tags/v}
|
||||
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
|
||||
|
||||
- name: Update package.json version
|
||||
run: |
|
||||
sed -i "s/\"version\": \"0.0.0\"/\"version\": \"${{ env.RELEASE_TAG }}\"/" ./apps/web/package.json
|
||||
cat ./apps/web/package.json | grep version
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@0a97817b6ade9f46837855d676c4cca3a2471fc9 # v4.2.1
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./apps/web/Dockerfile
|
||||
|
||||
51
.github/workflows/release-helm-chart.yml
vendored
51
.github/workflows/release-helm-chart.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Publish Helm Chart
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Extract release version
|
||||
run: echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5
|
||||
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@4075b4dca348d74bd83f2bf82d30f25d7c54539b # 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
|
||||
7
.github/workflows/scorecard.yml
vendored
7
.github/workflows/scorecard.yml
vendored
@@ -34,11 +34,6 @@ jobs:
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
@@ -76,6 +71,6 @@ jobs:
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
11
.github/workflows/semantic-pull-requests.yml
vendored
11
.github/workflows/semantic-pull-requests.yml
vendored
@@ -16,12 +16,7 @@ jobs:
|
||||
name: PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -40,7 +35,7 @@ jobs:
|
||||
revert
|
||||
ossgg
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
@@ -59,7 +54,7 @@ jobs:
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
|
||||
7
.github/workflows/sonarqube.yml
vendored
7
.github/workflows/sonarqube.yml
vendored
@@ -14,12 +14,7 @@ jobs:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
|
||||
74
.github/workflows/terrafrom-plan-and-apply.yml
vendored
74
.github/workflows/terrafrom-plan-and-apply.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: 'Terraform'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
terraform:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
|
||||
aws-region: "eu-central-1"
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
|
||||
|
||||
- name: Terraform Format
|
||||
id: fmt
|
||||
run: terraform fmt -check -recursive
|
||||
continue-on-error: true
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Terraform Init
|
||||
id: init
|
||||
run: terraform init
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Terraform Validate
|
||||
id: validate
|
||||
run: terraform validate
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Terraform Plan
|
||||
id: plan
|
||||
run: terraform plan -out .planfile
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Post PR comment
|
||||
uses: borchero/terraform-plan-comment@3399d8dbae8b05185e815e02361ede2949cd99c4 # v2.4.0
|
||||
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"
|
||||
|
||||
14
.github/workflows/test.yml
vendored
14
.github/workflows/test.yml
vendored
@@ -1,9 +1,6 @@
|
||||
name: Tests
|
||||
on:
|
||||
workflow_call:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Unit Tests
|
||||
@@ -13,21 +10,16 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
18
.github/workflows/tolgee-missing-key-check.yml
vendored
18
.github/workflows/tolgee-missing-key-check.yml
vendored
@@ -5,30 +5,18 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check-missing-translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
|
||||
31
.github/workflows/tolgee.yml
vendored
31
.github/workflows/tolgee.yml
vendored
@@ -3,7 +3,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
@@ -15,33 +15,30 @@ jobs:
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # This ensures we get the full git history
|
||||
|
||||
- name: Get source branch name
|
||||
id: branch-name
|
||||
run: |
|
||||
RAW_BRANCH="${{ github.head_ref }}"
|
||||
SOURCE_BRANCH=$(echo "$RAW_BRANCH" | sed 's/[^a-zA-Z0-9._\/-]//g')
|
||||
# For PR merges, use the head ref from the pull request event
|
||||
SOURCE_BRANCH="${{ github.head_ref }}"
|
||||
|
||||
# Only remove username prefix if needed
|
||||
if [[ "$SOURCE_BRANCH" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]+/ ]]; then
|
||||
PREFIX=${SOURCE_BRANCH%%/*}
|
||||
if [[ ! "$PREFIX" =~ ^(feature|fix|bugfix|hotfix|release|chore|docs|test|refactor|style|perf|build|ci|revert)$ ]]; then
|
||||
SOURCE_BRANCH=${SOURCE_BRANCH#*/}
|
||||
fi
|
||||
fi
|
||||
|
||||
# Safely add to environment variables using GitHub's recommended method
|
||||
# This prevents environment variable injection attacks
|
||||
echo "SOURCE_BRANCH<<EOF" >> $GITHUB_ENV
|
||||
echo "$SOURCE_BRANCH" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> $GITHUB_ENV
|
||||
echo "Detected source branch: $SOURCE_BRANCH"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18 # Ensure compatibility with your project
|
||||
|
||||
@@ -80,7 +77,7 @@ jobs:
|
||||
--yes
|
||||
|
||||
- name: Upload backup as artifact
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tolgee-backup-${{ github.sha }}
|
||||
path: ./tolgee-backup
|
||||
|
||||
@@ -17,12 +17,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/first-interaction@3c71ce730280171fd1cfb57c00c774f8998586f7 # v1
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |-
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -53,22 +53,4 @@ yarn-error.log*
|
||||
packages/lib/uploads
|
||||
apps/web/public/js
|
||||
packages/database/migrations
|
||||
branch.json
|
||||
.vercel
|
||||
|
||||
# Terraform
|
||||
infra/terraform/.terraform/
|
||||
**/.terraform.lock.hcl
|
||||
**/terraform.tfstate
|
||||
**/terraform.tfstate.*
|
||||
**/crash.log
|
||||
**/override.tf
|
||||
**/override.tf.json
|
||||
**/*.tfvars
|
||||
**/*.tfvars.json
|
||||
**/.terraformrc
|
||||
**/terraform.rc
|
||||
|
||||
# IntelliJ IDEA
|
||||
/.idea/
|
||||
/*.iml
|
||||
branch.json
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
images=($(yq eval '.services.*.image' docker-compose.dev.yml))
|
||||
images=($(yq eval '.services.*.image' packages/database/docker-compose.yml))
|
||||
|
||||
pull_image() {
|
||||
docker pull "$1"
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
{
|
||||
"language": "zh-Hant-TW",
|
||||
"path": "./packages/lib/messages/zh-Hant-TW.json"
|
||||
},
|
||||
{
|
||||
"language": "pt-PT",
|
||||
"path": "./packages/lib/messages/pt-PT.json"
|
||||
}
|
||||
],
|
||||
"forceMode": "OVERRIDE"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "8.18.0",
|
||||
"@typescript-eslint/parser": "8.18.0",
|
||||
"@vitejs/plugin-react": "4.3.4",
|
||||
"esbuild": "0.25.1",
|
||||
"esbuild": "0.25.0",
|
||||
"eslint-plugin-storybook": "0.11.1",
|
||||
"prop-types": "15.8.1",
|
||||
"storybook": "8.4.7",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22-alpine3.20@sha256:40be979442621049f40b1d51a26b55e281246b5de4e5f51a18da7beb6e17e3f9 AS base
|
||||
FROM node:22-alpine3.20 AS base
|
||||
|
||||
#
|
||||
## step 1: Prune monorepo
|
||||
@@ -111,12 +111,7 @@ VOLUME /home/nextjs/apps/web/uploads/
|
||||
RUN mkdir -p /home/nextjs/apps/web/saml-connection
|
||||
VOLUME /home/nextjs/apps/web/saml-connection
|
||||
|
||||
CMD if [ "${DOCKER_CRON_ENABLED:-1}" = "1" ]; then \
|
||||
echo "Starting cron jobs..."; \
|
||||
supercronic -quiet /app/docker/cronjobs & \
|
||||
else \
|
||||
echo "Docker cron jobs are disabled via DOCKER_CRON_ENABLED=0"; \
|
||||
fi; \
|
||||
CMD supercronic -quiet /app/docker/cronjobs & \
|
||||
(cd packages/database && npm run db:migrate:deploy) && \
|
||||
(cd packages/database && npm run db:create-saml-database:deploy) && \
|
||||
exec node apps/web/server.js
|
||||
|
||||
@@ -231,7 +231,6 @@ 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,41 +1,25 @@
|
||||
"use server";
|
||||
|
||||
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 { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { getSpreadsheetNameById } from "@formbricks/lib/googleSheet/service";
|
||||
import { ZIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { TIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
|
||||
|
||||
const ZGetSpreadsheetNameByIdAction = z.object({
|
||||
googleSheetIntegration: ZIntegrationGoogleSheets,
|
||||
environmentId: z.string(),
|
||||
spreadsheetId: z.string(),
|
||||
});
|
||||
export async function getSpreadsheetNameByIdAction(
|
||||
googleSheetIntegration: TIntegrationGoogleSheets,
|
||||
environmentId: string,
|
||||
spreadsheetId: string
|
||||
) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) throw new AuthorizationError("Not authorized");
|
||||
|
||||
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);
|
||||
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);
|
||||
});
|
||||
return await getSpreadsheetNameById(integrationData, spreadsheetId);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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";
|
||||
@@ -116,18 +115,11 @@ export const AddIntegrationModal = ({
|
||||
throw new Error(t("environments.integrations.select_at_least_one_question_error"));
|
||||
}
|
||||
const spreadsheetId = extractSpreadsheetIdFromUrl(spreadsheetUrl);
|
||||
const spreadsheetNameResponse = await getSpreadsheetNameByIdAction({
|
||||
const spreadsheetName = await getSpreadsheetNameByIdAction(
|
||||
googleSheetIntegration,
|
||||
environmentId,
|
||||
spreadsheetId,
|
||||
});
|
||||
|
||||
if (!spreadsheetNameResponse?.data) {
|
||||
const errorMessage = getFormattedErrorMessage(spreadsheetNameResponse);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const spreadsheetName = spreadsheetNameResponse.data;
|
||||
spreadsheetId
|
||||
);
|
||||
|
||||
setIsLinkingSheet(true);
|
||||
integrationData.spreadsheetId = spreadsheetId;
|
||||
|
||||
@@ -27,7 +27,7 @@ export const EditAlerts = ({
|
||||
return (
|
||||
<>
|
||||
{memberships.map((membership) => (
|
||||
<div key={membership.organization.id}>
|
||||
<>
|
||||
<div className="mb-5 grid grid-cols-6 items-center space-x-3">
|
||||
<div className="col-span-3 flex items-center space-x-3">
|
||||
<UsersIcon className="h-6 w-7 text-slate-600" />
|
||||
@@ -110,7 +110,7 @@ export const EditAlerts = ({
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler
|
||||
return (
|
||||
<>
|
||||
{memberships.map((membership) => (
|
||||
<div key={membership.organization.id}>
|
||||
<>
|
||||
<div className="mb-5 flex items-center space-x-3 text-sm font-medium">
|
||||
<UsersIcon className="h-6 w-7 text-slate-600" />
|
||||
|
||||
@@ -52,7 +52,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,8 @@ import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import {
|
||||
getOrganizationByEnvironmentId,
|
||||
getOrganizationsWhereUserIsSingleOwner,
|
||||
} from "@formbricks/lib/organization/service";
|
||||
import { getOrganizationsWhereUserIsSingleOwner } from "@formbricks/lib/organization/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { SettingsCard } from "../../components/SettingsCard";
|
||||
import { DeleteAccount } from "./components/DeleteAccount";
|
||||
@@ -73,9 +71,7 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
description={t("environments.settings.profile.two_factor_authentication_description")}
|
||||
buttons={[
|
||||
{
|
||||
text: IS_FORMBRICKS_CLOUD
|
||||
? t("common.start_free_trial")
|
||||
: t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${params.environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { MobileAppTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/MobileAppTab";
|
||||
import { WebAppTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/WebAppTab";
|
||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
export const AppTab = () => {
|
||||
export const AppTab = ({ environmentId }) => {
|
||||
const { t } = useTranslate();
|
||||
const [selectedTab, setSelectedTab] = useState("webapp");
|
||||
|
||||
@@ -21,7 +20,79 @@ export const AppTab = () => {
|
||||
handleOptionChange={(value) => setSelectedTab(value)}
|
||||
/>
|
||||
|
||||
<div className="mt-4">{selectedTab === "webapp" ? <WebAppTab /> : <MobileAppTab />}</div>
|
||||
<div className="mt-4">
|
||||
{selectedTab === "webapp" ? <WebAppTab environmentId={environmentId} /> : <MobileAppTab />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MobileAppTab = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<div>
|
||||
<p className="text-lg font-semibold text-slate-800">
|
||||
{t("environments.surveys.summary.how_to_embed_a_survey_on_your_react_native_app")}
|
||||
</p>
|
||||
<ol className="mt-4 list-decimal space-y-2 pl-5 text-sm text-slate-700">
|
||||
<li>
|
||||
{t("common.follow_these")}{" "}
|
||||
<Link
|
||||
href="https://formbricks.com/docs/developer-docs/react-native-in-app-surveys"
|
||||
target="_blank"
|
||||
className="decoration-brand-dark font-medium underline underline-offset-2">
|
||||
{t("environments.surveys.summary.setup_instructions_for_react_native_apps")}
|
||||
</Link>{" "}
|
||||
{t("environments.surveys.summary.to_connect_your_app_with_formbricks")}
|
||||
</li>
|
||||
</ol>
|
||||
<div className="mt-2 text-sm italic text-slate-700">
|
||||
{t("environments.surveys.summary.were_working_on_sdks_for_flutter_swift_and_kotlin")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WebAppTab = ({ environmentId }) => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<div>
|
||||
<p className="text-lg font-semibold text-slate-800">
|
||||
{t("environments.surveys.summary.how_to_embed_a_survey_on_your_web_app")}
|
||||
</p>
|
||||
<ol className="mt-4 list-decimal space-y-2 pl-5 text-sm text-slate-700">
|
||||
<li>
|
||||
{t("common.follow_these")}{" "}
|
||||
<Link
|
||||
href={`/environments/${environmentId}/project/app-connection`}
|
||||
target="_blank"
|
||||
className="decoration-brand-dark font-medium underline underline-offset-2">
|
||||
{t("environments.surveys.summary.setup_instructions")}
|
||||
</Link>{" "}
|
||||
{t("environments.surveys.summary.to_connect_your_web_app_with_formbricks")}
|
||||
</li>
|
||||
<li>
|
||||
{t("environments.surveys.summary.learn_how_to")}{" "}
|
||||
<Link
|
||||
href="https://formbricks.com/docs/app-surveys/user-identification"
|
||||
target="_blank"
|
||||
className="decoration-brand-dark font-medium underline underline-offset-2">
|
||||
{t("environments.surveys.summary.identify_users_and_set_attributes")}
|
||||
</Link>{" "}
|
||||
{t("environments.surveys.summary.to_run_highly_targeted_surveys")}.
|
||||
</li>
|
||||
<li>
|
||||
{t("environments.surveys.summary.make_sure_the_survey_type_is_set_to")}{" "}
|
||||
<b>{t("common.app_survey")}</b>
|
||||
</li>
|
||||
<li>{t("environments.surveys.summary.define_when_and_where_the_survey_should_pop_up")}</li>
|
||||
</ol>
|
||||
<div className="mt-4">
|
||||
<video autoPlay loop muted className="w-full rounded-xl border border-slate-200">
|
||||
<source src="/video/tooltips/change-survey-type-app.mp4" type="video/mp4" />
|
||||
{t("environments.surveys.summary.unsupported_video_tag_warning")}
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,7 +88,7 @@ export const EmbedView = ({
|
||||
locale={locale}
|
||||
/>
|
||||
) : activeId === "app" ? (
|
||||
<AppTab />
|
||||
<AppTab environmentId={environmentId} />
|
||||
) : null}
|
||||
<div className="mt-2 rounded-md p-3 text-center lg:hidden">
|
||||
{tabs.slice(0, 2).map((tab) => (
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import Link from "next/link";
|
||||
|
||||
export const MobileAppTab = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<Alert>
|
||||
<AlertTitle>{t("environments.surveys.summary.quickstart_mobile_apps")}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("environments.surveys.summary.quickstart_mobile_apps_description")}
|
||||
<Button asChild className="w-fit" size="sm" variant="link">
|
||||
<Link
|
||||
href="https://formbricks.com/docs/xm-and-surveys/surveys/website-app-surveys/framework-guides"
|
||||
target="_blank">
|
||||
{t("common.learn_more")}
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
@@ -85,10 +85,8 @@ export const PanelInfoView = ({ disableBack, handleInitialPageButton }: PanelInf
|
||||
</p>
|
||||
</div>
|
||||
<Button className="justify-center" asChild>
|
||||
<Link
|
||||
href="https://formbricks.com/docs/xm-and-surveys/surveys/link-surveys/market-research-panel"
|
||||
target="_blank">
|
||||
{t("common.learn_more")}
|
||||
<Link href="https://formbricks.com/docs/link-surveys/market-research-panel" target="_blank">
|
||||
{t("common.get_started")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import Link from "next/link";
|
||||
|
||||
export const WebAppTab = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<Alert>
|
||||
<AlertTitle>{t("environments.surveys.summary.quickstart_web_apps")}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("environments.surveys.summary.quickstart_web_apps_description")}
|
||||
<Button asChild className="w-fit" size="sm" variant="link">
|
||||
<Link
|
||||
href="https://formbricks.com/docs/xm-and-surveys/surveys/website-app-surveys/quickstart"
|
||||
target="_blank">
|
||||
{t("common.learn_more")}
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ const createTimeoutPromise = (ms, rejectReason) => {
|
||||
CacheHandler.onCreation(async () => {
|
||||
let client;
|
||||
|
||||
if (process.env.REDIS_URL) {
|
||||
if (process.env.REDIS_URL && process.env.ENTERPRISE_LICENSE_KEY) {
|
||||
try {
|
||||
// Create a Redis client.
|
||||
client = createClient({
|
||||
@@ -45,6 +45,8 @@ CacheHandler.onCreation(async () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (process.env.REDIS_URL) {
|
||||
console.log("Redis clustering requires an Enterprise License. Falling back to LRU cache.");
|
||||
}
|
||||
|
||||
/** @type {import("@neshca/cache-handler").Handler | null} */
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// instrumentation-node.ts
|
||||
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
|
||||
import { HostMetrics } from "@opentelemetry/host-metrics";
|
||||
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
||||
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
||||
import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node";
|
||||
import {
|
||||
Resource,
|
||||
detectResourcesSync,
|
||||
envDetector,
|
||||
hostDetector,
|
||||
processDetector,
|
||||
} from "@opentelemetry/resources";
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
const exporter = new PrometheusExporter({
|
||||
port: env.PROMETHEUS_EXPORTER_PORT ? parseInt(env.PROMETHEUS_EXPORTER_PORT) : 9464,
|
||||
endpoint: "/metrics",
|
||||
host: "0.0.0.0", // Listen on all network interfaces
|
||||
});
|
||||
|
||||
const detectedResources = detectResourcesSync({
|
||||
detectors: [envDetector, processDetector, hostDetector],
|
||||
});
|
||||
|
||||
const customResources = new Resource({});
|
||||
|
||||
const resources = detectedResources.merge(customResources);
|
||||
|
||||
const meterProvider = new MeterProvider({
|
||||
readers: [exporter],
|
||||
resource: resources,
|
||||
});
|
||||
|
||||
const hostMetrics = new HostMetrics({
|
||||
name: `otel-metrics`,
|
||||
meterProvider,
|
||||
});
|
||||
|
||||
registerInstrumentations({
|
||||
meterProvider,
|
||||
instrumentations: [new HttpInstrumentation(), new RuntimeNodeInstrumentation()],
|
||||
});
|
||||
|
||||
hostMetrics.start();
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
try {
|
||||
// Stop collecting metrics or flush them if needed
|
||||
await meterProvider.shutdown();
|
||||
// Possibly close other instrumentation resources
|
||||
} catch (e) {
|
||||
console.error("Error during graceful shutdown:", e);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
@@ -1,8 +1,25 @@
|
||||
import { registerOTel } from "@vercel/otel";
|
||||
import { LangfuseExporter } from "langfuse-vercel";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
// instrumentation.ts
|
||||
export const register = async () => {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs" && env.PROMETHEUS_ENABLED) {
|
||||
await import("./instrumentation-node");
|
||||
export async function register() {
|
||||
if (env.LANGFUSE_SECRET_KEY && env.LANGFUSE_PUBLIC_KEY && env.LANGFUSE_BASEURL) {
|
||||
registerOTel({
|
||||
serviceName: "formbricks-cloud-dev",
|
||||
traceExporter: new LangfuseExporter({
|
||||
debug: false,
|
||||
secretKey: env.LANGFUSE_SECRET_KEY,
|
||||
publicKey: env.LANGFUSE_PUBLIC_KEY,
|
||||
baseUrl: env.LANGFUSE_BASEURL,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
await import("./sentry.server.config");
|
||||
}
|
||||
|
||||
if (process.env.NEXT_RUNTIME === "edge") {
|
||||
await import("./sentry.edge.config");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,11 +360,13 @@ export const UploadContactsCSVButton = ({
|
||||
)}
|
||||
</div>
|
||||
{!csvResponse.length && (
|
||||
<div className="flex justify-start">
|
||||
<Button onClick={handleDownloadExampleCSV} variant="secondary">
|
||||
{t("environments.contacts.upload_contacts_modal_download_example_csv")}
|
||||
</Button>
|
||||
</div>
|
||||
<p>
|
||||
<a
|
||||
onClick={handleDownloadExampleCSV}
|
||||
className="cursor-pointer text-right text-sm text-slate-500">
|
||||
{t("environments.contacts.upload_contacts_modal_download_example_csv")}{" "}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export const ContactsPage = async ({
|
||||
description={t("environments.contacts.unlock_contacts_description")}
|
||||
buttons={[
|
||||
{
|
||||
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${params.environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -100,7 +100,7 @@ export const SegmentsPage = async ({
|
||||
description={t("environments.segments.unlock_segments_description")}
|
||||
buttons={[
|
||||
{
|
||||
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${params.environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -11,7 +11,7 @@ export const LanguagesLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="languages" loading />
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { EditLanguage } from "@/modules/ee/multi-language-surveys/components/edit-language";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
@@ -9,7 +12,7 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
@@ -32,6 +35,11 @@ export const LanguagesPage = async (props: { params: Promise<{ environmentId: st
|
||||
}
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
if (!isMultiLanguageAllowed) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@@ -55,20 +63,18 @@ export const LanguagesPage = async (props: { params: Promise<{ environmentId: st
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="languages" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="languages"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
title={t("environments.project.languages.multi_language_surveys")}
|
||||
description={t("environments.project.languages.multi_language_surveys_description")}>
|
||||
<EditLanguage
|
||||
project={project}
|
||||
locale={user.locale}
|
||||
isReadOnly={isReadOnly}
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
environmentId={params.environmentId}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
/>
|
||||
<EditLanguage project={project} locale={user.locale} isReadOnly={isReadOnly} />
|
||||
</SettingsCard>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
|
||||
import { ModalButton, UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
|
||||
import { Language } from "@prisma/client";
|
||||
import { TFnType, useTranslate } from "@tolgee/react";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { TFnType } from "@tolgee/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
@@ -26,9 +26,6 @@ interface EditLanguageProps {
|
||||
project: TProject;
|
||||
locale: TUserLocale;
|
||||
isReadOnly: boolean;
|
||||
isMultiLanguageAllowed: boolean;
|
||||
environmentId: string;
|
||||
isFormbricksCloud: boolean;
|
||||
}
|
||||
|
||||
const checkIfDuplicateExists = (arr: string[]) => {
|
||||
@@ -60,7 +57,7 @@ const validateLanguages = (languages: Language[], t: TFnType) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the chosen alias matches an ISO identifier of a language that hasn't been added
|
||||
// Check if the chosen alias matches an ISO identifier of a language that hasn’t been added
|
||||
for (const alias of languageAliases) {
|
||||
if (iso639Languages.some((language) => language.alpha2 === alias && !languageCodes.includes(alias))) {
|
||||
toast.error(t("environments.project.languages.conflict_between_selected_alias_and_another_language"), {
|
||||
@@ -73,14 +70,7 @@ const validateLanguages = (languages: Language[], t: TFnType) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export function EditLanguage({
|
||||
project,
|
||||
locale,
|
||||
isReadOnly,
|
||||
isMultiLanguageAllowed,
|
||||
environmentId,
|
||||
isFormbricksCloud,
|
||||
}: EditLanguageProps) {
|
||||
export function EditLanguage({ project, locale, isReadOnly }: EditLanguageProps) {
|
||||
const { t } = useTranslate();
|
||||
const [languages, setLanguages] = useState<Language[]>(project.languages);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
@@ -160,21 +150,6 @@ export function EditLanguage({
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const buttons: [ModalButton, ModalButton] = [
|
||||
{
|
||||
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
href: isFormbricksCloud
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
},
|
||||
{
|
||||
text: t("common.learn_more"),
|
||||
href: isFormbricksCloud
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/learn-more-self-hosting-license",
|
||||
},
|
||||
];
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
if (!validateLanguages(languages, t)) return;
|
||||
await Promise.all(
|
||||
@@ -204,75 +179,63 @@ export function EditLanguage({
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMultiLanguageAllowed ? (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="space-y-4">
|
||||
{languages.length > 0 ? (
|
||||
<>
|
||||
<LanguageLabels />
|
||||
{languages.map((language, index) => (
|
||||
<LanguageRow
|
||||
index={index}
|
||||
isEditing={isEditing}
|
||||
key={language.id}
|
||||
language={language}
|
||||
locale={locale}
|
||||
onDelete={() => handleDeleteLanguage(language.id)}
|
||||
onLanguageChange={(newLanguage: Language) => {
|
||||
const updatedLanguages = [...languages];
|
||||
updatedLanguages[index] = newLanguage;
|
||||
setLanguages(updatedLanguages);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm italic text-slate-500">
|
||||
{t("environments.project.languages.no_language_found")}
|
||||
</p>
|
||||
)}
|
||||
<AddLanguageButton onClick={handleAddLanguage} />
|
||||
</div>
|
||||
<EditSaveButtons
|
||||
isEditing={isEditing}
|
||||
onCancel={handleCancelChanges}
|
||||
disabled={isReadOnly}
|
||||
onEdit={() => {
|
||||
setIsEditing(true);
|
||||
}}
|
||||
onSave={handleSaveChanges}
|
||||
t={t}
|
||||
/>
|
||||
{isReadOnly && (
|
||||
<Alert variant="warning" className="mt-4">
|
||||
<AlertDescription>
|
||||
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
buttonText={t("environments.project.languages.remove_language")}
|
||||
isButtonDisabled={confirmationModal.isButtonDisabled}
|
||||
onConfirm={() => performLanguageDeletion(confirmationModal.languageId)}
|
||||
open={confirmationModal.isOpen}
|
||||
setOpen={() => {
|
||||
setConfirmationModal((prev) => ({ ...prev, isOpen: !prev.isOpen }));
|
||||
}}
|
||||
text={confirmationModal.text}
|
||||
title={t("environments.project.languages.remove_language")}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<UpgradePrompt
|
||||
title={t("environments.settings.general.use_multi_language_surveys_with_a_higher_plan")}
|
||||
description={t(
|
||||
"environments.settings.general.use_multi_language_surveys_with_a_higher_plan_description"
|
||||
)}
|
||||
buttons={buttons}
|
||||
/>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="space-y-4">
|
||||
{languages.length > 0 ? (
|
||||
<>
|
||||
<LanguageLabels />
|
||||
{languages.map((language, index) => (
|
||||
<LanguageRow
|
||||
index={index}
|
||||
isEditing={isEditing}
|
||||
key={language.id}
|
||||
language={language}
|
||||
locale={locale}
|
||||
onDelete={() => handleDeleteLanguage(language.id)}
|
||||
onLanguageChange={(newLanguage: Language) => {
|
||||
const updatedLanguages = [...languages];
|
||||
updatedLanguages[index] = newLanguage;
|
||||
setLanguages(updatedLanguages);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm italic text-slate-500">
|
||||
{t("environments.project.languages.no_language_found")}
|
||||
</p>
|
||||
)}
|
||||
<AddLanguageButton onClick={handleAddLanguage} />
|
||||
</div>
|
||||
<EditSaveButtons
|
||||
isEditing={isEditing}
|
||||
onCancel={handleCancelChanges}
|
||||
disabled={isReadOnly}
|
||||
onEdit={() => {
|
||||
setIsEditing(true);
|
||||
}}
|
||||
onSave={handleSaveChanges}
|
||||
t={t}
|
||||
/>
|
||||
{isReadOnly && (
|
||||
<Alert variant="warning" className="mt-4">
|
||||
<AlertDescription>
|
||||
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
<ConfirmationModal
|
||||
buttonText={t("environments.project.languages.remove_language")}
|
||||
isButtonDisabled={confirmationModal.isButtonDisabled}
|
||||
onConfirm={() => performLanguageDeletion(confirmationModal.languageId)}
|
||||
open={confirmationModal.isOpen}
|
||||
setOpen={() => {
|
||||
setConfirmationModal((prev) => ({ ...prev, isOpen: !prev.isOpen }));
|
||||
}}
|
||||
text={confirmationModal.text}
|
||||
title={t("environments.project.languages.remove_language")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -230,9 +230,7 @@ export const MultiLanguageCard: FC<MultiLanguageCardProps> = ({
|
||||
description={t("environments.surveys.edit.upgrade_notice_description")}
|
||||
buttons={[
|
||||
{
|
||||
text: isFormbricksCloud
|
||||
? t("common.start_free_trial")
|
||||
: t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: isFormbricksCloud
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/docs/self-hosting/license#30-day-trial-license-request",
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
|
||||
export const TeamsLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation activeId="teams" loading />
|
||||
</PageHeader>
|
||||
<div className="p-4">
|
||||
<div className="mb-4">
|
||||
<div className="h-6 w-1/3 animate-pulse rounded bg-slate-200" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{[...Array(3)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex animate-pulse items-center space-x-4 rounded border border-slate-200 p-4">
|
||||
<div className="h-10 w-10 rounded-full bg-slate-300" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="h-4 w-3/4 rounded bg-slate-200" />
|
||||
<div className="h-4 w-1/2 rounded bg-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { AccessView } from "@/modules/ee/teams/project-teams/components/access-view";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
@@ -33,6 +37,9 @@ export const ProjectTeams = async (props: { params: Promise<{ environmentId: str
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isOwner, isManager } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
const teams = await getTeamsByProjectId(project.id);
|
||||
|
||||
if (!teams) {
|
||||
@@ -43,8 +50,13 @@ export const ProjectTeams = async (props: { params: Promise<{ environmentId: str
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="teams" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="teams"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<AccessView environmentId={params.environmentId} teams={teams} isOwnerOrManager={isOwnerOrManager} />
|
||||
</PageContentWrapper>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const TeamsView = async ({
|
||||
|
||||
const buttons: [ModalButton, ModalButton] = [
|
||||
{
|
||||
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/docs/self-hosting/license#30-day-trial-license-request",
|
||||
|
||||
@@ -162,7 +162,7 @@ export const EmailCustomizationSettings = ({
|
||||
|
||||
const buttons: [ModalButton, ModalButton] = [
|
||||
{
|
||||
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: isFormbricksCloud
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -23,7 +23,7 @@ export const BrandingSettingsCard = async ({
|
||||
|
||||
const buttons: [ModalButton, ModalButton] = [
|
||||
{
|
||||
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -76,7 +76,6 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error in sendEmail:", error);
|
||||
throw new InvalidInputError("Incorrect SMTP credentials");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const AppConnectionLoading = () => {
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="app-connection" loading />
|
||||
</PageHeader>
|
||||
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator";
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { EnvironmentIdField } from "@/modules/projects/settings/(setup)/components/environment-id-field";
|
||||
import { SetupInstructions } from "@/modules/projects/settings/(setup)/components/setup-instructions";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
@@ -27,10 +31,18 @@ export const AppConnectionPage = async (props) => {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="app-connection" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="app-connection"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<div className="space-y-4">
|
||||
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/project/app-connection" />
|
||||
|
||||
@@ -42,7 +42,7 @@ export const APIKeysLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="api-keys" loading />
|
||||
</PageHeader>
|
||||
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
@@ -49,10 +53,18 @@ export const APIKeysPage = async (props) => {
|
||||
|
||||
const isReadOnly = isMember && !hasManageAccess;
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="api-keys" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="api-keys"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<EnvironmentNotice environmentId={environment.id} subPageUrl="/project/api-keys" />
|
||||
{environment.type === "development" ? (
|
||||
|
||||
@@ -8,13 +8,17 @@ import { usePathname } from "next/navigation";
|
||||
interface ProjectConfigNavigationProps {
|
||||
activeId: string;
|
||||
environmentId?: string;
|
||||
isMultiLanguageAllowed?: boolean;
|
||||
loading?: boolean;
|
||||
canDoRoleManagement?: boolean;
|
||||
}
|
||||
|
||||
export const ProjectConfigNavigation = ({
|
||||
activeId,
|
||||
environmentId,
|
||||
isMultiLanguageAllowed,
|
||||
loading,
|
||||
canDoRoleManagement,
|
||||
}: ProjectConfigNavigationProps) => {
|
||||
const { t } = useTranslate();
|
||||
const pathname = usePathname();
|
||||
@@ -39,6 +43,7 @@ export const ProjectConfigNavigation = ({
|
||||
label: t("common.survey_languages"),
|
||||
icon: <LanguagesIcon className="h-5 w-5" />,
|
||||
href: `/environments/${environmentId}/project/languages`,
|
||||
hidden: !isMultiLanguageAllowed,
|
||||
current: pathname?.includes("/languages"),
|
||||
},
|
||||
{
|
||||
@@ -65,8 +70,8 @@ export const ProjectConfigNavigation = ({
|
||||
{
|
||||
id: "teams",
|
||||
label: t("common.team_access"),
|
||||
icon: <UsersIcon className="h-5 w-5" />,
|
||||
href: `/environments/${environmentId}/project/teams`,
|
||||
hidden: !canDoRoleManagement,
|
||||
current: pathname?.includes("/teams"),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -28,7 +28,7 @@ export const GeneralSettingsLoading = () => {
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="general" loading />
|
||||
</PageHeader>
|
||||
{cards.map((card, index) => (
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
@@ -47,12 +51,21 @@ export const GeneralSettingsPage = async (props: { params: Promise<{ environment
|
||||
|
||||
const isReadOnly = isMember && !hasManageAccess;
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
const isOwnerOrManager = isOwner || isManager;
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="general" />
|
||||
{/* </PageHeader><PageHeader pageTitle={t("common.configuration")}> */}
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="general"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
title={t("common.project_name")}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/ser
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Configuration",
|
||||
title: "Config",
|
||||
};
|
||||
|
||||
export const ProjectSettingsLayout = async (props) => {
|
||||
|
||||
@@ -24,7 +24,7 @@ export const ProjectLookSettingsLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="look" loading />
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getWhiteLabelPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
getWhiteLabelPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { BrandingSettingsCard } from "@/modules/ee/whitelabel/remove-branding/components/branding-settings-card";
|
||||
@@ -47,10 +51,18 @@ export const ProjectLookSettingsPage = async (props: { params: Promise<{ environ
|
||||
|
||||
const isReadOnly = isMember && !hasManageAccess;
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="look" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="look"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
title={t("environments.project.look.theme")}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const TagsLoading = () => {
|
||||
const { t } = useTranslate();
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation activeId="tags" />
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
@@ -55,10 +59,18 @@ export const TagsPage = async (props) => {
|
||||
|
||||
const isReadOnly = isMember && !hasManageAccess;
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
|
||||
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.project_configuration")}>
|
||||
<ProjectConfigNavigation environmentId={params.environmentId} activeId="tags" />
|
||||
<PageHeader pageTitle={t("common.configuration")}>
|
||||
<ProjectConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="tags"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
canDoRoleManagement={canDoRoleManagement}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
title={t("environments.project.tags.manage_tags")}
|
||||
|
||||
@@ -243,6 +243,7 @@ export const SurveyEditor = ({
|
||||
environment={environment}
|
||||
previewType={localSurvey.type === "app" ? "modal" : "fullwidth"}
|
||||
languageCode={selectedLanguageCode}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export const TargetingLockedCard = ({ isFormbricksCloud, environmentId }: Target
|
||||
description={t("environments.surveys.edit.unlock_targeting_description")}
|
||||
buttons={[
|
||||
{
|
||||
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
|
||||
text: t("common.start_free_trial"),
|
||||
href: isFormbricksCloud
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/upgrade-self-hosting-license",
|
||||
|
||||
@@ -21,7 +21,7 @@ export const LegalFooter = ({
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-0 z-[1500] h-10 w-full" role="contentinfo">
|
||||
<div className="mx-auto flex h-full max-w-2xl items-center justify-center p-2 text-center text-xs text-slate-500">
|
||||
<div className="mx-auto flex h-full max-w-lg items-center justify-center p-2 text-center text-xs text-slate-500">
|
||||
{IMPRINT_URL && (
|
||||
<Link href={IMPRINT_URL} target="_blank" className="hover:underline" tabIndex={-1}>
|
||||
{t("common.imprint")}
|
||||
|
||||
@@ -80,7 +80,7 @@ export const LinkSurveyWrapper = ({
|
||||
onBackgroundLoaded={handleBackgroundLoaded}>
|
||||
<div className="flex max-h-dvh min-h-dvh items-center justify-center overflow-clip">
|
||||
{!styling.isLogoHidden && project.logo?.url && <ClientLogo projectLogo={project.logo} />}
|
||||
<div className="h-full w-full max-w-4xl space-y-6 px-1.5">
|
||||
<div className="h-full w-full max-w-lg space-y-6 px-1.5">
|
||||
{isPreview && (
|
||||
<div className="fixed left-0 top-0 flex w-full items-center justify-between bg-slate-600 p-2 px-4 text-center text-sm text-white shadow-sm">
|
||||
<div />
|
||||
|
||||
@@ -170,14 +170,14 @@ export const LinkSurvey = ({
|
||||
PRIVACY_URL={PRIVACY_URL}
|
||||
isBrandingEnabled={project.linkSurveyBranding}>
|
||||
<SurveyInline
|
||||
apiHost={webAppUrl}
|
||||
environmentId={survey.environmentId}
|
||||
isPreviewMode={isPreview}
|
||||
apiHost={!isPreview ? webAppUrl : undefined}
|
||||
environmentId={!isPreview ? survey.environmentId : undefined}
|
||||
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,6 +84,7 @@ export const TemplateContainerWithPreview = ({
|
||||
project={project}
|
||||
environment={environment}
|
||||
languageCode={"default"}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
@@ -8,8 +8,8 @@ export const SlackIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
{...props}>
|
||||
<rect width="3" height="8" x="13" y="2" rx="1.5" />
|
||||
<path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5" />
|
||||
|
||||
@@ -166,7 +166,7 @@ export const MediaBackground: React.FC<MediaBackgroundProps> = ({
|
||||
return (
|
||||
<div
|
||||
ref={ContentRef}
|
||||
className={`relative h-[90%] max-h-[42rem] w-[22rem] overflow-hidden rounded-[3rem] border-[6px] border-slate-400 ${getFilterStyle()}`}>
|
||||
className={`relative h-[90%] max-h-[40rem] w-[22rem] overflow-hidden rounded-[3rem] border-[6px] border-slate-400 ${getFilterStyle()}`}>
|
||||
{/* below element is use to create notch for the mobile device mockup */}
|
||||
<div className="absolute left-1/2 right-1/2 top-2 z-20 h-4 w-1/3 -translate-x-1/2 transform rounded-full bg-slate-400"></div>
|
||||
{surveyType === "link" && renderBackground()}
|
||||
|
||||
@@ -9,7 +9,9 @@ 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";
|
||||
@@ -23,6 +25,7 @@ interface PreviewSurveyProps {
|
||||
project: Project;
|
||||
environment: Pick<Environment, "id" | "appSetupCompleted">;
|
||||
languageCode: string;
|
||||
onFileUpload: (file: TJsFileUploadParams["file"], config?: TUploadFileConfig) => Promise<string>;
|
||||
}
|
||||
|
||||
let surveyNameTemp: string;
|
||||
@@ -63,6 +66,7 @@ export const PreviewSurvey = ({
|
||||
project,
|
||||
environment,
|
||||
languageCode,
|
||||
onFileUpload,
|
||||
}: PreviewSurveyProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
|
||||
@@ -261,11 +265,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}
|
||||
@@ -284,9 +288,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}
|
||||
@@ -363,11 +367,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}
|
||||
@@ -388,12 +392,12 @@ export const PreviewSurvey = ({
|
||||
<ClientLogo environmentId={environment.id} projectLogo={project.logo} previewSurvey />
|
||||
)}
|
||||
</div>
|
||||
<div className="z-0 w-full max-w-4xl rounded-lg border-transparent">
|
||||
<div className="z-0 w-full max-w-lg rounded-lg border-transparent">
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "link" }}
|
||||
isBrandingEnabled={project.linkSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={onFileUpload}
|
||||
languageCode={languageCode}
|
||||
responseCount={42}
|
||||
styling={styling}
|
||||
|
||||
@@ -81,7 +81,7 @@ export const QuestionToggleTable = ({
|
||||
</th>
|
||||
<th className="w-1/6 text-sm font-semibold">{t("common.show")}</th>
|
||||
<th className="w-1/6 text-sm font-semibold">{t("environments.surveys.edit.required")}</th>
|
||||
<th className="text-sm font-semibold">{t("common.label")}</th>
|
||||
<th className="text-sm font-semibold">{t("common.placeholder")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -16,72 +16,77 @@ interface SecondaryNavbarProps {
|
||||
export const SecondaryNavigation = ({ navigation, activeId, loading, ...props }: SecondaryNavbarProps) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<nav className="flex h-10 w-full items-center space-x-4" aria-label="Tabs">
|
||||
{loading
|
||||
? navigation.map((navElem) => (
|
||||
<div className="group flex h-full flex-col truncate" key={navElem.id}>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
className={cn(
|
||||
navElem.id === activeId ? "font-semibold text-slate-900" : "text-slate-500",
|
||||
"flex h-full items-center truncate px-3 text-sm font-medium",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
|
||||
navElem.id === activeId ? "bg-slate-300" : "bg-transparent group-hover:bg-slate-300",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
: navigation.map(
|
||||
(navElem) =>
|
||||
!navElem.hidden && (
|
||||
<div className="group flex h-full flex-col truncate" key={navElem.id}>
|
||||
{navElem.href ? (
|
||||
<Link
|
||||
href={navElem.href}
|
||||
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
|
||||
className={cn(
|
||||
navElem.id === activeId
|
||||
? "font-semibold text-slate-900"
|
||||
: "text-slate-500 hover:text-slate-700",
|
||||
"flex h-full items-center px-3 text-sm font-medium",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
|
||||
className={cn(
|
||||
navElem.id === activeId
|
||||
? "font-semibold text-slate-900"
|
||||
: "text-slate-500 hover:text-slate-700",
|
||||
"grow items-center px-3 text-sm font-medium transition-all duration-150 ease-in-out",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</button>
|
||||
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
|
||||
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
|
||||
{loading
|
||||
? navigation.map((navElem) => (
|
||||
<div className="group flex h-full flex-col" key={navElem.id}>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
className={cn(
|
||||
navElem.id === activeId ? "font-semibold text-slate-900" : "text-slate-500",
|
||||
"flex h-full items-center px-3 text-sm font-medium",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
|
||||
navElem.id === activeId ? "bg-brand-dark" : "bg-transparent group-hover:bg-slate-300",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
/>
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
<div
|
||||
className={cn(
|
||||
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
|
||||
navElem.id === activeId ? "bg-slate-300" : "bg-transparent group-hover:bg-slate-300",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
: navigation.map(
|
||||
(navElem) =>
|
||||
!navElem.hidden && (
|
||||
<div className="group flex h-full flex-col" key={navElem.id}>
|
||||
{navElem.href ? (
|
||||
<Link
|
||||
href={navElem.href}
|
||||
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
|
||||
className={cn(
|
||||
navElem.id === activeId
|
||||
? "font-semibold text-slate-900"
|
||||
: "text-slate-500 hover:text-slate-700",
|
||||
"flex h-full items-center px-3 text-sm font-medium",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
|
||||
className={cn(
|
||||
navElem.id === activeId
|
||||
? "font-semibold text-slate-900"
|
||||
: "text-slate-500 hover:text-slate-700",
|
||||
"grow items-center px-3 text-sm font-medium transition-all duration-150 ease-in-out",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
aria-current={navElem.id === activeId ? "page" : undefined}>
|
||||
{navElem.label}
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
|
||||
navElem.id === activeId
|
||||
? "bg-brand-dark"
|
||||
: "bg-transparent group-hover:bg-slate-300",
|
||||
navElem.hidden && "hidden"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
<div className="justify-self-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -162,7 +162,6 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
borderRadius={project.styling.roundness ?? 8}>
|
||||
<Fragment key={surveyFormKey}>
|
||||
<SurveyInline
|
||||
isPreviewMode={true}
|
||||
survey={{ ...survey, type: "app" }}
|
||||
isBrandingEnabled={project.inAppSurveyBranding}
|
||||
isRedirectDisabled={true}
|
||||
@@ -188,7 +187,6 @@ 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}
|
||||
|
||||
@@ -23,14 +23,11 @@ const nextConfig = {
|
||||
"app/api/packages": ["../../packages/js-core/dist/*", "../../packages/surveys/dist/*"],
|
||||
},
|
||||
i18n: {
|
||||
locales: ["en-US", "de-DE", "fr-FR", "pt-BR", "zh-Hant-TW", "pt-PT"],
|
||||
locales: ["en-US", "de-DE", "fr-FR", "pt-BR", "zh-Hant-TW"],
|
||||
localeDetection: false,
|
||||
defaultLocale: "en-US",
|
||||
},
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
serverComponentsExternalPackages: ["@opentelemetry/instrumentation"],
|
||||
},
|
||||
experimental: {},
|
||||
transpilePackages: ["@formbricks/database", "@formbricks/lib"],
|
||||
images: {
|
||||
remotePatterns: [
|
||||
@@ -111,10 +108,6 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
});
|
||||
config.resolve.fallback = {
|
||||
http: false, // Prevents Next.js from trying to bundle 'http'
|
||||
https: false,
|
||||
};
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "0.0.0",
|
||||
"version": "3.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
@@ -41,13 +41,8 @@
|
||||
"@lexical/rich-text": "0.21.0",
|
||||
"@lexical/table": "0.21.0",
|
||||
"@opentelemetry/api-logs": "0.56.0",
|
||||
"@opentelemetry/exporter-prometheus": "0.57.2",
|
||||
"@opentelemetry/host-metrics": "0.35.5",
|
||||
"@opentelemetry/instrumentation": "0.56.0",
|
||||
"@opentelemetry/instrumentation-http": "0.57.2",
|
||||
"@opentelemetry/instrumentation-runtime-node": "0.12.2",
|
||||
"@opentelemetry/sdk-logs": "0.56.0",
|
||||
"@opentelemetry/sdk-metrics": "1.30.1",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@prisma/client": "6.0.1",
|
||||
"@radix-ui/react-accordion": "1.2.2",
|
||||
@@ -109,12 +104,11 @@
|
||||
"next-safe-action": "7.10.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.16",
|
||||
"opentelemetry": "0.1.0",
|
||||
"optional": "0.1.4",
|
||||
"otplib": "12.0.1",
|
||||
"papaparse": "5.4.1",
|
||||
"posthog-js": "1.200.2",
|
||||
"prismjs": "1.30.0",
|
||||
"prismjs": "1.29.0",
|
||||
"react": "19.0.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-confetti": "6.1.0",
|
||||
|
||||
@@ -205,20 +205,22 @@ test.describe("Survey Create & Submit Response without logic", async () => {
|
||||
|
||||
// Address Question
|
||||
await expect(page.getByText(surveys.createAndSubmit.address.question)).toBeVisible();
|
||||
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.addressLine1)).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.addressLine1)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByLabel(surveys.createAndSubmit.address.placeholder.addressLine1)
|
||||
.getByPlaceholder(surveys.createAndSubmit.address.placeholder.addressLine1)
|
||||
.fill("This is my Address");
|
||||
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.city)).toBeVisible();
|
||||
await page.getByLabel(surveys.createAndSubmit.address.placeholder.city).fill("This is my city");
|
||||
await expect(page.getByLabel(surveys.createAndSubmit.address.placeholder.zip)).toBeVisible();
|
||||
await page.getByLabel(surveys.createAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.city)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.city).fill("This is my city");
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.zip)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await page.locator("#questionCard-10").getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// Contact Info Question
|
||||
await expect(page.getByText(surveys.createAndSubmit.contactInfo.question)).toBeVisible();
|
||||
await expect(page.getByLabel(surveys.createAndSubmit.contactInfo.placeholder)).toBeVisible();
|
||||
await page.getByLabel(surveys.createAndSubmit.contactInfo.placeholder).fill("John Doe");
|
||||
await expect(page.getByPlaceholder(surveys.createAndSubmit.contactInfo.placeholder)).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createAndSubmit.contactInfo.placeholder).fill("John Doe");
|
||||
await page.locator("#questionCard-11").getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// Ranking Question
|
||||
@@ -864,17 +866,21 @@ test.describe("Testing Survey with advanced logic", async () => {
|
||||
// Address Question
|
||||
await expect(page.getByText(surveys.createWithLogicAndSubmit.address.question)).toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.addressLine1)
|
||||
.fill("This is my Address");
|
||||
await expect(page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.city)).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.city)
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.city)
|
||||
.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.city)
|
||||
.fill("This is my city");
|
||||
await expect(page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.zip)).toBeVisible();
|
||||
await page.getByLabel(surveys.createWithLogicAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await expect(
|
||||
page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.zip)
|
||||
).toBeVisible();
|
||||
await page.getByPlaceholder(surveys.createWithLogicAndSubmit.address.placeholder.zip).fill("12345");
|
||||
await page.locator("#questionCard-13").getByRole("button", { name: "Next" }).click();
|
||||
|
||||
// loading spinner -> wait for it to disappear
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
scrape_configs:
|
||||
- job_name: "nodejs-app"
|
||||
scrape_interval: 5s
|
||||
static_configs:
|
||||
- targets: ["host.docker.internal:9464"]
|
||||
@@ -4,7 +4,7 @@ import { DevTools, Tolgee } from "@tolgee/web";
|
||||
const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY;
|
||||
const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL;
|
||||
|
||||
export const ALL_LANGUAGES = ["en-US", "de-DE", "fr-FR", "pt-BR", "pt-PT", "zh-Hant-TW"];
|
||||
export const ALL_LANGUAGES = ["en-US", "de-DE", "fr-FR", "pt-BR", "zh-Hant-TW"];
|
||||
|
||||
export const DEFAULT_LANGUAGE = "en-US";
|
||||
|
||||
@@ -20,7 +20,6 @@ export function TolgeeBase() {
|
||||
"de-DE": () => import("@formbricks/lib/messages/de-DE.json"),
|
||||
"fr-FR": () => import("@formbricks/lib/messages/fr-FR.json"),
|
||||
"pt-BR": () => import("@formbricks/lib/messages/pt-BR.json"),
|
||||
"pt-PT": () => import("@formbricks/lib/messages/pt-PT.json"),
|
||||
"zh-Hant-TW": () => import("@formbricks/lib/messages/zh-Hant-TW.json"),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
services:
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg17
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
mailhog:
|
||||
image: arjenz/mailhog # Copy of mailhog/MailHog to support linux/arm64
|
||||
ports:
|
||||
- 8025:8025 # web ui
|
||||
- 1025:1025 # smtp server
|
||||
|
||||
redis:
|
||||
image: redis:7.0.11
|
||||
ports:
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-02-28T09-55-16Z
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
- MINIO_ROOT_USER=devminio
|
||||
- MINIO_ROOT_PASSWORD=devminio123
|
||||
ports:
|
||||
- "9000:9000" # S3 API
|
||||
- "9001:9001" # Console
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
driver: local
|
||||
redis-data:
|
||||
driver: local
|
||||
minio-data:
|
||||
driver: local
|
||||
@@ -36,7 +36,6 @@ x-environment: &environment
|
||||
|
||||
# Email Configuration
|
||||
# MAIL_FROM:
|
||||
# MAIL_FROM_NAME:
|
||||
# SMTP_HOST:
|
||||
# SMTP_PORT:
|
||||
# SMTP_USER:
|
||||
@@ -69,9 +68,6 @@ x-environment: &environment
|
||||
# Set the below to your Unsplash API Key for their Survey Backgrounds
|
||||
# UNSPLASH_ACCESS_KEY:
|
||||
|
||||
# Set the below to 0 to disable cron jobs
|
||||
# DOCKER_CRON_ENABLED: 1
|
||||
|
||||
################################################### OPTIONAL (STORAGE) ###################################################
|
||||
|
||||
# Set the below to set a custom Upload Directory
|
||||
|
||||
@@ -262,9 +262,7 @@
|
||||
"group": "Auth & SSO",
|
||||
"icon": "lock",
|
||||
"pages": [
|
||||
"self-hosting/configuration/auth-sso/open-id-connect",
|
||||
"self-hosting/configuration/auth-sso/azure-ad-oauth",
|
||||
"self-hosting/configuration/auth-sso/google-oauth",
|
||||
"self-hosting/configuration/auth-sso/oauth",
|
||||
"self-hosting/configuration/auth-sso/saml-sso"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
title: Azure AD OAuth
|
||||
description: "Configure Microsoft Entra ID (Azure AD) OAuth for secure Single Sign-On with your Formbricks instance. Use enterprise-grade authentication for your survey platform."
|
||||
icon: "microsoft"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Single Sign-On (SSO) functionality, including OAuth integrations with Google, Microsoft Azure AD, and OpenID Connect, requires is part of the [Enterprise Edition](/self-hosting/advanced/license).
|
||||
</Note>
|
||||
|
||||
### Microsoft Entra ID
|
||||
|
||||
Do you have a Microsoft Entra ID Tenant? Integrate it with your Formbricks instance to allow users to log in using their existing Microsoft credentials. This guide will walk you through the process of setting up an Application Registration for your Formbricks instance.
|
||||
|
||||
### Requirements
|
||||
|
||||
- A Microsoft Entra ID Tenant populated with users. [Create a tenant as per Microsoft's documentation](https://learn.microsoft.com/en-us/entra/fundamentals/create-new-tenant).
|
||||
|
||||
- A Formbricks instance running and accessible.
|
||||
|
||||
- The callback URI for your Formbricks instance: `{WEBAPP_URL}/api/auth/callback/azure-ad`
|
||||
|
||||
## How to connect your Formbricks instance to Microsoft Entra
|
||||
|
||||
<Steps>
|
||||
<Step title="Access the Microsoft Entra admin center">
|
||||
- Login to the [Microsoft Entra admin center](https://entra.microsoft.com/).
|
||||
- Go to **Applications** > **App registrations** in the left menu.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Create a new app registration">
|
||||
- Click the **New registration** button at the top.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Configure the application">
|
||||
- Name your application something descriptive, such as `Formbricks SSO`.
|
||||
|
||||

|
||||
|
||||
- If you have multiple tenants/organizations, choose the appropriate **Supported account types** option. Otherwise, leave the default option for _Single Tenant_.
|
||||
|
||||

|
||||
|
||||
- Under **Redirect URI**, select **Web** for the platform and paste your Formbricks callback URI (see Requirements above).
|
||||
|
||||

|
||||
|
||||
- Click **Register** to create the App registration. You will be redirected to your new app's _Overview_ page after it is created.
|
||||
</Step>
|
||||
|
||||
<Step title="Collect application credentials">
|
||||
- On the _Overview_ page, under **Essentials**:
|
||||
- Copy the entry for **Application (client) ID** to populate the `AZUREAD_CLIENT_ID` variable.
|
||||
- Copy the entry for **Directory (tenant) ID** to populate the `AZUREAD_TENANT_ID` variable.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Create a client secret">
|
||||
- From your App registration's _Overview_ page, go to **Manage** > **Certificates & secrets**.
|
||||
|
||||

|
||||
|
||||
- Make sure you have the **Client secrets** tab active, and click **New client secret**.
|
||||
|
||||

|
||||
|
||||
- Enter a **Description**, set an **Expires** period, then click **Add**.
|
||||
|
||||
<Note>
|
||||
You will need to create a new client secret using these steps whenever your chosen expiry period ends.
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
- Copy the entry under **Value** to populate the `AZUREAD_CLIENT_SECRET` variable.
|
||||
|
||||
<Note>
|
||||
Microsoft will only show this value to you immediately after creation, and you will not be able to access it again. If you lose it, simply create a new secret.
|
||||
</Note>
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Update environment variables">
|
||||
- Update these environment variables in your `docker-compose.yml` or pass it like your other environment variables to the Formbricks container.
|
||||
|
||||
<Note>
|
||||
You must wrap the `AZUREAD_CLIENT_SECRET` value in double quotes (e.g., "THis~iS4faKe.53CreTvALu3"`) to prevent issues with special characters.
|
||||
</Note>
|
||||
|
||||
An example `.env` for Microsoft Entra ID in Formbricks would look like this:
|
||||
|
||||
```yml Formbricks Env for Microsoft Entra ID SSO
|
||||
AZUREAD_CLIENT_ID=a25cadbd-f049-4690-ada3-56a163a72f4c
|
||||
AZUREAD_TENANT_ID=2746c29a-a3a6-4ea1-8762-37816d4b7885
|
||||
AZUREAD_CLIENT_SECRET="THis~iS4faKe.53CreTvALu3"
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Restart and test">
|
||||
- Restart your Formbricks instance.
|
||||
- You're all set! Users can now sign up & log in using their Microsoft credentials associated with your Entra ID Tenant.
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -1,81 +0,0 @@
|
||||
---
|
||||
title: "Google OAuth"
|
||||
description: "Configure Google OAuth for secure Single Sign-On with your Formbricks instance. Implement enterprise-grade authentication for your survey platform with Google credentials."
|
||||
icon: "google"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Single Sign-On (SSO) functionality, including OAuth integrations with Google, Microsoft Azure AD, and OpenID Connect, requires is part of the [Enterprise Edition](/self-hosting/advanced/license).
|
||||
</Note>
|
||||
|
||||
### Google OAuth
|
||||
|
||||
Integrating Google OAuth with your Formbricks instance allows users to log in using their Google credentials, ensuring a secure and streamlined user experience. This guide will walk you through the process of setting up Google OAuth for your Formbricks instance.
|
||||
|
||||
### Requirements
|
||||
|
||||
- A Google Cloud Platform (GCP) account
|
||||
|
||||
- A Formbricks instance running
|
||||
|
||||
### How to connect your Formbricks instance to Google
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a GCP Project">
|
||||
- Navigate to the [GCP Console](https://console.cloud.google.com/).
|
||||
- From the projects list, select a project or create a new one.
|
||||
</Step>
|
||||
|
||||
<Step title="Setting up OAuth 2.0">
|
||||
- If the **APIs & services** page isn't already open, open the console left side menu and select **APIs & services**.
|
||||
- On the left, click **Credentials**.
|
||||
- Click **Create Credentials**, then select **OAuth client ID**.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OAuth Consent Screen">
|
||||
- If this is your first time creating a client ID, configure your consent screen by clicking **Consent Screen**.
|
||||
- Fill in the necessary details and under **Authorized domains**, add the domain where your Formbricks instance is hosted.
|
||||
</Step>
|
||||
|
||||
<Step title="Create OAuth 2.0 Client IDs">
|
||||
- Select the application type **Web application** for your project and enter any additional information required.
|
||||
- Ensure to specify authorized JavaScript origins and authorized redirect URIs.
|
||||
|
||||
```
|
||||
Authorized JavaScript origins: {WEBAPP_URL}
|
||||
Authorized redirect URIs: {WEBAPP_URL}/api/auth/callback/google
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Update Environment Variables in Docker">
|
||||
- To integrate the Google OAuth, you have two options: either update the environment variables in the docker-compose file or directly add them to the running container.
|
||||
|
||||
- In your Docker setup directory, open the `.env` file, and add or update the following lines with the `Client ID` and `Client Secret` obtained from Google Cloud Platform:
|
||||
|
||||
```sh
|
||||
GOOGLE_CLIENT_ID=your-client-id-here
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret-here
|
||||
```
|
||||
|
||||
- Alternatively, you can add the environment variables directly to the running container using the following commands (replace `container_id` with your actual Docker container ID):
|
||||
|
||||
```sh
|
||||
docker exec -it container_id /bin/bash
|
||||
export GOOGLE_CLIENT_ID=your-client-id-here
|
||||
export GOOGLE_CLIENT_SECRET=your-client-secret-here
|
||||
exit
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Restart Your Formbricks Instance">
|
||||
<Note>
|
||||
Restarting your Docker containers may cause a brief period of downtime. Plan accordingly.
|
||||
</Note>
|
||||
|
||||
- Once the environment variables have been updated, it's crucial to restart your Docker containers to apply the changes. This ensures that your Formbricks instance can utilize the new Google OAuth configuration for user authentication.
|
||||
|
||||
- Navigate to your Docker setup directory where your `docker-compose.yml` file is located.
|
||||
|
||||
- Run the following command to bring down your current Docker containers and then bring them back up with the updated environment configuration.
|
||||
</Step>
|
||||
</Steps>
|
||||
208
docs/self-hosting/configuration/auth-sso/oauth.mdx
Normal file
208
docs/self-hosting/configuration/auth-sso/oauth.mdx
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: OAuth
|
||||
description: "OAuth for Formbricks"
|
||||
icon: "key"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Single Sign-On (SSO) functionality, including OAuth integrations with Google, Microsoft Entra ID, Github and OpenID Connect, requires a valid Formbricks Enterprise License.
|
||||
</Note>
|
||||
|
||||
### Google OAuth
|
||||
|
||||
Integrating Google OAuth with your Formbricks instance allows users to log in using their Google credentials, ensuring a secure and streamlined user experience. This guide will walk you through the process of setting up Google OAuth for your Formbricks instance.
|
||||
|
||||
#### Requirements:
|
||||
|
||||
- A Google Cloud Platform (GCP) account.
|
||||
|
||||
- A Formbricks instance running and accessible.
|
||||
|
||||
#### Steps:
|
||||
|
||||
1. **Create a GCP Project**:
|
||||
|
||||
- Navigate to the [GCP Console](https://console.cloud.google.com/).
|
||||
|
||||
- From the projects list, select a project or create a new one.
|
||||
|
||||
2. **Setting up OAuth 2.0**:
|
||||
|
||||
- If the **APIs & services** page isn't already open, open the console left side menu and select **APIs & services**.
|
||||
|
||||
- On the left, click **Credentials**.
|
||||
|
||||
- Click **Create Credentials**, then select **OAuth client ID**.
|
||||
|
||||
3. **Configure OAuth Consent Screen**:
|
||||
|
||||
- If this is your first time creating a client ID, configure your consent screen by clicking **Consent Screen**.
|
||||
|
||||
- Fill in the necessary details and under **Authorized domains**, add the domain where your Formbricks instance is hosted.
|
||||
|
||||
4. **Create OAuth 2.0 Client IDs**:
|
||||
|
||||
- Select the application type **Web application** for your project and enter any additional information required.
|
||||
|
||||
- Ensure to specify authorized JavaScript origins and authorized redirect URIs.
|
||||
|
||||
```{{ Redirect & Origin URLs
|
||||
Authorized JavaScript origins: {WEBAPP_URL}
|
||||
Authorized redirect URIs: {WEBAPP_URL}/api/auth/callback/google
|
||||
```
|
||||
|
||||
- **Update Environment Variables in Docker**:
|
||||
|
||||
- To integrate the Google OAuth, you have two options: either update the environment variables in the docker-compose file or directly add them to the running container.
|
||||
|
||||
- In your Docker setup directory, open the `.env` file, and add or update the following lines with the `Client ID` and `Client Secret` obtained from Google Cloud Platform:
|
||||
|
||||
- Alternatively, you can add the environment variables directly to the running container using the following commands (replace `container_id` with your actual Docker container ID):
|
||||
|
||||
```sh Shell commands
|
||||
docker exec -it container_id /bin/bash
|
||||
export GOOGLE_CLIENT_ID=your-client-id-here
|
||||
export GOOGLE_CLIENT_SECRET=your-client-secret-here
|
||||
exit
|
||||
```
|
||||
|
||||
```sh env file
|
||||
GOOGLE_CLIENT_ID=your-client-id-here
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret-here
|
||||
```
|
||||
|
||||
1. **Restart Your Formbricks Instance**:
|
||||
|
||||
- **Note:** Restarting your Docker containers may cause a brief period of downtime. Plan accordingly.
|
||||
|
||||
- Once the environment variables have been updated, it's crucial to restart your Docker containers to apply the changes. This ensures that your Formbricks instance can utilize the new Google OAuth configuration for user authentication. Here's how you can do it:
|
||||
|
||||
- Navigate to your Docker setup directory where your `docker-compose.yml` file is located.
|
||||
|
||||
- Run the following command to bring down your current Docker containers and then bring them back up with the updated environment configuration:
|
||||
|
||||
### Microsoft Entra ID (Azure Active Directory) SSO OAuth
|
||||
|
||||
Do you have a Microsoft Entra ID Tenant? Integrate it with your Formbricks instance to allow users to log in using their existing Microsoft credentials. This guide will walk you through the process of setting up an Application Registration for your Formbricks instance.
|
||||
|
||||
#### Requirements
|
||||
|
||||
- A Microsoft Entra ID Tenant populated with users. [Create a tenant as per Microsoft's documentation](https://learn.microsoft.com/en-us/entra/fundamentals/create-new-tenant).
|
||||
|
||||
- A Formbricks instance running and accessible.
|
||||
|
||||
- The callback URI for your Formbricks instance: `{WEBAPP_URL}/api/auth/callback/azure-ad`
|
||||
|
||||
#### Creating an App Registration
|
||||
|
||||
- Login to the [Microsoft Entra admin center](https://entra.microsoft.com/).
|
||||
|
||||
- Go to **Applications** > **App registrations** in the left menu.
|
||||
|
||||

|
||||
|
||||
- Click the **New registration** button at the top.
|
||||
|
||||

|
||||
|
||||
- Name your application something descriptive, such as `Formbricks SSO`.
|
||||
|
||||

|
||||
|
||||
- If you have multiple tenants/organizations, choose the appropriate **Supported account types** option. Otherwise, leave the default option for _Single Tenant_.
|
||||
|
||||

|
||||
|
||||
- Under **Redirect URI**, select **Web** for the platform and paste your Formbricks callback URI (see Requirements above).
|
||||
|
||||

|
||||
|
||||
- Click **Register** to create the App registration. You will be redirected to your new app's _Overview_ page after it is created.
|
||||
|
||||
- On the _Overview_ page, under **Essentials**:
|
||||
|
||||
- Copy the entry for **Application (client) ID** to populate the `AZUREAD_CLIENT_ID` variable.
|
||||
|
||||
- Copy the entry for **Directory (tenant) ID** to populate the `AZUREAD_TENANT_ID` variable.
|
||||
|
||||

|
||||
|
||||
- From your App registration's _Overview_ page, go to **Manage** > **Certificates & secrets**.
|
||||
|
||||

|
||||
|
||||
- Make sure you have the **Client secrets** tab active, and click **New client secret**.
|
||||
|
||||

|
||||
|
||||
- Enter a **Description**, set an **Expires** period, then click **Add**.
|
||||
|
||||
<Note>
|
||||
You will need to create a new client secret using these steps whenever your chosen expiry period ends.
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
- Copy the entry under **Value** to populate the `AZUREAD_CLIENT_SECRET` variable.
|
||||
|
||||
<Note>
|
||||
Microsoft will only show this value to you immediately after creation, and you will not be able to access it again. If you lose it, simply start from step 9 to create a new secret.
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
- Update these environment variables in your `docker-compose.yml` or pass it like your other environment variables to the Formbricks container.
|
||||
|
||||
<Note>
|
||||
You must wrap the `AZUREAD_CLIENT_SECRET` value in double quotes (e.g., "THis~iS4faKe.53CreTvALu3"`) to prevent issues with special characters.
|
||||
</Note>
|
||||
|
||||
An example `.env` for Microsoft Entra ID in Formbricks would look like:
|
||||
|
||||
```yml Formbricks Env for Microsoft Entra ID SSO
|
||||
AZUREAD_CLIENT_ID=a25cadbd-f049-4690-ada3-56a163a72f4c
|
||||
AZUREAD_TENANT_ID=2746c29a-a3a6-4ea1-8762-37816d4b7885
|
||||
AZUREAD_CLIENT_SECRET="THis~iS4faKe.53CreTvALu3"
|
||||
```
|
||||
|
||||
- Restart your Formbricks instance.
|
||||
|
||||
- You're all set! Users can now sign up & log in using their Microsoft credentials associated with your Entra ID Tenant.
|
||||
|
||||
## OpenID Configuration
|
||||
|
||||
Integrating your own OIDC (OpenID Connect) instance with your Formbricks instance allows users to log in using their OIDC credentials, ensuring a secure and streamlined user experience. Please follow the steps below to set up OIDC for your Formbricks instance.
|
||||
|
||||
- Configure your OIDC provider & get the following variables:
|
||||
|
||||
- `OIDC_CLIENT_ID`
|
||||
|
||||
- `OIDC_CLIENT_SECRET`
|
||||
|
||||
- `OIDC_ISSUER`
|
||||
|
||||
- `OIDC_SIGNING_ALGORITHM`
|
||||
|
||||
<Note>
|
||||
Make sure the Redirect URI for your OIDC Client is set to `{WEBAPP_URL}/api/auth/callback/openid`.
|
||||
</Note>
|
||||
|
||||
- Update these environment variables in your `docker-compose.yml` or pass it directly to the running container.
|
||||
|
||||
An example configuration for a FusionAuth OpenID Connect in Formbricks would look like:
|
||||
|
||||
|
||||
```yml Formbricks Env for FusionAuth OIDC
|
||||
OIDC_CLIENT_ID=59cada54-56d4-4aa8-a5e7-5823bbe0e5b7
|
||||
OIDC_CLIENT_SECRET=4f4dwP0ZoOAqMW8fM9290A7uIS3E8Xg29xe1umhlB_s
|
||||
OIDC_ISSUER=http://localhost:9011
|
||||
OIDC_DISPLAY_NAME=FusionAuth
|
||||
OIDC_SIGNING_ALGORITHM=HS256
|
||||
```
|
||||
|
||||
|
||||
- Set an environment variable `OIDC_DISPLAY_NAME` to the display name of your OIDC provider.
|
||||
|
||||
- Restart your Formbricks instance.
|
||||
|
||||
- You're all set! Users can now sign up & log in using their OIDC credentials.
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
title: "Open ID Connect"
|
||||
description: "Configure Open ID Connect for secure Single Sign-On with your Formbricks instance. Implement enterprise-grade authentication for your survey platform with Open ID Connect."
|
||||
icon: "key"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Single Sign-On (SSO) functionality, including OAuth integrations with Google, Microsoft Azure AD, and OpenID Connect, requires is part of the [Enterprise Edition](/self-hosting/advanced/license).
|
||||
</Note>
|
||||
|
||||
Integrating your own OIDC (OpenID Connect) instance with your Formbricks instance allows users to log in using their OIDC credentials, ensuring a secure and streamlined user experience. Please follow the steps below to set up OIDC for your Formbricks instance.
|
||||
|
||||
- Configure your OIDC provider & get the following variables:
|
||||
|
||||
- `OIDC_CLIENT_ID`
|
||||
|
||||
- `OIDC_CLIENT_SECRET`
|
||||
|
||||
- `OIDC_ISSUER`
|
||||
|
||||
- `OIDC_SIGNING_ALGORITHM`
|
||||
|
||||
<Note>
|
||||
Make sure the Redirect URI for your OIDC Client is set to `{WEBAPP_URL}/api/auth/callback/openid`.
|
||||
</Note>
|
||||
|
||||
- Update these environment variables in your `docker-compose.yml` or pass it directly to the running container.
|
||||
|
||||
An example configuration for a FusionAuth OpenID Connect in Formbricks would look like:
|
||||
|
||||
|
||||
```yml Formbricks Env for FusionAuth OIDC
|
||||
OIDC_CLIENT_ID=59cada54-56d4-4aa8-a5e7-5823bbe0e5b7
|
||||
OIDC_CLIENT_SECRET=4f4dwP0ZoOAqMW8fM9290A7uIS3E8Xg29xe1umhlB_s
|
||||
OIDC_ISSUER=http://localhost:9011
|
||||
OIDC_DISPLAY_NAME=FusionAuth
|
||||
OIDC_SIGNING_ALGORITHM=HS256
|
||||
```
|
||||
|
||||
|
||||
- Set an environment variable `OIDC_DISPLAY_NAME` to the display name of your OIDC provider.
|
||||
|
||||
- Restart your Formbricks instance.
|
||||
|
||||
- You're all set! Users can now sign up & log in using their OIDC credentials.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "SAML SSO - Self-hosted"
|
||||
title: "SAML SSO"
|
||||
icon: "user-shield"
|
||||
description: "Configure SAML Single Sign-On (SSO) for secure enterprise authentication with your Formbricks instance."
|
||||
description: "How to set up SAML SSO for Formbricks"
|
||||
---
|
||||
|
||||
<Note>You require an Enterprise License along with a SAML SSO add-on to avail this feature.</Note>
|
||||
@@ -12,7 +12,7 @@ Formbricks supports SAML Single Sign-On (SSO) to enable secure, centralized auth
|
||||
|
||||
To learn more about SAML Jackson, please refer to the [BoxyHQ SAML Jackson documentation](https://boxyhq.com/docs/jackson/deploy).
|
||||
|
||||
## How SAML works in Formbricks
|
||||
## How SAML Works in Formbricks
|
||||
|
||||
SAML (Security Assertion Markup Language) is an XML-based standard for exchanging authentication and authorization data between an Identity Provider (IdP) and Formbricks. Here's how the integration works with BoxyHQ Jackson embedded into the flow:
|
||||
|
||||
@@ -37,7 +37,7 @@ SAML (Security Assertion Markup Language) is an XML-based standard for exchangin
|
||||
7. **Access Granted:**
|
||||
Formbricks logs the user in using the verified information.
|
||||
|
||||
## SAML Auth Flow Sequence Diagram
|
||||
## SAML Authentication Flow Sequence Diagram
|
||||
|
||||
Below is a sequence diagram illustrating the complete SAML authentication flow with BoxyHQ Jackson integrated:
|
||||
|
||||
@@ -67,31 +67,12 @@ sequenceDiagram
|
||||
|
||||
To configure SAML SSO in Formbricks, follow these steps:
|
||||
|
||||
<Steps>
|
||||
<Step title="Database Setup">
|
||||
Configure a dedicated database for SAML by setting the `SAML_DATABASE_URL` environment variable in your `docker-compose.yml` file (e.g., `postgres://postgres:postgres@postgres:5432/formbricks-saml`). If you're using a self-signed certificate for Postgres, include the `sslmode=disable` parameter.
|
||||
</Step>
|
||||
|
||||
<Step title="IdP Application">
|
||||
Create a SAML application in your IdP by following your provider's instructions([SAML Setup](/development/guides/auth-and-provision/setup-saml-with-identity-providers))
|
||||
</Step>
|
||||
|
||||
<Step title="User Provisioning">
|
||||
Provision users in your IdP and configure access to the IdP SAML app for all your users (who need access to Formbricks).
|
||||
</Step>
|
||||
|
||||
<Step title="Metadata">
|
||||
Keep the XML metadata from your IdP handy for the next step.
|
||||
</Step>
|
||||
|
||||
<Step title="Metadata Setup">
|
||||
Create a file called `connection.xml` in your self-hosted Formbricks instance's `formbricks/saml-connection` directory and paste the XML metadata from your IdP into it. Please create the directory if it doesn't exist. Your metadata file should start with a tag like this: `<?xml version="1.0" encoding="UTF-8"?><...>` or `<md:EntityDescriptor entityID="...">`. Please remove any extra text from the metadata.
|
||||
</Step>
|
||||
|
||||
<Step title="Restart Formbricks">
|
||||
Restart Formbricks to apply the changes. You can do this by running `docker compose down` and then `docker compose up -d`.
|
||||
</Step>
|
||||
</Steps>
|
||||
1. **Database Setup:** Configure a dedicated database for SAML by setting the `SAML_DATABASE_URL` environment variable in your `docker-compose.yml` file (e.g., `postgres://postgres:postgres@postgres:5432/formbricks-saml`). If you're using a self-signed certificate for Postgres, include the `sslmode=disable` parameter.
|
||||
2. **IdP Application:** Create a SAML application in your IdP by following your provider's instructions([SAML Setup](/development/guides/auth-and-provision/setup-saml-with-identity-providers))
|
||||
3. **User Provisioning:** Provision users in your IdP and configure access to the IdP SAML app for all your users (who need access to Formbricks).
|
||||
4. **Metadata:** Keep the XML metadata from your IdP handy for the next step.
|
||||
5. **Metadata Setup:** Create a file called `connection.xml` in your self-hosted Formbricks instance's `formbricks/saml-connection` directory and paste the XML metadata from your IdP into it. Please create the directory if it doesn't exist. Your metadata file should start with a tag like this: `<?xml version="1.0" encoding="UTF-8"?><...>` or `<md:EntityDescriptor entityID="...">`. Please remove any extra text from the metadata.
|
||||
6. **Restart Formbricks:** Restart Formbricks to apply the changes. You can do this by running `docker compose down` and then `docker compose up -d`.
|
||||
|
||||
<Note>
|
||||
We don't support multiple SAML connections yet. You can only have one SAML connection at a time. If you
|
||||
|
||||
@@ -59,10 +59,7 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| OIDC_ISSUER | Issuer URL for Custom OpenID Connect Provider (should have .well-known configured at this) | optional (required if OIDC auth is enabled) | |
|
||||
| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | RS256 |
|
||||
| OPENTELEMETRY_LISTENER_URL | URL for OpenTelemetry listener inside Formbricks. | optional | |
|
||||
| UNKEY_ROOT_KEY | Key for the [Unkey](https://www.unkey.com/) service. This is used for Rate Limiting for management API. | optional | |
|
||||
| UNKEY_ROOT_KEY | Key for the [Unkey](https://www.unkey.com/) service. This is used for Rate Limiting for management API. | optional | |
|
||||
| CUSTOM_CACHE_DISABLED | Disables custom cache handler if set to 1 (required for deployment on Vercel) | optional | |
|
||||
| PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | |
|
||||
| PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 |
|
||||
| DOCKER_CRON_ENABLED | Controls whether cron jobs run in the Docker image. Set to 0 to disable (useful for cluster setups). | optional | 1 |
|
||||
|
||||
Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Github Discussions and we’ll try our best to work out a solution with you.
|
||||
|
||||
@@ -160,19 +160,6 @@ When using S3 in a cluster setup, ensure that:
|
||||
- The bucket has appropriate CORS settings configured
|
||||
- IAM roles/users have sufficient permissions for read/write operations
|
||||
|
||||
## Disabling Docker Cron Jobs
|
||||
|
||||
When running Formbricks in a cluster setup, you should disable the built-in cron jobs in the Docker image to prevent them from running on multiple instances simultaneously. Instead, you should set up cron jobs in your orchestration system (like Kubernetes) to run on a single instance or as separate jobs.
|
||||
|
||||
To disable the Docker cron jobs, set the following environment variable:
|
||||
|
||||
```sh env
|
||||
# Disable Docker cron jobs (0 = disabled, 1 = enabled)
|
||||
DOCKER_CRON_ENABLED=0
|
||||
```
|
||||
|
||||
This will prevent the cron jobs from starting in the Docker container while still allowing all other Formbricks functionality to work normally.
|
||||
|
||||
## Kubernetes Setup
|
||||
|
||||
Formbricks provides an official Helm chart for deploying the entire cluster stack on Kubernetes. The Helm chart is available in the [Formbricks GitHub repository](https://github.com/formbricks/formbricks/tree/main/helm-chart).
|
||||
@@ -180,7 +167,6 @@ Formbricks provides an official Helm chart for deploying the entire cluster stac
|
||||
### Features of the Helm Chart
|
||||
|
||||
The Helm chart provides a complete deployment solution that includes:
|
||||
|
||||
- Formbricks application with configurable replicas
|
||||
- PostgreSQL database (with optional HA configuration)
|
||||
- Redis cluster for caching
|
||||
@@ -190,14 +176,12 @@ The Helm chart provides a complete deployment solution that includes:
|
||||
### Installation Steps
|
||||
|
||||
1. Add the Formbricks Helm repository:
|
||||
|
||||
```sh
|
||||
helm repo add formbricks https://raw.githubusercontent.com/formbricks/formbricks/main/helm-chart
|
||||
helm repo update
|
||||
```
|
||||
|
||||
2. Install the chart:
|
||||
|
||||
```sh
|
||||
helm install formbricks formbricks/formbricks
|
||||
```
|
||||
@@ -205,7 +189,6 @@ helm install formbricks formbricks/formbricks
|
||||
### Configuration Options
|
||||
|
||||
The Helm chart can be customized using a `values.yaml` file to configure:
|
||||
|
||||
- Number of Formbricks replicas
|
||||
- Resource limits and requests
|
||||
- Database configuration
|
||||
|
||||
@@ -1,217 +1,154 @@
|
||||
---
|
||||
title: "Kubernetes Deployment"
|
||||
description: "Deploy the new Helm chart on a Kubernetes cluster using Helm."
|
||||
description: "Deploy Formbricks on a Kubernetes cluster using Helm."
|
||||
icon: "circle-nodes"
|
||||
---
|
||||
|
||||
# **🚀 Kubernetes Deployment Guide**
|
||||
This guide explains how to deploy Formbricks on a Kubernetes cluster using Helm. The primary focus is on deploying Formbricks pods in a production-ready environment with external database services.
|
||||
|
||||
This guide explains how to deploy the **Formbricks Helm Chart** on a Kubernetes cluster using Helm. It provides configuration options for **internal** and **external** databases, caching services, and secrets management.
|
||||
## Prerequisites
|
||||
|
||||
---
|
||||
Before you begin, ensure that:
|
||||
|
||||
## **📌 Prerequisites**
|
||||
Ensure you have the following before proceeding:
|
||||
- You have a running Kubernetes cluster (AWS EKS, GCP GKE, Azure AKS, Minikube, etc.)
|
||||
- An Ingress controller (e.g., Traefik, Nginx) is configured
|
||||
- You have Helm installed on your local machine
|
||||
- For production environments, you have access to external PostgreSQL and Redis services
|
||||
|
||||
- A running Kubernetes cluster (EKS, GKE, AKS, Minikube, etc.)
|
||||
- An **Ingress Controller** (e.g., Traefik, Nginx)
|
||||
- **Helm installed** on your local machine
|
||||
- **Production setup requires managed PostgreSQL and Redis services**
|
||||
> **Important:** Running multiple Formbricks pods in a cluster setup requires a Formbricks Enterprise license. With the Community Edition, only a single Formbricks pod is supported. Redis is required when deploying multiple Formbricks pods for proper session handling and caching.
|
||||
|
||||
> **Note:** Redis is required for **session handling** when deploying multiple pods.
|
||||
## Basic Installation
|
||||
|
||||
---
|
||||
### Step 1: Clone the Formbricks Helm Chart
|
||||
|
||||
## **1️⃣ Installation Steps**
|
||||
|
||||
### **🔹 Step 1: Clone the Helm Chart**
|
||||
```sh
|
||||
git clone https://github.com/formbricks/formbricks
|
||||
cd helm-chart
|
||||
git clone https://github.com/formbricks/formbricks.git
|
||||
cd formbricks/helm-chart
|
||||
```
|
||||
|
||||
### **🔹 Step 2: Install with Default Configuration**
|
||||
### Step 2: Deploy Formbricks
|
||||
|
||||
For a basic deployment with a single pod (Community Edition) and PostgreSQL running in the cluster:
|
||||
|
||||
```sh
|
||||
helm install formbricks ./ -n formbricks --create-namespace
|
||||
helm install my-formbricks ./ \
|
||||
--namespace formbricks \
|
||||
--set redis.enabled=false \
|
||||
--create-namespace
|
||||
```
|
||||
By default:
|
||||
- PostgreSQL and Redis **are deployed within the cluster**.
|
||||
- Secrets **are dynamically generated** and stored as Kubernetes Secrets.
|
||||
|
||||
### **🔹 Step 3: Install with an Enterprise License**
|
||||
## Production Deployment
|
||||
|
||||
For production environments, we recommend using managed database and cache services like AWS RDS for PostgreSQL and AWS ElastiCache for Redis:
|
||||
|
||||
```sh
|
||||
helm install formbricks ./ -n formbricks --create-namespace --set enterprise.licenseKey="YOUR_LICENSE_KEY"
|
||||
helm install my-formbricks ./ \
|
||||
--namespace formbricks \
|
||||
--create-namespace \
|
||||
--set replicaCount=3 \
|
||||
--set postgresql.enabled=false \
|
||||
--set postgresql.externalUrl="postgresql://user:password@your-postgres-host:5432/formbricks" \
|
||||
--set redis.enabled=false \
|
||||
--set redis.externalUrl="redis://your-redis-host:6379"
|
||||
```
|
||||
|
||||
---
|
||||
> **Note:** The above multi-pod configuration requires a Formbricks Enterprise license. Redis is enabled and configured to support multiple Formbricks pods.
|
||||
|
||||
## **2️⃣ Configuring Secrets**
|
||||
## Verify Installation
|
||||
|
||||
### **🔹 Using Kubernetes Secrets (Default)**
|
||||
By default, **secrets are stored as Kubernetes Secrets**.
|
||||
The chart automatically generates **random values** for required secrets.
|
||||
### Check Running Services
|
||||
|
||||
Modify `values.yaml`:
|
||||
```yaml
|
||||
secret:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **🔹 Using External Secrets (AWS Secrets Manager, Vault, etc.)**
|
||||
To use an **external secrets manager**, enable `externalSecret` in `values.yaml`:
|
||||
```yaml
|
||||
secret:
|
||||
enabled: false # Disable default secret generation
|
||||
|
||||
externalSecret:
|
||||
enabled: true
|
||||
secretStore:
|
||||
name: aws-secrets-manager
|
||||
kind: ClusterSecretStore
|
||||
refreshInterval: "1h"
|
||||
files:
|
||||
redis:
|
||||
data:
|
||||
REDIS_PASSWORD:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: REDIS_PASSWORD
|
||||
postgres:
|
||||
data:
|
||||
POSTGRES_ADMIN_PASSWORD:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: POSTGRES_ADMIN_PASSWORD
|
||||
POSTGRES_USER_PASSWORD:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: POSTGRES_USER_PASSWORD
|
||||
app-secrets:
|
||||
data:
|
||||
DATABASE_URL:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: DATABASE_URL
|
||||
REDIS_URL:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: REDIS_URL
|
||||
ENCRYPTION_KEY:
|
||||
remoteRef:
|
||||
key: "prod/formbricks/secrets"
|
||||
property: ENCRYPTION_KEY
|
||||
```
|
||||
📌 **Ensure ExternalSecrets Operator is installed:**
|
||||
[https://external-secrets.io/latest/](https://external-secrets.io/latest/)
|
||||
|
||||
Install with:
|
||||
```sh
|
||||
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
|
||||
kubectl get pods -n formbricks
|
||||
kubectl get svc -n formbricks
|
||||
kubectl get ingress -n formbricks
|
||||
```
|
||||
|
||||
---
|
||||
> **Note:** The Formbricks application pod may take some time to reach a stable state as it runs database migrations during startup.
|
||||
|
||||
## **3️⃣ Configuring PostgreSQL and Redis**
|
||||
### Access Formbricks
|
||||
|
||||
### **🔹 Using Managed PostgreSQL and Redis**
|
||||
For production, we recommend using **managed database and cache services**.
|
||||
- If running locally with Minikube:
|
||||
```sh
|
||||
minikube service my-formbricks -n formbricks
|
||||
```
|
||||
- If deployed on a cloud cluster, make sure to set up your ingress controller properly and visit the domain or IP address associated with your ingress.
|
||||
|
||||
Modify `values.yaml`:
|
||||
```yaml
|
||||
postgresql:
|
||||
enabled: false
|
||||
externalDatabaseUrl: "postgresql://user:password@your-postgres-host:5432/mydb"
|
||||
## Upgrading Formbricks
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
externalRedisUrl: "redis://your-redis-host:6379"
|
||||
```
|
||||
Install with:
|
||||
```sh
|
||||
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
|
||||
To upgrade your Formbricks deployment, use:
|
||||
|
||||
```bash
|
||||
# From the helm-chart directory
|
||||
helm upgrade my-formbricks ./ --namespace formbricks
|
||||
```
|
||||
|
||||
---
|
||||
### Common Upgrade Scenarios
|
||||
|
||||
### **🔹 Using In-Cluster PostgreSQL and Redis (Default)**
|
||||
By default, PostgreSQL and Redis are **deployed inside the cluster**.
|
||||
To **ensure in-cluster deployment**, use:
|
||||
#### 1. Updating Environment Variables
|
||||
|
||||
```yaml
|
||||
postgresql:
|
||||
enabled: true
|
||||
|
||||
redis:
|
||||
enabled: true
|
||||
```
|
||||
Apply with:
|
||||
```sh
|
||||
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
|
||||
```bash
|
||||
helm upgrade my-formbricks ./ --namespace formbricks \
|
||||
--set env.SMTP_HOST=new-smtp.example.com \
|
||||
--set env.SMTP_PORT=587 \
|
||||
--set env.NEW_CUSTOM_VAR=newvalue
|
||||
```
|
||||
|
||||
---
|
||||
#### 2. Scaling Resources
|
||||
|
||||
## **4️⃣ Upgrading the Deployment**
|
||||
To apply changes:
|
||||
```sh
|
||||
helm upgrade formbricks ./ -n formbricks
|
||||
```bash
|
||||
helm upgrade my-formbricks ./ --namespace formbricks \
|
||||
--set resources.limits.cpu=1 \
|
||||
--set resources.limits.memory=2Gi \
|
||||
--set resources.requests.cpu=500m \
|
||||
--set resources.requests.memory=1Gi
|
||||
```
|
||||
|
||||
### **🔹 Scaling Resources**
|
||||
```sh
|
||||
helm upgrade formbricks ./ -n formbricks --set deployment.resources.limits.cpu=2 --set deployment.resources.limits.memory=4Gi
|
||||
#### 3. Updating Autoscaling Configuration
|
||||
|
||||
```bash
|
||||
helm upgrade my-formbricks ./ --namespace formbricks \
|
||||
--set autoscaling.enabled=true \
|
||||
--set autoscaling.minReplicas=3 \
|
||||
--set autoscaling.maxReplicas=10 \
|
||||
--set autoscaling.metrics[0].resource.target.averageUtilization=75
|
||||
```
|
||||
|
||||
### **🔹 Enabling Autoscaling**
|
||||
```sh
|
||||
helm upgrade formbricks ./ -n formbricks --set autoscaling.enabled=true --set autoscaling.minReplicas=3 --set autoscaling.maxReplicas=10
|
||||
> **Note:** Enabling autoscaling requires a Formbricks Enterprise license and proper Redis configuration.
|
||||
|
||||
#### 4. Changing Database Connection
|
||||
|
||||
```bash
|
||||
helm upgrade my-formbricks ./ --namespace formbricks \
|
||||
--set postgresql.enabled=false \
|
||||
--set postgresql.externalUrl="postgresql://newuser:newpassword@external-postgres-host:5432/newdatabase"
|
||||
```
|
||||
|
||||
---
|
||||
## Advanced Configuration Options
|
||||
|
||||
## **5️⃣ Key Configuration Values**
|
||||
For advanced configurations including:
|
||||
|
||||
| Field | Description | Default Value |
|
||||
|--------------------------------|--------------------------------------|--------------|
|
||||
| `deployment.replicas` | Number of application replicas | `1` |
|
||||
| `deployment.image.repository` | Docker image repository | `"ghcr.io/formbricks/formbricks"` |
|
||||
| `deployment.image.tag` | Docker image tag | `"latest"` |
|
||||
| `autoscaling.enabled` | Enable autoscaling | `false` |
|
||||
| `postgresql.enabled` | Deploy PostgreSQL in cluster | `true` |
|
||||
| `postgresql.externalDatabaseUrl` | External PostgreSQL URL | `""` |
|
||||
| `redis.enabled` | Deploy Redis in cluster | `true` |
|
||||
| `redis.externalRedisUrl` | External Redis URL | `""` |
|
||||
| `externalSecret.enabled` | Enable external secrets manager | `false` |
|
||||
- Deploying PostgreSQL and Redis within your Kubernetes cluster
|
||||
- Configuring Traefik ingress controller
|
||||
- Setting up high availability
|
||||
- Customizing autoscaling behavior
|
||||
|
||||
📌 **Refer to the Helm chart repository for full configuration options.**
|
||||
Please refer to the complete Helm chart documentation at:
|
||||
[https://github.com/formbricks/formbricks/tree/main/helm-chart](https://github.com/formbricks/formbricks/tree/main/helm-chart)
|
||||
|
||||
---
|
||||
## Key Configuration Values
|
||||
|
||||
## **6️⃣ Uninstalling the Deployment**
|
||||
To remove the deployment:
|
||||
```sh
|
||||
helm uninstall formbricks -n formbricks
|
||||
```
|
||||
| Field | Description | Default |
|
||||
| ------------------------- | ----------------------------- | ------------------------------- |
|
||||
| `replicaCount` | Number of Formbricks replicas | `1` |
|
||||
| `image.repository` | Docker image repository | `ghcr.io/formbricks/formbricks` |
|
||||
| `image.tag` | Docker image tag | `"2.6.0"` |
|
||||
| `resources.limits.cpu` | CPU resource limit | `500m` |
|
||||
| `resources.limits.memory` | Memory resource limit | `1Gi` |
|
||||
| `autoscaling.enabled` | Enable autoscaling | `false` |
|
||||
| `postgresql.enabled` | Deploy PostgreSQL in cluster | `true` |
|
||||
| `postgresql.externalUrl` | External PostgreSQL URL | `""` |
|
||||
| `redis.enabled` | Deploy Redis in cluster | `false` |
|
||||
| `redis.externalUrl` | External Redis URL | `""` |
|
||||
|
||||
### **Removing Persistent Volumes (PVCs)**
|
||||
By default, **PVCs are not deleted** with Helm.
|
||||
To manually remove them:
|
||||
```sh
|
||||
kubectl delete pvc --all -n formbricks
|
||||
```
|
||||
|
||||
To completely delete the namespace:
|
||||
```sh
|
||||
kubectl delete namespace formbricks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **📢 Additional Notes**
|
||||
- **Ingress Setup:** If using an ingress controller, make sure to configure `ingress.enabled: true` in `values.yaml`.
|
||||
- **Environment Variables:** Pass custom environment variables via `envFrom` in `values.yaml`.
|
||||
- **Backup Strategy:** Ensure you have a backup policy for PostgreSQL if running in-cluster.
|
||||
|
||||
🚀 **Your Formbricks deployment is now ready!** 🚀
|
||||
For the complete list of configuration options, please refer to the Formbricks Helm chart repository.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Third-party Integrations"
|
||||
title: "Overview"
|
||||
description: "Configure third-party integrations with Formbricks Cloud."
|
||||
---
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user