Compare commits

..

1 Commits

Author SHA1 Message Date
ShubhamPalriwala
b301482868 fix: add nextauth url to kamal 2024-03-18 14:22:30 +05:30
2761 changed files with 96500 additions and 129553 deletions

View File

@@ -12,8 +12,8 @@
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["dbaeumer.vscode-eslint"]
}
"extensions": ["dbaeumer.vscode-eslint"],
},
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
@@ -22,8 +22,8 @@
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev",
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
"postAttachCommand": "pnpm dev --filter=web... --filter=demo...",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node"
"remoteUser": "node",
}

View File

@@ -35,5 +35,3 @@ yarn-error.log*
.vscode
.github
**/.turbo
.env

View File

@@ -8,8 +8,8 @@
WEBAPP_URL=http://localhost:3000
# Required for next-auth. Should be the same as WEBAPP_URL
NEXTAUTH_URL=http://localhost:3000
# Set this if you want to have a shorter link for surveys
SHORT_URL_BASE=
# Encryption keys
# Please set both for now, we will change this in the future
@@ -17,20 +17,24 @@ NEXTAUTH_URL=http://localhost:3000
# You can use: `openssl rand -hex 32` to generate one
ENCRYPTION_KEY=
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
# You can use: `openssl rand -hex 32` to generate a secure one
NEXTAUTH_SECRET=
# API Secret for running cron jobs. (mandatory)
# You can use: `openssl rand -hex 32` to generate a secure one
CRON_SECRET=
##############
# DATABASE #
##############
DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=public'
###############
# NEXT AUTH #
###############
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
# You can use: `openssl rand -hex 32` to generate one
NEXTAUTH_SECRET=RANDOM_STRING
# Set this to your public-facing URL, e.g., https://example.com
# You do not need the NEXTAUTH_URL environment variable in Vercel.
NEXTAUTH_URL=http://localhost:3000
################
# MAIL SETUP #
################
@@ -46,9 +50,6 @@ SMTP_SECURE_ENABLED=0
SMTP_USER=smtpUser
SMTP_PASSWORD=smtpPassword
# If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs (default is 1).
# SMTP_REJECT_UNAUTHORIZED_TLS=0
########################################################################
# ------------------------------ OPTIONAL -----------------------------#
########################################################################
@@ -70,8 +71,6 @@ S3_BUCKET_NAME=
# Configure a third party S3 compatible storage service endpoint like StorJ leave empty if you use Amazon S3
# e.g., https://gateway.storjshare.io
S3_ENDPOINT_URL=
# Force path style for S3 compatible storage (0 for disabled, 1 for enabled)
S3_FORCE_PATH_STYLE=0
#####################
# Disable Features #
@@ -84,13 +83,12 @@ EMAIL_VERIFICATION_DISABLED=1
PASSWORD_RESET_DISABLED=1
# Signup. Disable the ability for new users to create an account.
# Note: This variable is only available to the SaaS setup of Formbricks Cloud. Signup is disable by default for self-hosting.
# SIGNUP_DISABLED=1
# Email login. Disable the ability for users to login with email.
# EMAIL_AUTH_DISABLED=1
# Organization Invite. Disable the ability for invited users to create an account.
# Team Invite. Disable the ability for invited users to create an account.
# INVITE_DISABLED=1
##########
@@ -122,6 +120,9 @@ AZUREAD_TENANT_ID=
# OIDC_DISPLAY_NAME=
# OIDC_SIGNING_ALGORITHM=
# Cron Secret
CRON_SECRET=
# Configure this when you want to ship JS & CSS files from a complete URL instead of the current domain
# ASSET_PREFIX_URL=
@@ -146,18 +147,17 @@ GOOGLE_SHEETS_REDIRECT_URL=
# Oauth credentials for Airtable integration
AIRTABLE_CLIENT_ID=
# Oauth credentials for Slack integration
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# Enterprise License Key
ENTERPRISE_LICENSE_KEY=
# Automatically assign new users to a specific organization and role within that organization
# Insert an existing organization id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn)
# Automatically assign new users to a specific team and role within that team
# Insert an existing team id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn)
# (Role Management is an Enterprise feature)
# DEFAULT_ORGANIZATION_ID=
# DEFAULT_ORGANIZATION_ROLE=admin
# DEFAULT_TEAM_ID=
# DEFAULT_TEAM_ROLE=admin
# set to 1 to skip onboarding for new users
# ONBOARDING_DISABLED=1
# Send new users to customer.io
# CUSTOMER_IO_API_KEY=
@@ -168,15 +168,3 @@ ENTERPRISE_LICENSE_KEY=
# OpenTelemetry URL for tracing
# OPENTELEMETRY_LISTENER_URL=http://localhost:4318/v1/traces
# Unsplash API 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
# 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:
# Disable custom cache handler if necessary (e.g. if deployed on Vercel)
# CUSTOM_CACHE_DISABLED=1

View File

@@ -1,2 +0,0 @@
node_modules/
packages/config-eslint/

10
.eslintrc.js Normal file
View File

@@ -0,0 +1,10 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-formbricks`
extends: ["formbricks"],
settings: {
next: {
rootDir: ["apps/*/"],
},
},
};

View File

@@ -1,33 +0,0 @@
name: oss.gg hack submission 🕹️
description: "Submit your contribution for the for the oss.gg hackathon"
title: "[oss.gg hackathon]"
labels: 🕹️ oss.gg, player submission
assignees: []
body:
- type: textarea
id: contribution-name
attributes:
label: What side quest or challenge are you solving?
description: Add the name of the side quest or challenge.
validations:
required: true
- type: textarea
id: points
attributes:
label: Points
description: How many points are assigned to this contribution?
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: What's the task your performed?
validations:
- type: textarea
id: proof
attributes:
label: Provide proof that you've completed the task
description: Screenshots, loom recordings, links to the content you shared or interacted with.
validations:
required: true

View File

@@ -32,7 +32,6 @@ Fixes # (issue)
- [ ] Removed all `console.logs`
- [ ] Merged the latest changes from main onto my branch with `git pull origin main`
- [ ] My changes don't cause any responsiveness issues
- [ ] First PR at Formbricks? [Please sign the CLA!](https://cla-assistant.io/formbricks/formbricks) Without it we wont be able to merge it 🙏
### Appreciated

View File

@@ -1,13 +1,5 @@
name: Build & Cache Web App
on:
workflow_dispatch:
inputs:
e2e_testing_mode:
description: "Set E2E Testing Mode"
required: false
default: "0"
runs:
using: "composite"
steps:
@@ -23,16 +15,13 @@ runs:
cache-name: prod-build
key-1: ${{ hashFiles('pnpm-lock.yaml') }}
key-2: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', '!**/node_modules') }}
key-3: ${{ github.event.pull_request.number || github.ref }}
with:
path: |
${{ github.workspace }}/apps/web/.next
**/.turbo/**
**/dist/**
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}
- name: Set Cache Hit Status
run: echo "cache-hit=${{ steps.cache-build.outputs.cache-hit }}" >> "$GITHUB_OUTPUT"
shell: bash
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
@@ -41,7 +30,7 @@ runs:
if: steps.cache-build.outputs.cache-hit != 'true'
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
if: steps.cache-build.outputs.cache-hit != 'true'
- name: Install dependencies
@@ -53,18 +42,14 @@ runs:
run: cp .env.example .env
shell: bash
- name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env
- name: Generate Random ENCRYPTION_KEY
run: |
RANDOM_KEY=$(openssl rand -hex 32)
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${RANDOM_KEY}/" .env
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> .env
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
shell: bash
- run: |
pnpm build --filter=@formbricks/web...
pnpm build --filter=web...
if: steps.cache-build.outputs.cache-hit != 'true'
shell: bash

View File

@@ -0,0 +1,26 @@
name: Build formbricks-com
on:
workflow_call:
jobs:
build:
name: Build Formbricks-com
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
- name: Build Formbricks-com
run: pnpm build --filter=formbricks-com...

View File

@@ -11,8 +11,24 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- name: Build & Cache Web Binaries
uses: ./.github/actions/cache-build-web
id: cache-build-web
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
with:
e2e_testing_mode: "0"
node-version: 20.x
- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
- name: create .env
run: cp .env.example .env
- name: Generate Random ENCRYPTION_KEY
run: |
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Build Formbricks-web
run: pnpm build --filter=web...

View File

@@ -1,30 +0,0 @@
name: "Chromatic"
on:
push:
branches:
- main
workflow_dispatch:
jobs:
chromatic:
name: Run Chromatic
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
- name: Run Chromatic
uses: chromaui/action@latest
with:
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
workingDir: apps/storybook

23
.github/workflows/cron-closeOnDate.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Cron - closeOnDate
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At 00:00.” (see https://crontab.guru)
- cron: "0 0 * * *"
jobs:
cron-weeklySummary:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_SECRET }}
run: |
curl ${{ env.APP_URL }}/api/cron/close_surveys \
-X POST \
-H 'content-type: application/json' \
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
--fail

View File

@@ -0,0 +1,23 @@
name: Cron - reportUsageToStripe
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# This will run the job at 20:00 UTC every day of every month.
- cron: "0 20 * * *"
jobs:
cron-reportUsageToStripe:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_SECRET }}
run: |
curl ${{ env.APP_URL }}/api/cron/report-usage \
-X POST \
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
-H 'Cache-Control: no-cache' \
--fail

View File

@@ -1,24 +0,0 @@
name: Cron - Survey status update
on:
workflow_dispatch:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
# schedule:
# Runs “At 00:00.” (see https://crontab.guru)
# - cron: "0 0 * * *"
jobs:
cron-weeklySummary:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_SECRET }}
run: |
curl ${{ env.APP_URL }}/api/cron/survey-status \
-X POST \
-H 'content-type: application/json' \
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
--fail

View File

@@ -1,7 +1,6 @@
name: Cron - Weekly summary
name: Cron - weeklySummary
on:
workflow_dispatch:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
@@ -17,7 +16,7 @@ jobs:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_SECRET }}
run: |
curl ${{ env.APP_URL }}/api/cron/weekly-summary \
curl ${{ env.APP_URL }}/api/cron/weekly_summary \
-X POST \
-H 'content-type: application/json' \
-H 'x-api-key: ${{ env.CRON_SECRET }}' \

View File

@@ -2,84 +2,65 @@ name: E2E Tests
on:
workflow_call:
workflow_dispatch:
env:
TELEMETRY_DISABLED: 1
jobs:
build:
name: Run E2E Tests
runs-on: ubuntu-latest
timeout-minutes: 60
services:
postgres:
image: postgres:latest
env:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U testuser"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Build & Cache Web Binaries
uses: ./.github/actions/cache-build-web
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
shell: bash
run: pnpm install
- name: create .env
run: cp .env.example .env
shell: bash
- name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env
- name: Start PostgreSQL
run: |
RANDOM_KEY=$(openssl rand -hex 32)
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${RANDOM_KEY}/" .env
echo "E2E_TESTING=1" >> .env
echo "NEXT_PUBLIC_E2E_TESTING=1" >> .env
shell: bash
- name: Build App
cd packages/database && pnpm db:up &
for attempt in {1..20}; do
if nc -zv localhost 5432; then
echo "Ready"
break
fi
echo "Waiting..."
sleep 5
done
pnpm db:migrate:dev
- name: Serve packages for lazy loading
run: |
pnpm build --filter=@formbricks/web...
- name: Apply Prisma Migrations
run: |
pnpm prisma migrate deploy
cd packages/surveys && pnpm serve &
- name: Run App
run: |
NODE_ENV=test pnpm start --filter=@formbricks/web &
sleep 10 # Optional: gives some buffer for the app to start
for attempt in {1..10}; do
NODE_ENV=test pnpm start --filter=web &
for attempt in {1..20}; do
if [ $(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/health) -eq 200 ]; then
echo "Application is ready."
echo "Ready"
break
fi
if [ $attempt -eq 10 ]; then
echo "Application failed to start in time."
exit 1
fi
echo "Still waiting for the application to be ready..."
echo "Waiting..."
sleep 10
done
- name: Test Serve endpoints
run: |
curl -s http://localhost:3003
- name: Cache Playwright
uses: actions/cache@v3
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Playwright
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps
- name: Run E2E Tests

121
.github/workflows/ecs-deployment.yml vendored Normal file
View File

@@ -0,0 +1,121 @@
name: ECS
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [main]
workflow_dispatch: # Add manual trigger support
env:
REGISTRY: ghcr.io
IMAGE_NAME: formbricks/formbricks-experimental
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Only necessary for sigstore/fulcio outside PRs
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Depot CLI
uses: depot/setup-action@v1
# https://github.com/sigstore/cosign-installer
- name: Install cosign
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
with:
cosign-release: "v2.1.1"
# https://github.com/docker/login-action
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,format=long
# Build and push Docker image with Buildx
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: depot/build-push-action@v1
env:
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
with:
project: tw0fqmsx3c
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: .
file: ./apps/web/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NEXT_PUBLIC_SENTRY_DSN=${{ env.NEXT_PUBLIC_SENTRY_DSN }}
- name: Sign the images with GitHub OIDC Token
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
images=""
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} "
done
cosign sign --yes ${images}
outputs:
image_tag_sha: ${{ steps.meta.outputs.tags }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition prod-webapp-ecs-service --query taskDefinition > task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: prod-webapp-container
image: ${{ needs.build.outputs.image_tag_sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: prod-webapp-ecs-service
cluster: prod-core-infra-ecs-cluster
wait-for-service-stability: true

120
.github/workflows/kamal.yml vendored Normal file
View File

@@ -0,0 +1,120 @@
name: Kamal Deploy
concurrency:
group: deploy-to-kamal
cancel-in-progress: false
on:
push:
branches:
- main
jobs:
Deploy:
runs-on: ubuntu-latest
environment: production
env:
DOCKER_BUILDKIT: 1
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }}
MAIL_FROM: ${{ secrets.MAIL_FROM }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
SMTP_PORT: ${{ secrets.SMTP_PORT }}
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
TERMS_URL: ${{ vars.TERMS_URL }}
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }}
AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }}
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }}
OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }}
OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }}
NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }}
NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }}
GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }}
GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }}
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
DEFAULT_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }}
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
NODE_ENV: production
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
S3_REGION: ${{ vars.S3_REGION }}
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
bundler-cache: true
- name: Install dependencies
run: |
gem install kamal
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Create builder
run: docker buildx create --use --name formbricks-gh-actions-builder
if: steps.buildx.outputs.should_create_builder == 'true'
- name: Push env variables to Kamal
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
kamal env push
- name: Run deploy command
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
set +e
DEPLOY_OUTPUT=$(kamal setup 2>&1)
DEPLOY_EXIT_CODE=$?
echo "$DEPLOY_OUTPUT"
if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then
echo "Deployment reported healthy container. Considering as success."
kamal lock release
exit 0
else
exit $DEPLOY_EXIT_CODE
fi
shell: bash

View File

@@ -11,13 +11,13 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- name: Setup Node.js 20.x
- name: Setup Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 20.x
node-version: 18.x
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
@@ -25,12 +25,10 @@ jobs:
- name: create .env
run: cp .env.example .env
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
- name: Generate Random ENCRYPTION_KEY
run: |
RANDOM_KEY=$(openssl rand -hex 32)
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Lint
run: pnpm lint

View File

@@ -1,10 +1,9 @@
name: Release Changesets
on:
workflow_dispatch:
#push:
# branches:
# - main
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}

View File

@@ -1,5 +1,10 @@
name: Docker for Data Migrations
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
workflow_dispatch:
push:
@@ -7,6 +12,7 @@ on:
- "v*"
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
IMAGE_NAME: formbricks/data-migrations
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public"
@@ -17,6 +23,8 @@ jobs:
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
@@ -25,7 +33,9 @@ jobs:
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@v3.5.0
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
with:
cosign-release: "v2.1.1"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
@@ -42,7 +52,6 @@ jobs:
tags: |
type=ref,event=tag
type=raw,value=${{ github.ref_name }}
type=raw,value=latest
- name: Build and push Docker image
uses: docker/build-push-action@v3
@@ -59,4 +68,3 @@ jobs:
if: ${{ github.event_name != 'pull_request' }}
run: |
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:latest

View File

@@ -1,91 +0,0 @@
name: Docker Release to Github Experimental
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
workflow_dispatch:
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}-experimental
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Depot CLI
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@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@v3 # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: depot/build-push-action@v1
with:
project: tw0fqmsx3c
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: .
file: ./apps/web/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

View File

@@ -1,4 +1,4 @@
name: Docker Release to Github
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
@@ -41,7 +41,9 @@ jobs:
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@v3.5.0
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
with:
cosign-release: "v2.1.1"
# Login against a Docker registry except on PR
# https://github.com/docker/login-action

View File

@@ -17,7 +17,7 @@ jobs:
node-version: 20.x
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
@@ -25,12 +25,10 @@ jobs:
- name: create .env
run: cp .env.example .env
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
- name: Generate Random ENCRYPTION_KEY
run: |
RANDOM_KEY=$(openssl rand -hex 32)
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Test
run: pnpm test

4
.gitignore vendored
View File

@@ -54,7 +54,3 @@ Zone.Identifier
# uploads
packages/lib/uploads
# Vite Timestamps
*vite.config.*.timestamp-*

View File

@@ -33,6 +33,7 @@ tasks:
gp sync-await init &&
cp .env.example .env &&
sed -i -r "s#^(WEBAPP_URL=).*#\1 $(gp url 3000)#" .env &&
sed -i -r "s#^(NEXTAUTH_URL=).*#\1 $(gp url 3000)#" .env &&
RANDOM_ENCRYPTION_KEY=$(openssl rand -hex 32)
sed -i 's/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY='"$RANDOM_ENCRYPTION_KEY"'/' .env
turbo --filter "@formbricks/web" go

View File

@@ -1 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged

14
.kamal/hooks/post-deploy.sample Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# A sample post-deploy hook
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)
# KAMAL_RUNTIME
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooted Traefik on $KAMAL_HOSTS"

51
.kamal/hooks/pre-build.sample Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/sh
# A sample pre-build hook
#
# Checks:
# 1. We have a clean checkout
# 2. A remote is configured
# 3. The branch has been pushed to the remote
# 4. The version we are deploying matches the remote
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)
if [ -n "$(git status --porcelain)" ]; then
echo "Git checkout is not clean, aborting..." >&2
git status --porcelain >&2
exit 1
fi
first_remote=$(git remote)
if [ -z "$first_remote" ]; then
echo "No git remote set, aborting..." >&2
exit 1
fi
current_branch=$(git branch --show-current)
if [ -z "$current_branch" ]; then
echo "Not on a git branch, aborting..." >&2
exit 1
fi
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
if [ -z "$remote_head" ]; then
echo "Branch not pushed to remote, aborting..." >&2
exit 1
fi
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
exit 1
fi
exit 0

47
.kamal/hooks/pre-connect.sample Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env ruby
# A sample pre-connect check
#
# Warms DNS before connecting to hosts in parallel
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)
# KAMAL_RUNTIME
hosts = ENV["KAMAL_HOSTS"].split(",")
results = nil
max = 3
elapsed = Benchmark.realtime do
results = hosts.map do |host|
Thread.new do
tries = 1
begin
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
rescue SocketError
if tries < max
puts "Retrying DNS warmup: #{host}"
tries += 1
sleep rand
retry
else
puts "DNS warmup failed: #{host}"
host
end
end
tries
end
end.map(&:value)
end
retries = results.sum - hosts.size
nopes = results.count { |r| r == max }
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]

109
.kamal/hooks/pre-deploy.sample Executable file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env ruby
# A sample pre-deploy hook
#
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
#
# Fails unless the combined status is "success"
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_COMMAND
# KAMAL_SUBCOMMAND
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)
# Only check the build status for production deployments
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
exit 0
end
require "bundler/inline"
# true = install gems so this is fast on repeat invocations
gemfile(true, quiet: true) do
source "https://rubygems.org"
gem "octokit"
gem "faraday-retry"
end
MAX_ATTEMPTS = 72
ATTEMPTS_GAP = 10
def exit_with_error(message)
$stderr.puts message
exit 1
end
class GithubStatusChecks
attr_reader :remote_url, :git_sha, :github_client, :combined_status
def initialize
@remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
@git_sha = `git rev-parse HEAD`.strip
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
refresh!
end
def refresh!
@combined_status = github_client.combined_status(remote_url, git_sha)
end
def state
combined_status[:state]
end
def first_status_url
first_status = combined_status[:statuses].find { |status| status[:state] == state }
first_status && first_status[:target_url]
end
def complete_count
combined_status[:statuses].count { |status| status[:state] != "pending"}
end
def total_count
combined_status[:statuses].count
end
def current_status
if total_count > 0
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
else
"Build not started..."
end
end
end
$stdout.sync = true
puts "Checking build status..."
attempts = 0
checks = GithubStatusChecks.new
begin
loop do
case checks.state
when "success"
puts "Checks passed, see #{checks.first_status_url}"
exit 0
when "failure"
exit_with_error "Checks failed, see #{checks.first_status_url}"
when "pending"
attempts += 1
end
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
puts checks.current_status
sleep(ATTEMPTS_GAP)
checks.refresh!
end
rescue Octokit::NotFound
exit_with_error "Build status could not be found"
end

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Rebooting Traefik on $KAMAL_HOSTS..."

View File

@@ -1,4 +1,4 @@
const baseConfig = require("./packages/config-prettier/prettier-preset");
const baseConfig = require("./packages/prettier-config/prettier-preset");
module.exports = {
...baseConfig,

View File

@@ -82,7 +82,7 @@ Formbricks is both a free and open source survey platform - and a privacy-first
- 🔗 Create shareable **link surveys**.
- 👨‍👩‍👦 Invite your organization members to **collaborate** on your surveys.
- 👨‍👩‍👦 Invite your team members to **collaborate** on your surveys.
- 🔌 Integrate Formbricks with **Slack, Notion, Zapier, n8n and more**.
@@ -138,18 +138,6 @@ You can deploy Formbricks on [Railway](https://railway.app) using the button bel
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/PPDzCd)
##### RepoCloud
Or you can also deploy Formbricks on [RepoCloud](https://repocloud.io) using the button below.
[![Deploy on RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=254)
##### Zeabur
Or you can also deploy Formbricks on [Zeabur](https://zeabur.com) using the button below.
[![Deploy to Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/G4TUJL)
<a id="development"></a>
## 👨‍💻 Development
@@ -166,7 +154,7 @@ Here is what you need to be able to run Formbricks:
### Local Setup
To get started locally, we've got a [guide to help you](https://formbricks.com/docs/developer-docs/contributing/get-started#local-machine-setup).
To get started locally, we've got a [guide to help you](https://formbricks.com/docs/contributing/setup).
### Gitpod Setup
@@ -190,7 +178,7 @@ Here are a few options:
- Upvote issues with 👍 reaction so we know what the demand for a particular issue is to prioritize it within the roadmap.
Please check out [our contribution guide](https://formbricks.com/docs/developer-docs/contributing/get-started) and our [list of open issues](https://github.com/formbricks/formbricks/issues) for more information.
Please check out [our contribution guide](https://formbricks.com/docs/contributing/introduction) and our [list of open issues](https://github.com/formbricks/formbricks/issues) for more information.
## All Thanks To Our Contributors
@@ -230,7 +218,7 @@ Additional to the AGPL licensed Formbricks core, this repository contains code l
### White-Labeling Formbricks and Other Licensing Needs
We currently do not offer Formbricks white-labeled. Any other needs? [Send us an email](mailto:hola@formbricks.com).
If you have other licensing requirements such as White-Labeling please [send us an email](mailto:hola@formbricks.com).
### Why charge for Enterprise Features?

View File

@@ -1,2 +0,0 @@
EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq

View File

@@ -1,7 +0,0 @@
module.exports = {
extends: ["@formbricks/eslint-config/react.js"],
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: __dirname,
},
};

View File

@@ -1,35 +0,0 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo

View File

@@ -1,34 +0,0 @@
{
"expo": {
"name": "react-native-demo",
"slug": "react-native-demo",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"jsEngine": "hermes",
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"infoPlist": {
"NSCameraUsageDescription": "Take pictures for certain activities.",
"NSPhotoLibraryUsageDescription": "Select pictures for certain activities.",
"NSMicrophoneUsageDescription": "Need microphone access for recording videos."
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,6 +0,0 @@
module.exports = function babel(api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
};
};

View File

@@ -1,7 +0,0 @@
import { registerRootComponent } from "expo";
import { LogBox } from "react-native";
import App from "./src/app";
registerRootComponent(App);
LogBox.ignoreAllLogs();

View File

@@ -1,21 +0,0 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const path = require("node:path");
const { getDefaultConfig } = require("expo/metro-config");
// Find the workspace root, this can be replaced with `find-yarn-workspace-root`
const workspaceRoot = path.resolve(__dirname, "../..");
const projectRoot = __dirname;
const config = getDefaultConfig(projectRoot);
// 1. Watch all files within the monorepo
config.watchFolders = [workspaceRoot];
// 2. Let Metro know where to resolve packages, and in what order
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;
module.exports = config;

View File

@@ -1,28 +0,0 @@
{
"name": "@formbricks/demo-react-native",
"version": "1.0.0",
"main": "./index.js",
"scripts": {
"dev": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"clean": "rimraf .turbo node_modules .expo"
},
"dependencies": {
"@formbricks/js": "workspace:*",
"@formbricks/react-native": "workspace:*",
"expo": "^51.0.26",
"expo-status-bar": "~1.12.1",
"react": "^18.2.0",
"react-native": "^0.74.4",
"react-native-webview": "13.8.6"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~18.2.79",
"typescript": "^5.3.3"
},
"private": true
}

View File

@@ -1,52 +0,0 @@
import { StatusBar } from "expo-status-bar";
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
import Formbricks, { track } from "@formbricks/react-native";
LogBox.ignoreAllLogs();
export default function App(): JSX.Element {
if (!process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID) {
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
}
if (!process.env.EXPO_PUBLIC_API_HOST) {
throw new Error("EXPO_PUBLIC_API_HOST is required");
}
const config = {
environmentId: process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
apiHost: process.env.EXPO_PUBLIC_API_HOST,
userId: "random-user-id",
attributes: {
language: "en",
testAttr: "attr-test",
},
};
return (
<View style={styles.container}>
<Text>Formbricks React Native SDK Demo</Text>
<Button
title="Trigger Code Action"
onPress={() => {
track("code").catch((error: unknown) => {
// eslint-disable-next-line no-console -- logging is allowed in demo apps
console.error("Error tracking event:", error);
});
}}
/>
<StatusBar style="auto" />
<Formbricks initConfig={config} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});

View File

@@ -1,6 +0,0 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

View File

@@ -1,3 +1,4 @@
module.exports = {
extends: ["@formbricks/eslint-config/legacy-next.js"],
root: true,
extends: ["formbricks"],
};

View File

@@ -1,6 +1,6 @@
import { Sidebar } from "./Sidebar";
import Sidebar from "./Sidebar";
export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
export default function LayoutApp({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-full">
{/* Static sidebar for desktop */}
@@ -10,4 +10,4 @@ export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
<div className="flex flex-1 flex-col lg:pl-64">{children}</div>
</div>
);
};
}

View File

@@ -1,3 +1,4 @@
import { classNames } from "@/lib/utils";
import {
ClockIcon,
CogIcon,
@@ -9,7 +10,6 @@ import {
ShieldCheckIcon,
UsersIcon,
} from "lucide-react";
import { classNames } from "../lib/utils";
const navigation = [
{ name: "Home", href: "#", icon: HomeIcon, current: true },
@@ -25,7 +25,7 @@ const secondaryNavigation = [
{ name: "Privacy", href: "#", icon: ShieldCheckIcon },
];
export const Sidebar = () => {
export default function Sidebar({}) {
return (
<div className="flex flex-grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
<nav
@@ -62,4 +62,4 @@ export const Sidebar = () => {
</nav>
</div>
);
};
}

View File

@@ -1,22 +0,0 @@
interface SurveySwitchProps {
value: "website" | "app";
formbricks: any;
}
export const SurveySwitch = ({ value, formbricks }: SurveySwitchProps) => {
return (
<select
value={value}
onChange={(v) => {
formbricks.logout();
window.location.href = `/${v.target.value}`;
}}>
<option value="website" className="h-10 px-4 hover:bg-slate-100">
Website Surveys
</option>
<option value="app" className="hover:bg-slate-10 h-10 px-4">
App Surveys
</option>
</select>
);
};

View File

@@ -1,3 +1,3 @@
export const classNames = (...classes: any) => {
export function classNames(...classes: any) {
return classes.filter(Boolean).join(" ");
};
}

27
apps/demo/next.config.js Normal file
View File

@@ -0,0 +1,27 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
async redirects() {
return [
{
source: "/",
destination: "/app",
permanent: false,
},
];
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "tailwindui.com",
},
{
protocol: "https",
hostname: "images.unsplash.com",
},
],
},
};
module.exports = nextConfig;

View File

@@ -1,27 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
async redirects() {
return [
{
source: "/",
destination: "/app",
permanent: false,
},
];
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "tailwindui.com",
},
{
protocol: "https",
hostname: "images.unsplash.com",
},
],
},
};
export default nextConfig;

View File

@@ -12,14 +12,12 @@
},
"dependencies": {
"@formbricks/js": "workspace:*",
"@formbricks/ui": "workspace:*",
"lucide-react": "^0.418.0",
"next": "14.2.5",
"react": "18.3.1",
"react-dom": "18.3.1"
"lucide-react": "^0.356.0",
"next": "14.1.3",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@formbricks/eslint-config": "workspace:*",
"@formbricks/config-typescript": "workspace:*"
"eslint-config-formbricks": "workspace:*"
}
}

View File

@@ -1,8 +1,9 @@
import type { AppProps } from "next/app";
import Head from "next/head";
import "../styles/globals.css";
const App = ({ Component, pageProps }: AppProps) => {
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
@@ -17,6 +18,4 @@ const App = ({ Component, pageProps }: AppProps) => {
<Component {...pageProps} />
</>
);
};
export default App;
}

View File

@@ -1,6 +1,6 @@
import { Head, Html, Main, NextScript } from "next/document";
const Document = () => {
export default function Document() {
return (
<Html lang="en" className="h-full bg-slate-50">
<Head />
@@ -10,6 +10,4 @@ const Document = () => {
</body>
</Html>
);
};
export default Document;
}

View File

@@ -1,13 +1,14 @@
import Image from "next/image";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import formbricks from "@formbricks/js/app";
import { SurveySwitch } from "../../components/SurveySwitch";
import formbricks from "@formbricks/js";
import fbsetup from "../../public/fb-setup.png";
declare const window: any;
const AppPage = ({}) => {
export default function AppPage({}) {
const [darkMode, setDarkMode] = useState(false);
const router = useRouter();
@@ -29,23 +30,19 @@ const AppPage = ({}) => {
window.history.replaceState({}, "", newUrl);
}
};
addFormbricksDebugParam();
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
const userId = "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING";
const userInitAttributes = {
language: "de",
"Init Attribute 1": "eight",
"Init Attribute 2": "two",
};
const isUserId = window.location.href.includes("userId=true");
const attributes = isUserId ? { "Init Attribute 1": "eight", "Init Attribute 2": "two" } : undefined;
const userId = isUserId ? "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING" : undefined;
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
userId,
attributes: userInitAttributes,
attributes,
});
window.formbricks = formbricks;
}
// Connect next.js router to Formbricks
@@ -57,24 +54,20 @@ const AppPage = ({}) => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}
}, []);
});
return (
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
<div className="flex flex-col justify-between md:flex-row">
<div className="flex flex-col items-center gap-2 sm:flex-row">
<SurveySwitch value="app" formbricks={formbricks} />
<div>
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
Formbricks In-product Survey Demo App
</h1>
<p className="text-slate-700 dark:text-slate-300">
This app helps you test your app surveys. You can create and test user actions, create and
update user attributes, etc.
</p>
</div>
<div>
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
Formbricks In-product Survey Demo App
</h1>
<p className="text-slate-700 dark:text-slate-300">
This app helps you test your in-app surveys. You can create and test user actions, create and
update user attributes, etc.
</p>
</div>
<button
className="mt-2 rounded-lg bg-slate-200 px-6 py-1 dark:bg-slate-700 dark:text-slate-100"
onClick={() => setDarkMode(!darkMode)}>
@@ -98,7 +91,7 @@ const AppPage = ({}) => {
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
</strong>
<span className="relative ml-2 flex h-3 w-3">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span>
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
</span>
</div>
@@ -117,7 +110,7 @@ const AppPage = ({}) => {
</div>
<div className="md:grid md:grid-cols-3">
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-800">
<div className="col-span-3 rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-800">
<h3 className="text-lg font-semibold dark:text-white">
Reset person / pull data from Formbricks app
</h3>
@@ -140,7 +133,27 @@ const AppPage = ({}) => {
<div className="p-6">
<div>
<button className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
<button
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
onClick={() => {
formbricks.track("Code Action");
}}>
Code Action
</button>
</div>
<div>
<p className="text-xs text-slate-700 dark:text-slate-300">
This button sends a{" "}
<a href="https://formbricks.com/docs/actions/code" className="underline" target="_blank">
Code Action
</a>{" "}
to the Formbricks API called &apos;Code Action&apos;. You will find it in the Actions Tab.
</p>
</div>
</div>
<div className="p-6">
<div>
<button className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
No-Code Action
</button>
</div>
@@ -169,7 +182,7 @@ const AppPage = ({}) => {
onClick={() => {
formbricks.setAttribute("Plan", "Free");
}}
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
Set Plan to &apos;Free&apos;
</button>
</div>
@@ -192,7 +205,7 @@ const AppPage = ({}) => {
onClick={() => {
formbricks.setAttribute("Plan", "Paid");
}}
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
Set Plan to &apos;Paid&apos;
</button>
</div>
@@ -215,7 +228,7 @@ const AppPage = ({}) => {
onClick={() => {
formbricks.setEmail("test@web.com");
}}
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
Set Email
</button>
</div>
@@ -232,10 +245,43 @@ const AppPage = ({}) => {
</p>
</div>
</div>
<div className="p-6">
{router.query.userId === "true" ? (
<div>
<button
onClick={() => {
window.location.href = "/app";
}}
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
Deactivate User Identification
</button>
</div>
) : (
<div>
<button
onClick={() => {
window.location.href = "/app?userId=true";
}}
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
Activate User Identification
</button>
</div>
)}
<div>
<p className="text-xs text-slate-700 dark:text-slate-300">
This button activates/deactivates{" "}
<a
href="https://formbricks.com/docs/attributes/identify-users"
target="_blank"
className="underline dark:text-blue-500">
user identification
</a>{" "}
with the userId &apos;THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING&apos;
</p>
</div>
</div>
</div>
</div>
</div>
);
};
export default AppPage;
}

View File

@@ -1,143 +0,0 @@
import Image from "next/image";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import formbricks from "@formbricks/js/website";
import { SurveySwitch } from "../../components/SurveySwitch";
import fbsetup from "../../public/fb-setup.png";
declare const window: any;
const AppPage = ({}) => {
const [darkMode, setDarkMode] = useState(false);
const router = useRouter();
useEffect(() => {
if (darkMode) {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
}, [darkMode]);
useEffect(() => {
// enable Formbricks debug mode by adding formbricksDebug=true GET parameter
const addFormbricksDebugParam = () => {
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has("formbricksDebug")) {
urlParams.set("formbricksDebug", "true");
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
window.history.replaceState({}, "", newUrl);
}
};
addFormbricksDebugParam();
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
const defaultAttributes = {
language: "en",
};
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
attributes: defaultAttributes,
});
}
// Connect next.js router to Formbricks
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
const handleRouteChange = formbricks?.registerRouteChange;
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}
});
return (
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
<div className="flex flex-col items-center justify-between md:flex-row">
<div className="flex flex-col items-center gap-2 sm:flex-row">
<SurveySwitch value="website" formbricks={formbricks} />
<div>
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
Formbricks Website Survey Demo App
</h1>
<p className="text-slate-700 dark:text-slate-300">
This app helps you test your app surveys. You can create and test user actions, create and
update user attributes, etc.
</p>
</div>
</div>
<button
className="mt-2 rounded-lg bg-slate-200 px-6 py-1 dark:bg-slate-700 dark:text-slate-100"
onClick={() => setDarkMode(!darkMode)}>
{darkMode ? "Toggle Light Mode" : "Toggle Dark Mode"}
</button>
</div>
<div className="my-4 grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<div className="rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">1. Setup .env</h3>
<p className="text-slate-700 dark:text-slate-300">
Copy the environment ID of your Formbricks app to the env variable in /apps/demo/.env
</p>
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded" priority />
<div className="mt-4 flex-col items-start text-sm text-slate-700 sm:flex sm:items-center sm:text-base dark:text-slate-300">
<p className="mb-1 sm:mb-0 sm:mr-2">You&apos;re connected with env:</p>
<div className="flex items-center">
<strong className="w-32 truncate sm:w-auto">
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
</strong>
<span className="relative ml-2 flex h-3 w-3">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span>
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
</span>
</div>
</div>
</div>
<div className="mt-4 rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">2. Widget Logs</h3>
<p className="text-slate-700 dark:text-slate-300">
Look at the logs to understand how the widget works.{" "}
<strong className="dark:text-white">Open your browser console</strong> to see the logs.
</p>
{/* <div className="max-h-[40vh] overflow-y-auto py-4">
<LogsContainer />
</div> */}
</div>
</div>
<div className="md:grid md:grid-cols-3">
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-800">
<h3 className="text-lg font-semibold dark:text-white">
Reset person / pull data from Formbricks app
</h3>
<p className="text-slate-700 dark:text-slate-300">
On formbricks.reset() the local state will <strong>be deleted</strong> and formbricks gets{" "}
<strong>reinitialized</strong>.
</p>
<button
className="my-4 rounded-lg bg-slate-500 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
onClick={() => {
formbricks.reset();
}}>
Reset
</button>
<p className="text-xs text-slate-700 dark:text-slate-300">
If you made a change in Formbricks app and it does not seem to work, hit &apos;Reset&apos; and
try again.
</p>
</div>
</div>
</div>
</div>
);
};
export default AppPage;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,5 +1,23 @@
{
"extends": "@formbricks/config-typescript/nextjs.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View File

@@ -1,10 +0,0 @@
NEXT_PUBLIC_FORMBRICKS_COM_API_HOST=http://localhost:3000
NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID=
NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID=
# Strapi API Key
STRAPI_API_KEY=
NEXT_PUBLIC_DOCSEARCH_APP_ID=
NEXT_PUBLIC_DOCSEARCH_API_KEY=
NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=

View File

@@ -1,3 +0,0 @@
module.exports = {
extends: ["@formbricks/eslint-config/legacy-next.js"],
};

38
apps/docs/.gitignore vendored
View File

@@ -1,38 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
public/sitemap*.xml
public/robots.txt

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,131 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import I1 from "./images/I1.webp";
import I2 from "./images/I2.webp";
export const metadata = {
title: "Using Actions in Formbricks | Fine-tuning User Moments",
description:
"Dive deep into how actions in Formbricks help products and organizations to engage users at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine user targeting and generate richer, more detailed user insights.",
};
#### App Surveys
# Actions & Targeting
Understanding user thoughts and feelings at critical moments in their journey is pivotal. To achieve this, Formbricks uses user-centric actions that trigger surveys at precisely the right time. Actions are essentially notifications sent from your application to Formbricks when predefined user activities occur, making it possible to gather insights during key interactions.
<Note>
Ensure that youve **initialized Formbricks with a userId** to fully utilize this feature along with other
app survey capabilities.
</Note>
## **How Do Actions Work?**
Actions in Formbricks App Surveys are deeply integrated with user activities within your app. When a user performs a specified action, the Formbricks widget detects this activity and can present a survey to that specific user if the trigger conditions match of that survey, while also recording the event. This capability ensures that surveys are not only triggered at the right time but are also tailored to the users recent interactions within the app. You can set up these actions through a user-friendly No-Code interface within the Formbricks dashboard.
## **Why Are Actions Useful?**
Actions are invaluable for enhancing survey relevance and effectiveness:
- **Personalized Engagement**: Surveys triggered by user actions ensure content is highly relevant and engaging, matching each users current context.
- **User Attributes**: By tying surveys to specific user attributes, such as activity levels or feature usage, you can customize the survey experience to reflect individual user profiles.
- **User Segments**: Analyze action data to create detailed user segments, targeting specific groups with surveys that are pertinent to their behaviors or interactions within the app.
- **User Targeting**: Precise targeting based on user actions and attributes ensures that surveys are shown only to users who meet certain criteria, enhancing the relevance and effectiveness of each survey.
## **Setting Up No-Code Actions**
Formbricks offers an intuitive No-Code interface that allows you to configure actions without needing to write any code.
To add a No-Code Action:
1. Visit the Formbricks Dashboard & switch to the Actions tab:
<MdxImage
src={I1}
alt="setup checklist ui of survey popup for app surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. Now click on “Add Action”
<MdxImage
src={I2}
alt="setup checklist ui of survey popup for app surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
Here are four types of No-Code actions you can set up:
### **1. Click Action**
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
### **2. Page view Action**
This action is triggered when a user visits a page within your application.
### **3. Exit Intent Action**
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
### **4. 50% Scroll Action**
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
<Note>
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
### **URL Match Conditions**
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
- **contains**: Activates when the URL contains the specified substring.
- **startsWith**: Fires when the URL starts with the specified string.
- **endsWith**: Executes when the URL ends with the specified string.
- **notMatch**: Triggers when the URL does not match the specified condition.
- **notContains**: Activates when the URL does not contain the specified substring.
</Note>
## **Setting Up Code Actions**
For more granular control, you can implement actions directly in your codebase:
1. **Configure the Action**: First, add the action via the Formbricks web interface to make it available for survey configuration.
After that you can fire an action using `formbricks.track()`
2. **Track an Action**: Use formbricks.track() to send an action event to Formbricks.
<Col>
<CodeGroup title="Track an action">
```javascript
formbricks.track("Action Name");
```
</CodeGroup>
</Col>
Here is an example of how to fire an action when a user clicks a button:
<Col>
<CodeGroup title="Track Button Click">
```javascript
const handleClick = () => {
formbricks.track("Button Clicked");
};
return <button onClick={handleClick}>Click Me</button>;
```
</CodeGroup>
</Col>
This documentation frames actions around user interactions, emphasizing the connection between the user's activities and the survey experience. By leveraging user-centric actions, you can create highly targeted and timely surveys that resonate with users and yield valuable insights.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,81 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import { ResponsiveVideo } from "@/components/ResponsiveVideo";
import GermansGpt from "./germans-gpt.webp";
import Hni from "./hni.webp";
import PowerUsers from "./power-users.webp";
import RideHailing from "./ride-hailing.webp";
import UpsellMiro from "./upsell-miro.webp";
export const metadata = {
title: "Advanced Targeting for In-app Surveys | Formbricks",
description:
"Advanced Targeting allows you to show surveys to just the right group of people. You can target surveys based on user attributes, user events, and metadata. This helps you get more relevant feedback and make data-driven decisions.",
};
#### App Surveys
# Advanced Targeting
<Note>
Targeting based on actions is deprecated in Advanced Targeting and will be removed soon. We recommend using
filters on user attributes to target the survey only to specific groups of users.
</Note>
Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, device type, and more instead of spraying and praying. This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code.
<ResponsiveVideo
title="Formbricks Multi-language Surveys"
src="https://www.youtube-nocookie.com/embed/0BQp6N4cXzU?si=KeBM7G7Ch1xtrsOm&amp;controls=0"
/>
## How to setup Advanced Targeting
<Note>
Advanced Targeting is available on the Pro plan!
</Note>
1. On the Formbricks dashboard, click on **People** tab from the top navigation bar.
2. Switch to the **Segments** tab & click on **Create Segment**.
3. Give your segment a title & a description to help you remember what this segment is about.
4. Now click on the **Add Filter** button to add a filter. You can filter based on actions, user attributes, other segments, devices, and more.
5. To group a set of filters together, click on the Three Dots icon on the right side of the filter and click on **Create Group**.
6. Try playing around with different filters & conditions that we have provided to see how the segment size changes.
7. Once you are happy with the segment, click on **Save Segment**.
8. Now, when you create a survey, you can select this segment to target your survey to.
## Examples:
1. Let's say you want to upsell to: Miro, Loom, Figma, Slack and Asana.
<MdxImage
src={UpsellMiro}
alt="Upselling Opportunity"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. Post-experience surveying for a ride hailing app where users who have taken more than 1 ride are shown a survey.
<MdxImage
src={RideHailing}
alt="Ride Hailing Targeting"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
3. Sneak Peak: How we at Formbricks automate inviting power users to chat with us
<MdxImage
src={PowerUsers}
alt="Automate inviting power users to chat with us at Formbricks"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,66 +0,0 @@
import { Button } from "@/components/Button";
import logoHtml from "@/images/frameworks/html5.svg";
import logoNextjs from "@/images/frameworks/nextjs.svg";
import logoReactJs from "@/images/frameworks/reactjs.svg";
import logoVueJs from "@/images/frameworks/vuejs.svg";
import Image from "next/image";
const libraries = [
{
href: "#html",
name: "HTML",
description: "All you need to do is add 3 lines of code to your HTML script and thats it, you're done!",
logo: logoHtml,
},
{
href: "#react-js",
name: "React.js",
description: "Load the our Js library with your environment ID and you're ready to go!",
logo: logoReactJs,
},
{
href: "#next-js",
name: "Next.js",
description:
"Natively add us to your NextJs project with support for both App as well as Pages project structure!",
logo: logoNextjs,
},
{
href: "#vue-js",
name: "Vue.js",
description: "Simply add us to your router change and sit back!",
logo: logoVueJs,
},
{
href: "#react-native",
name: "React Native",
description: "Easily integrate our SDK with your React Native app for seamless survey support!",
logo: logoReactJs,
},
];
export const Libraries = () => {
return (
<div className="my-16 xl:max-w-none">
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-slate-900/5 xl:max-w-none xl:grid-cols-2 2xl:grid-cols-3 dark:border-white/5">
{libraries.map((library) => (
<a
key={library.name}
href={library.href}
className="flex flex-row-reverse gap-6 rounded-2xl p-6 transition-all duration-100 ease-in-out hover:cursor-pointer hover:bg-slate-100/50 dark:hover:bg-slate-800/50">
<div className="flex-auto">
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{library.name}</h3>
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">{library.description}</p>
<p className="mt-4">
<Button href={library.href} variant="text" arrow="right">
Read more
</Button>
</p>
</div>
<Image src={library.logo} alt="" className="h-12 w-12" unoptimized />
</a>
))}
</div>
</div>
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,471 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import { Libraries } from "./components/Libraries";
import ReactApp from "./images/react-in-app-survey-app-popup-form.webp";
import WidgetConnected from "./images/widget-connected.webp";
import WidgetNotConnected from "./images/widget-not-connected.webp";
export const metadata = {
title: "Integrate Formbricks: Comprehensive Framework Guide & Integration Tutorial",
description:
"Master the integration of Formbricks into your application with our detailed guides. From HTML to ReactJS, NextJS, and VueJS, get step-by-step instructions and ensure seamless setup.",
};
# Framework Guides
One can integrate Formbricks App Survey SDK into their app using multiple options! Checkout the options below that we provide! If you are looking
for something else, please [join our Discord!](https://formbricks.com/discord) and we would be glad to help.
<Libraries />
---
## Prerequisites
Before getting started, make sure you have:
1. A web application (behind your user authentication system) in your desired framework is set up and running.
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings.
---
## HTML
All you need to do is copy a `<script>` tag to your HTML head, and thats about it!
<Col>
<CodeGroup title="HTML">
```html {{ title: 'index.html' }}
<!-- START Formbricks Surveys -->
<script type="text/javascript">
!function(){
var apiHost = "https://app.formbricks.com";
var environmentId = "<your-environment-id>";
var userId = "<your-user-id>";
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/app";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
</script>
<!-- END Formbricks Surveys -->
```
</CodeGroup>
</Col>
### Required customizations to be made
<Properties>
<Property name="environment-id" type="string">
Formbricks Environment ID.
</Property>
<Property name="api-host" type="string">
URL of the hosted Formbricks instance.
</Property>
<Property name="userId" type="string">
User ID of the user who has active session.
</Property>
</Properties>
Refer to our [Example HTML project](https://github.com/formbricks/examples/tree/main/html) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
---
## ReactJS
Install the Formbricks SDK using one of the package managers ie `npm`,`pnpm`,`yarn`. Note that zod is required as a peer dependency must also be installed in your project.
<Col>
<CodeGroup title="Install Formbricks JS library">
```shell {{ title: 'npm' }}
npm install @formbricks/js zod
```
```shell {{ title: 'pnpm' }}
pnpm add @formbricks/js zod
```
```shell {{ title: 'yarn' }}
yarn add @formbricks/js zod
```
</CodeGroup>
</Col>
Now, update your App.js/ts file to initialise Formbricks.
<Col>
<CodeGroup title="src/App.js">
```js
// other imports
import formbricks from "@formbricks/js/app";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>",
});
}
function App() {
// your own app
}
export default App;
```
</CodeGroup>
</Col>
### Required customizations to be made
<Properties>
<Property name="environment-id" type="string">
Formbricks Environment ID.
</Property>
<Property name="api-host" type="string">
URL of the hosted Formbricks instance.
</Property>
<Property name="userId" type="string">
User ID of the user who has active session.
</Property>
</Properties>
Refer to our [Example ReactJs project](https://github.com/formbricks/examples/tree/main/reactjs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
---
## NextJS
NextJs projects typically follow two main conventions: the App Directory and the Pages Directory.
To ensure smooth integration with the Formbricks SDK, which operates solely on the client side, follow the
guidelines for each convention below:
- App directory: You will have to define a new component in `app/formbricks.tsx` file and call it in your `app/layout.tsx` file.
- Pages directory: You will have to visit your `_app.tsx` and just initialise Formbricks there.
Code snippets for the integration for both conventions are provided to further assist you.
<Col>
<CodeGroup title="Install Formbricks JS library">
```shell {{ title: 'npm' }}
npm install @formbricks/js zod
```
```shell {{ title: 'pnpm' }}
pnpm add @formbricks/js zod
```
```shell {{ title: 'yarn' }}
yarn add @formbricks/js zod
```
</CodeGroup>
</Col>
### App Directory
<Col>
<CodeGroup title="app/formbricks.tsx">
```tsx {{title: 'Typescript'}}
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import formbricks from "@formbricks/js/app";
export default function FormbricksProvider() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>",
});
}, []);
useEffect(() => {
formbricks?.registerRouteChange();
}, [pathname, searchParams]);
return null;
}
```
</CodeGroup>
<CodeGroup title="app/layout.tsx">
```tsx {{title: 'Typescript'}}
// other imports
import FormbricksProvider from "./formbricks";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<FormbricksProvider />
<body>{children}</body>
</html>
);
}
```
</CodeGroup>
</Col>
Refer to our [Example NextJS App Directory project](https://github.com/formbricks/examples/tree/main/nextjs-app) for more help!
### Pages Directory
<Col>
<CodeGroup title="src/pages/_app.tsx">
```tsx {{ title: 'Typescript' }}
// other import
import { useRouter } from "next/router";
import { useEffect } from "react";
import formbricks from "@formbricks/js/app";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>",
});
}
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
useEffect(() => {
// Connect next.js router to Formbricks
const handleRouteChange = formbricks?.registerRouteChange;
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, []);
return <Component {...pageProps} />;
}
```
</CodeGroup>
</Col>
Refer to our [Example NextJS Pages Directory project](https://github.com/formbricks/examples/tree/main/nextjs-pages) for more help!
### Required customizations to be made
<Properties>
<Property name="environment-id" type="string">
Formbricks Environment ID.
</Property>
<Property name="api-host" type="string">
URL of the hosted Formbricks instance.
</Property>
<Property name="userId" type="string">
User ID of the user who has active session.
</Property>
</Properties>
First we initialize the Formbricks SDK, ensuring that it only runs on the client side.
To connect the Next.js router to Formbricks and ensure the SDK can keep track of every page change, we are registering the route change event.
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
---
## VueJs
Integrating the Formbricks SDK with Vue.js is a straightforward process.
We will make sure the SDK is only loaded and used on the client side, as it's not intended for server-side usage.
<Col>
<CodeGroup title="Install Formbricks JS library">
```shell {{ title: 'npm' }}
npm install @formbricks/js
````
```shell {{ title: 'pnpm' }}
pnpm add @formbricks/js
```
```shell {{ title: 'yarn' }}
yarn add @formbricks/js
```
</CodeGroup>
<CodeGroup title="src/formbricks.js">
```js
import formbricks from "@formbricks/js/app";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>",
});
}
export default formbricks;
```
</CodeGroup>
<CodeGroup title="src/main.js">
```js
// other imports
import formbricks from "@/formbricks";
const app = createApp(App);
app.use(router);
app.mount("#app");
router.afterEach((to, from) => {
if (typeof formbricks !== "undefined") {
formbricks.registerRouteChange();
}
});
```
</CodeGroup>
</Col>
### Required customizations to be made
<Properties>
<Property name="environment-id" type="string">
Formbricks Environment ID.
</Property>
</Properties>
<Properties>
<Property name="api-host" type="string">
URL of the hosted Formbricks instance.
</Property>
<Property name="userId" type="string">
User ID of the user who has active session.
</Property>
</Properties>
Refer to our [Example VueJs project](https://github.com/formbricks/examples/tree/main/vuejs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
## React Native
Install the Formbricks React Native SDK using one of the package managers, i.e., npm, pnpm, or yarn.
<Col>
<CodeGroup title="Install Formbricks JS library">
```shell {{ title: 'npm' }}
npm install @formbricks/react-native
```
```shell {{ title: 'pnpm' }}
pnpm add @formbricks/react-native
```
```shell {{ title: 'yarn' }}
yarn add @formbricks/react-native
```
</CodeGroup>
</Col>
Now, update your App.js/App.tsx file to initialize Formbricks:
<Col>
<CodeGroup title="src/App.js">
```js
// other imports
import Formbricks from "@formbricks/react-native";
const config = {
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>",
};
export default function App() {
return (
<>
{/* Your app content */}
<Formbricks initConfig={config} />
</>
);
}
```
</CodeGroup>
</Col>
### Required customizations to be made
<Properties>
<Property name="environment-id" type="string">
Formbricks Environment ID.
</Property>
<Property name="api-host" type="string">
URL of the hosted Formbricks instance.
</Property>
<Property name="userId" type="string">
User ID of the user who has active session.
</Property>
</Properties>
---
## Validate your setup
Once you have completed the steps above, you can validate your setup by checking the **Setup Checklist** in the Settings. Your widget status indicator should go from this:
<MdxImage
src={WidgetNotConnected}
alt="Widget isnt connected"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
To this:
<MdxImage
src={WidgetConnected}
alt="Widget is connected"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
## Debugging Formbricks Integration
Enabling Formbricks debug mode in your browser is a useful troubleshooting step for identifying and resolving complex issues. This section outlines how to activate debug mode, covers common use cases, and provides insights into specific debug log messages.
### Activate Debug Mode
To activate Formbricks debug mode:
1. **Via URL Parameter:**
- Enable debug mode mode by adding `?formbricksDebug=true` to your application's URL (e.g. `https://example.com?formbricksDebug=true` or `https://example.com?page=123&formbricksDebug=true`). This parameter will enable debugging for the current page.
2. **View Debug Logs:**
- Open your browser's developer tools by pressing `F12` or right-clicking and selecting "Inspect."
- Navigate to the "Console" tab to view Formbricks debugging information.
**How to Open Browser Console:**
- **Google Chrome:** Press `F12` or right-click, select "Inspect," and go to the "Console" tab.
- **Firefox:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
### Common Use Cases
Debug mode is beneficial for scenarios such as:
- Verifying Formbricks initialization.
- Identifying survey trigger issues.
- Troubleshooting unexpected behavior.
### Debug Log Messages
Debug log messages provide insights into:
- API calls and responses.
- Event tracking, survey triggers and form interactions.
- Initialization errors.
**Cant figure it out? [Join our Discord!](https://formbricks.com/discord)**
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,117 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import I1 from "./images/I1.webp";
import I2 from "./images/I2.webp";
import I3 from "./images/I3.webp";
import I4 from "./images/I4.webp";
import I5 from "./images/I5.webp";
import I6 from "./images/I6.webp";
import I7 from "./images/I7.webp";
import I8 from "./images/I8.webp";
export const metadata = {
title: "Formbricks Quickstart Guide: App Surveys Made Easier & Faster",
description:
"Formbricks is the easiest way to create and manage app surveys. This quickstart guide will show you how to create your first app survey in under 5 minutes.",
};
#### App Surveys
# Quickstart
App surveys have 6-10x better conversion rates than emailed surveys. This tutorial explains how to run a survey in both your web app and mobile app (React Native) in just 10 to 15 minutes. Lets go!
<Note>
App Surveys are ideal for websites that **have a user authentication** system. If you are looking to run
surveys on your public facing website, head over to the [Website Surveys Quickstart
Guide](/website-surveys/quickstart).
</Note>
1. **Create a free Formbricks Cloud account**: While you can [self-host](/self-hosting/deployment) Formbricks, but the quickest and easiest way to get started is with the free Cloud plan. Just [sign up here](https://app.formbricks.com/auth/signup) and you'll be guided to our onboarding like below:
<Note>
Website & App Surveys have the same integration process. The difference will come when we setup our survey.
</Note>
<MdxImage
src={I1}
alt="Choose website survey from survey type"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. **Connect your App/Website**: Once you get through a couple of onboarding steps, youll be asked to connect your app or website. This is where youll find the code snippet for both HTML as well as the npm package which you need to embed in your app:
<MdxImage
src={I2}
alt="Code snippet for connecting app with Formbricks"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
Paste the code snippet in your app and reload the page. You should now see the Formbricks widget in the lower right corner of your app! The integration is now complete.
<MdxImage
src={I3}
alt="App connected with Formbricks"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
Onboarding is complete! Now lets create our first survey as you should see templates to choose from after clicking on **Next**:
<MdxImage
src={I4}
alt="Choose a survey template"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
3. **Create your first survey**: To be able to see a survey in your app, you need to create one. Well choose one of the templates and head over to the survey settings:
Pick the Survey Type as **App Survey**.
<MdxImage
src={I5}
alt="Survey settings for app survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
4. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
<MdxImage
src={I6}
alt="Survey trigger settings for app survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
5. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once to each user to prevent survey fatigue:
<Note>
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
</Note>
<MdxImage
src={I7}
alt="Recontact options for app survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
6. **Publish your survey**: Now hit **Publish** and youll be forwarded to the Summary Page. This is where youll find the responses to this survey.
<MdxImage
src={I8}
alt="Survey published successfully"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
---
- We offer framework guides for various frontend tech, head over to the the [App Survey Framework Guides](/app-survey/framework-guides) to get started with your app survey.
- Head over to our App Survey SDK documentation to get started with the [App Survey JS SDK](/developer-docs/app-survey-sdk).
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,107 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import AppSurvey from "./app-survey.webp";
import GlobalWaitTime from "./global-wait-time.webp";
import IgnoreWaitTime from "./ignore-wait-time.webp";
import SurveyRecontact from "./survey-recontact.webp";
export const metadata = {
title: "Recontact Options in Formbricks: When to show a survey again to a user",
description:
"Explore how to configure Recontact options in Formbricks to control the frequency of survey exposure to users, ensuring effective feedback collection without compromising user experience.",
};
#### App Surveys
# Recontact Options
Recontact options in Formbricks enable you to manage how often and under what conditions a survey is shown to a user. This feature is crucial for balancing effective feedback collection with a positive user experience by preventing survey fatigue.
## When do Recontact Options come into play?
Recontact options are the last layer of the logic that determines if a survey is shown to the current user. The logic goes as follows:
1. Targeting: Does the current user targeted to fill out this survey? If yes...
2. Trigger: Is the survey triggered? If yes...
3. **Recontact Options:** Should the survey be shown (again) to this user? That's dependent on:
- Did the user see any survey recently (meaning, has Global Waiting Time passed)?
- Did the user see this specific survey already?
- How many times did the user see this specific survey already?
- Has the user already responded to this survey?
As you can see, there are a lot of different cases to cover. Let's have a closer look 👇
## Recontact Options
<Note>By default, a survey is shown to each user only once.</Note>
You can adjust the default behavior by modifying the Recontact Options for each survey in the settings:
1. Open the Survey Editor for the survey you want to see & modify the Recontact Options for.
2. Select the Settings Tab.
3. Ensure your Survey type is set to **App Survey**.
<MdxImage
src={AppSurvey}
alt="Choose Survey Type as App Survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
4. Scroll down to the Recontact Options section.
Available Recontact Options include:
- **Show only once**: (default) Displays the survey a single time, regardless of whether it was completed.
- **Until they Submit a Response**: If tareting matches and trigger fires, Formbricks keeps showing the survey until the user submits a response.
- **Keep Showing while Conditions Match**: Always shows the survey while specific conditions are met. Useful for continuous feedback collection, such as in [Docs Feedback Survey](/best-practices/docs-feedback) or the [Feedback Box](/best-practices/feedback-box).
<MdxImage
src={SurveyRecontact}
alt="Choose Recontanct Options for the Survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
## Product-wide Global Waiting Time
The Global Waiting Time is a universal blocker to make sure that no user sees too many surveys. This is particularly helpful when several teams of large organisations use Formbricks at the same time.
<Note>The default Global Waiting Time is set to 7 days.</Note>
To adjust the Global Waiting Time:
1. Visit Formbricks Settings
2. Go to Product Settings
3. Find the **Recontact Waiting Time** section
4. Modify the interval (in days) as needed.
<MdxImage
src={GlobalWaitTime}
alt="Formbricks Product-Wide Wait Time"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
## Overriding Global Waiting Time for a Specific Survey
For specific surveys, you may need to override the default waiting time. Below is how you can do that:
1. In the Survey Editor, access the Settings Tab.
2. Find the Ignore Waiting Time between Surveys toggle under Recontact Options.
3. Enable this toggle to bypass the global setting.
4. Set a custom recontact period:
- **Always Show Survey**: Displays the survey whenever triggered, ignoring the waiting time.
- **Wait `X` days before showing this survey again**: Sets a specific interval before the survey can be shown again.
<MdxImage
src={IgnoreWaitTime}
alt="Ignore Global Waiting Time for a Specific Survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
---
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,123 +0,0 @@
export const metadata = {
title: "Identifying Users in Formbricks App Surveys",
description:
"Dive into the importance of user identification in surveys. Boost your survey response rates and target the right users with Formbricks.",
};
#### App Surveys
# User Identification
User Identification helps you to not only segment your users but also to see more information about the user who responded to a survey. This helps you to target surveys to specific user segments and see more information about the user who responded to a survey.
### Understanding Identified vs Unidentified Users
<Note>
Formbricks App Surveys can **only be used with identified users**. If you are looking to run surveys on your
public-facing website, head over to the [Website Surveys](/website-surveys/quickstart) section.
</Note>
In Formbricks, understanding the distinction between identified and unidentified users is crucial for effective survey segmentation and targeted feedback collection.
| Feature | Unidentified Users | Identified Users |
| -------------------------------------------------------- | ------------------ | ---------------- |
| Show surveys based on **trigger** actions | ✅ | ✅ |
| Set **recontact details** to avoid survey fatique | ✅ | ✅ |
| Target a subset of users using **attributes & segments** | ❌ | ✅ |
| Collect **user information** in Formbricks | ❌ | ✅ |
| Track **custom attributes** | ❌ | ✅ |
| Counts towards your **monthly tacked user (MTU)** limit | ❌ | ✅ |
| Recommended for **public-facing websites** | ✅ | ❌ |
| Recommended for **web apps after login** | ❌ | ✅ |
## Identified Users
Identified users are those for whom specific information has been set, notably the userId. This identification allows for more precise targeting of surveys and a deeper understanding of the feedback provided. When enabled, all information specified by you and all actions are sent to Formbricks.
This method is recommended for applications where users are required to log in and will often return.
### Setting User ID
To enable the User identification feature you need to set the `userId` in the init() call of Formbricks. Only when the `userId` is set the person will be visible in the Formbricks dashboard. The `userId` can be any string and it's best to use the default identifier you use in your app (e.g. unique id from database or the email address if it's unique) but you can also anonymize these as long as they are unique for every user.
<Col>
<CodeGroup title="Setting User ID">
```javascript
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user_id>",
});
```
</CodeGroup>
</Col>
### Enhanced Initialization with User Attributes
In addition to setting the `userId`, Formbricks allows you to set user attributes right at the initialization. This ensures that your user data is seamlessly integrated from the start. Here's how you can include user attributes in the `init()` function:
<Col>
<CodeGroup title="Enhanced Initialization with User Attributes">
```javascript
formbricks.init({
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user_id>",
attributes: {
// your custom attributes
Plan: "premium",
},
});
```
</CodeGroup>
</Col>
## Setting Custom User Attributes
You can use the setAttribute function to set any custom attribute for the user (e.g. name, plan, etc.):
<Note>
Please note that the number of different attribute classes (e.g., "Plan," "First Name," etc.) is currently
limited to 150 attributes per environment.
</Note>
<Col>
<CodeGroup title="Setting Custom Attributes">
```javascript
formbricks.setAttribute("Plan", "free");
```
</CodeGroup>
</Col>
Generally speaking, the setAttribute function works like this:
<Col>
<CodeGroup title="Setting Custom Attributes">
```javascript
formbricks.setAttribute("attribute_key", "attribute_value");
```
</CodeGroup>
</Col>
Where `attributeName` is the name of the attribute you want to set, and `attributeValue` is the value of the attribute you want to set.
### Logging Out Users
When a user logs out of your webpage, make sure to log them out of Formbricks as well. This will prevent new activity from being associated with an incorrect user. Use the logout function:
<Col>
<CodeGroup title="Logging out User">
```javascript
formbricks.logout();
```
</CodeGroup>
</Col>

View File

@@ -1,158 +0,0 @@
import { MdxImage } from "@/components/MdxImage";
import ChangeText from "./change-text.webp";
import CreateChurnFlow from "./create-cancel-flow.webp";
import PublishSurvey from "./publish-survey.webp";
import RecontactOptions from "./recontact-options.webp";
import SelectAction from "./select-action.webp";
import TriggerCSS from "./trigger-css-selector.webp";
import TriggerInnerText from "./trigger-inner-text.webp";
import TriggerPageUrl from "./trigger-page-url.webp";
export const metadata = {
title: "Mastering Churn Surveys with Formbricks | Essential Tips & Steps",
description:
"Learn how to effectively utilize Formbricks' Churn Surveys to gain deeper insights into user departures. Dive into a step-by-step guide to craft, trigger, and optimize your churn surveys, ensuring you capture invaluable feedback at critical junctures",
};
#### Best Practices
# Learn from Churn
Churn is hard, but can teach you a lot. Whenever a user decides that your product isnt worth it anymore, you have a unique opportunity to get deep insights. These insights are pure gold to reduce churn.
## Purpose
The Churn Survey is among the most effective ways to identify weaknesses in your offering. People were willing to pay but now are not anymore: What changed? Lets find out!
## Formbricks Approach
- Ask at exactly the right point in time
- Follow-up to prevent bad reviews
- Coming soon: Make survey mandatory
## Overview
To run the Churn Survey in your app you want to proceed as follows:
1. Create new Churn Survey at [app.formbricks.com](https://app.formbricks.com/)
2. Set up the user action to display survey at right point in time
3. Choose correct recontact options to never miss a feedback
4. Prevent that churn!
<Note>
## Formbricks Widget running? We assume that you have already installed the Formbricks Widget in your web app. Its required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (takes 15mins max.)](/app-surveys/quickstart)
</Note>
### 1. Create new Churn Survey
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
Click on "Create Survey" and choose the template “Churn Survey”:
<MdxImage
src={CreateChurnFlow}
alt="Create churn survey by template"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
### 2. Update questions (if you like)
Youre free to update the question and answer options. However, based on our experience, we suggest giving the provided template a go 😊
<MdxImage
src={ChangeText}
alt="Change text content"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
_Want to change the button color? You can do so in the product settings._
Save, and move over to the “Audience” tab.
### 3. Pre-segment your audience
In this case, you dont really need to pre-segment your audience. You likely want to ask everyone who hits the “Cancel subscription” button.
### 4. Set up a trigger
To create the trigger for your Churn Survey, you have three options to choose from:
1. **Trigger by Inner Text:** You likely have a “Cancel Subscription” button in your app. You can setup a user Action with the according `Inner Text` to trigger the survey, like so:
<MdxImage
src={TriggerInnerText}
alt="Set the trigger by inner Text"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. **Trigger by CSS Selector:** In case you have more than one button saying “Cancel Subscription” in your app and only want to display the survey when one of them is clicked, you want to be more specific. The best way to do that is to give this button the HTML `id=“cancel-subscription”` and set your user action up like so:
<MdxImage
src={TriggerCSS}
alt="Set the trigger by CSS Selector"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
1. **Trigger by page view filters:** Lastly, you could also display your survey on a subpage “/subscription-cancelled” where you forward users once they cancelled the trial subscription. You can then create a user Action with the type `Page View` and add select `Limit to specific pages` to add url filters, with the following settings:
<MdxImage
src={TriggerPageUrl}
alt="Set the trigger by page URL"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
Whenever a user visits this page, matches the filter conditions above and the recontact options (below) the survey will be displayed ✅
Here is our complete [Actions manual](/app-surveys/actions/) covering [No-Code](/app-surveys/actions#setting-up-no-code-actions) and [Code](/app-surveys/actions#setting-up-code-actions) Actions.
<Note>
## Pre-churn flow coming soon Were currently building full-screen survey pop-ups. Youll be able to prevent
users from closing the survey unless they respond to it. Its certainly debatable if you want that but you
could force them to click through the survey before letting them cancel 🤷
</Note>
### 5. Select Action in the “When to ask” card
<MdxImage
src={SelectAction}
alt="Select feedback button action"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
### 6. Last step: Set Recontact Options correctly
Lastly, scroll down to “Recontact Options”. Here you have to choose the correct settings to make sure you milk these super valuable insights. You want to make sure that this survey is always displayed, no matter if the user has already seen a survey in the past days:
<MdxImage
src={RecontactOptions}
alt="Set recontact options"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
These settings make sure the survey is always displayed, when a user wants to Cancel their subscription.
### 7. Congrats! Youre ready to publish your survey 💃
<MdxImage
src={PublishSurvey}
alt="Publish survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<Note>
## Formbricks Widget running? You need to have the Formbricks Widget installed to display the Churn Survey
in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
</Note>
###
# Get those insights! 🎉

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Some files were not shown because too many files have changed in this diff Show More