Compare commits
68 Commits
testing/ts
...
v2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deef604325 | ||
|
|
22e44b47e6 | ||
|
|
08052b13cf | ||
|
|
5e7fe96ef6 | ||
|
|
83ff98cd3a | ||
|
|
eb54230445 | ||
|
|
59d57c50eb | ||
|
|
f358254e3c | ||
|
|
3ab8092a82 | ||
|
|
3b08b718ff | ||
|
|
a473719eee | ||
|
|
cd40e655fb | ||
|
|
5d468b4420 | ||
|
|
96806c613f | ||
|
|
6f2a4b2b03 | ||
|
|
d33efa0274 | ||
|
|
9b35ebc114 | ||
|
|
dba62157bf | ||
|
|
556184f442 | ||
|
|
c33532f952 | ||
|
|
8f61ceb4ea | ||
|
|
e19dcdc0c4 | ||
|
|
65f977786d | ||
|
|
95c13fd9a9 | ||
|
|
f8adf28df8 | ||
|
|
9d468379f2 | ||
|
|
8071e82390 | ||
|
|
3ce775ec05 | ||
|
|
1223a30127 | ||
|
|
f3906cab55 | ||
|
|
d33304e3ad | ||
|
|
ebed950392 | ||
|
|
4e6ab1c2bb | ||
|
|
543d85eb28 | ||
|
|
afe01a61ae | ||
|
|
b4c86835ed | ||
|
|
ab80bc1bf2 | ||
|
|
5e5a9fac00 | ||
|
|
09b6805886 | ||
|
|
33822350fd | ||
|
|
8c7b5891a2 | ||
|
|
864b3a34e9 | ||
|
|
a382a18d44 | ||
|
|
70e77247f6 | ||
|
|
915fe00434 | ||
|
|
2e64b0d54f | ||
|
|
ed4b885935 | ||
|
|
5bf5825f30 | ||
|
|
9888d128a2 | ||
|
|
a143e9d1a3 | ||
|
|
4878e8e43a | ||
|
|
b989272bce | ||
|
|
df927555e0 | ||
|
|
5e94d0b4af | ||
|
|
c73d4e82b5 | ||
|
|
a269da4e1c | ||
|
|
b49ca8087a | ||
|
|
7649095c03 | ||
|
|
a8223d2f3c | ||
|
|
53aef9ce0b | ||
|
|
bcceab4858 | ||
|
|
ac4064503a | ||
|
|
d9b115db37 | ||
|
|
a61f294eea | ||
|
|
7b4db30efd | ||
|
|
c8aece8003 | ||
|
|
ad8d473a2d | ||
|
|
55db14e758 |
@@ -22,7 +22,7 @@
|
||||
|
||||
// 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=web... --filter=demo...",
|
||||
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node"
|
||||
|
||||
@@ -35,3 +35,5 @@ yarn-error.log*
|
||||
.vscode
|
||||
.github
|
||||
**/.turbo
|
||||
|
||||
.env
|
||||
|
||||
13
.env.example
@@ -28,13 +28,17 @@ DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=pu
|
||||
###############
|
||||
|
||||
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
|
||||
# You can use: `openssl rand -hex 32` to generate one
|
||||
# You can use: `openssl rand -hex 32` to generate a secure 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
|
||||
|
||||
# Cron Secret
|
||||
# You can use: `openssl rand -hex 32` to generate a secure one
|
||||
CRON_SECRET=
|
||||
|
||||
################
|
||||
# MAIL SETUP #
|
||||
################
|
||||
@@ -83,6 +87,7 @@ 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.
|
||||
@@ -120,9 +125,6 @@ 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=
|
||||
|
||||
@@ -160,9 +162,6 @@ ENTERPRISE_LICENSE_KEY=
|
||||
# DEFAULT_ORGANIZATION_ID=
|
||||
# DEFAULT_ORGANIZATION_ROLE=admin
|
||||
|
||||
# set to 1 to skip onboarding for new users
|
||||
# ONBOARDING_DISABLED=1
|
||||
|
||||
# Send new users to customer.io
|
||||
# CUSTOMER_IO_API_KEY=
|
||||
# CUSTOMER_IO_SITE_ID=
|
||||
|
||||
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
packages/config-eslint/
|
||||
10
.eslintrc.js
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// This tells ESLint to load the config from the package `eslint-config-formbricks`
|
||||
extends: ["formbricks"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/"],
|
||||
},
|
||||
},
|
||||
};
|
||||
16
.github/actions/cache-build-web/action.yml
vendored
@@ -49,20 +49,16 @@ runs:
|
||||
run: cp .env.example .env
|
||||
shell: bash
|
||||
|
||||
- name: Add E2E Testing Mode
|
||||
- name: Fill ENCRYPTION_KE, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env
|
||||
run: |
|
||||
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
echo "ENTERPRISE_LICENSE_KEY=$SECRET" >> $GITHUB_ENV
|
||||
RANDOM_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
|
||||
sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${RANDOM_KEY}/" .env
|
||||
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> .env
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
pnpm build --filter=web...
|
||||
pnpm build --filter=@formbricks/web...
|
||||
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
|
||||
2
.github/workflows/build-web.yml
vendored
@@ -31,4 +31,4 @@ jobs:
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Formbricks-web
|
||||
run: pnpm build --filter=web...
|
||||
run: pnpm build --filter=@formbricks/web...
|
||||
|
||||
24
.github/workflows/cron-reportUsageToStripe.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Cron - Report usage to Stripe
|
||||
|
||||
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:
|
||||
# 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
|
||||
6
.github/workflows/cron-weeklySummary.yml
vendored
@@ -4,9 +4,9 @@ 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 08:00 on Monday.” (see https://crontab.guru)
|
||||
# - cron: "0 8 * * 1"
|
||||
schedule:
|
||||
# Runs “At 08:00 on Monday.” (see https://crontab.guru)
|
||||
- cron: "0 8 * * 1"
|
||||
jobs:
|
||||
cron-weeklySummary:
|
||||
env:
|
||||
|
||||
2
.github/workflows/e2e.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Run App
|
||||
run: |
|
||||
NODE_ENV=test pnpm start --filter=web &
|
||||
NODE_ENV=test pnpm start --filter=@formbricks/web &
|
||||
for attempt in {1..20}; do
|
||||
if [ $(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/health) -eq 200 ]; then
|
||||
echo "Ready"
|
||||
|
||||
7
.github/workflows/kamal-deploy.yml
vendored
@@ -5,9 +5,9 @@ concurrency:
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
#push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
Deploy:
|
||||
@@ -57,7 +57,6 @@ jobs:
|
||||
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
|
||||
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
|
||||
DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_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 }}
|
||||
|
||||
1
.github/workflows/kamal-setup.yml
vendored
@@ -54,7 +54,6 @@ jobs:
|
||||
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
|
||||
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
|
||||
DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_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 }}
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
@@ -25,10 +25,10 @@ jobs:
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
- name: Generate Random ENCRYPTION_KEY and fill in .env
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${ENCRYPTION_KEY}/" .env
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
7
.github/workflows/release-changesets.yml
vendored
@@ -1,9 +1,10 @@
|
||||
name: Release Changesets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
#push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
|
||||
91
.github/workflows/release-docker-github-experimental.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
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}
|
||||
6
.github/workflows/test.yml
vendored
@@ -25,10 +25,10 @@ jobs:
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
- name: Generate Random ENCRYPTION_KEY and fill in .env
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${ENCRYPTION_KEY}/" .env
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const baseConfig = require("./packages/prettier-config/prettier-preset");
|
||||
const baseConfig = require("./packages/config-prettier/prettier-preset");
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ShieldCheckIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import { classNames } from "../lib/utils";
|
||||
|
||||
const navigation = [
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next": "14.2.3",
|
||||
"lucide-react": "^0.395.0",
|
||||
"next": "14.2.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*"
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@formbricks/config-typescript": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
|
||||
import "../styles/globals.css";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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 fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
@@ -59,7 +57,7 @@ const AppPage = ({}) => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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";
|
||||
|
||||
@@ -36,7 +34,7 @@ const AppPage = ({}) => {
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const defaultAttributes = {
|
||||
language: "de",
|
||||
language: "en",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"extends": "@formbricks/config-typescript/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 53 KiB |
@@ -57,12 +57,35 @@ To add a No-Code Action:
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Here are three types of No-Code actions you can set up:
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
### **1. Page URL Action**
|
||||
### **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.
|
||||
@@ -70,15 +93,7 @@ This action is triggered when a user visits a specific page within your applicat
|
||||
- **notMatch**: Triggers when the URL does not match the specified condition.
|
||||
- **notContains**: Activates when the URL does not contain the specified substring.
|
||||
|
||||
### **2. innerText Action**
|
||||
|
||||
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.
|
||||
|
||||
### **3. CSS Selector Action**
|
||||
|
||||
This action 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.
|
||||
|
||||
<Note>You can have an action use combination of the 3 types as you wish</Note>
|
||||
</Note>
|
||||
|
||||
## **Setting Up Code Actions**
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
@@ -22,9 +22,7 @@ export const metadata = {
|
||||
App surveys have 6-10x better conversion rates than emailed out surveys. This tutorial explains how to run an app survey in your web app in 10 to 15 minutes. Let’s 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).
|
||||
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:
|
||||
|
||||
@@ -41,9 +41,7 @@ To run the Churn Survey in your app you want to proceed as follows:
|
||||
4. Prevent that churn!
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running? We assume that you have already installed the Formbricks Widget in your web app. It’s 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
|
||||
@@ -80,9 +78,9 @@ In this case, you don’t really need to pre-segment your audience. You likely w
|
||||
|
||||
### 4. Set up a trigger
|
||||
|
||||
To create the trigger for your Churn Survey, you have two options to choose from:
|
||||
To create the trigger for your Churn Survey, you have three options to choose from:
|
||||
|
||||
1. **Trigger by innerText:** You likely have a “Cancel Subscription” button in your app. You can setup a user Action with the according `innerText` to trigger the survey, like so:
|
||||
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}
|
||||
@@ -100,7 +98,7 @@ To create the trigger for your Churn Survey, you have two options to choose from
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Trigger by pageURL:** 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 `pageURL` with the following settings:
|
||||
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}
|
||||
@@ -111,7 +109,7 @@ To create the trigger for your Churn Survey, you have two options to choose from
|
||||
|
||||
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](/actions/why) covering [Code](/actions/code) and [No-Code](/actions/no-code) Actions.
|
||||
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 We’re currently building full-screen survey pop-ups. You’ll be able to prevent
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 59 KiB |
@@ -38,9 +38,10 @@ To run the Feature Chaser survey in your app you want to proceed as follows:
|
||||
2. Setup a user action to display survey at the right point in time
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web wapp. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web
|
||||
wapp. It’s 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 Feature Chaser
|
||||
@@ -73,11 +74,11 @@ Save, and move over to where the magic happens: The “Audience” tab.
|
||||
|
||||
Before setting the right trigger, you need to identify a user action in your app which signals, that they have just used the feature you want to understand better. In most cases, it is clicking a specific button in your product.
|
||||
|
||||
You can create [Code Actions](/actions/code) and [No Code Actions](/actions/no-code) to follow users through your app. In this example, we will create a No Code Action.
|
||||
You can create [Code Actions](/app-surveys/actions#setting-up-code-actions) and [No Code Actions](/app-surveys/actions#setting-up-no-code-actions) to follow users through your app. In this example, we will create a No Code Action.
|
||||
|
||||
There are two ways to track a button:
|
||||
|
||||
1. **Trigger by innerText:** You might have a button with a unique text at the end of your feature e.g. "Export Report". You can setup a user Action with the according `innerText` to trigger the survey, like so:
|
||||
1. **Trigger by Inner Text:** You might have a button with a unique text at the end of your feature e.g. "Export Report". You can setup a user Action with the according `Inner Text` to trigger the survey, like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionText}
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 57 KiB |
@@ -65,19 +65,18 @@ Change the questions and answer options according to your preference:
|
||||
|
||||
### 3. Create user action to trigger Feedback Box:
|
||||
|
||||
Go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool No-Code User Action Tracker:
|
||||
Go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool User Action Tracker:
|
||||
|
||||
<MdxImage src={AddAction} alt="Add action" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
We have two options to track the Feedback Button in your application: innerText and CSS-Selector:
|
||||
|
||||
1. **innerText:** This means that whenever a user clicks any HTML item in your app which has an `innerText` of `Feedback` the Feedback Box will be displayed.
|
||||
2. **CSS-Selector:** This means that when an element with a specific CSS-Selector like `#feedback-button` is clicked, your Feedback Box is triggered.
|
||||
1. **Inner Text:** This means that whenever a user clicks any HTML item in your app which has an `Inner Text` of `Feedback` the Feedback Box will be displayed.
|
||||
|
||||
<div className="grid max-w-full grid-cols-2 space-x-2 sm:max-w-3xl">
|
||||
<MdxImage src={ActionText} alt="Add HTML action" quality="100" className="rounded-lg" />
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
</div>
|
||||
<MdxImage src={ActionText} alt="Add HTML action" quality="100" className="rounded-lg" />
|
||||
|
||||
2. **CSS Selector:** This means that when an element with a specific CSS-Selector like `#feedback-button` is clicked, your Feedback Box is triggered.
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
|
||||
### 4. Select action in the “When to ask” card
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 64 KiB |
@@ -38,9 +38,8 @@ To display the Trial Conversion Survey in your app you want to proceed as follow
|
||||
3. Print that 💸
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s 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 Trial Conversion Survey
|
||||
@@ -84,7 +83,7 @@ Pre-segmentation isn't relevant for this survey because you likely want to solve
|
||||
|
||||
How you trigger your survey depends on your product. There are two options:
|
||||
|
||||
1. **Trigger by pageURL:** Let’s say you have a page under “/trial-cancelled” where you forward users once they cancelled the trial subscription. You can then create an user Action with the type `pageURL` with the following settings:
|
||||
1. **Trigger by Page view:** Let’s say you have a page under “/trial-cancelled” where you forward users once they cancelled the trial subscription. You can then create an user Action with the type `Page View` and add select `Limit to specific pages` to add url filters, with the following settings:
|
||||
|
||||
<MdxImage
|
||||
src={ActionPageurl}
|
||||
@@ -95,7 +94,7 @@ How you trigger your survey depends on your product. There are two options:
|
||||
|
||||
Whenever a user visits this page, the survey will be displayed ✅
|
||||
|
||||
2. **Trigger by Button Click:** In a different case, you have a “Cancel Trial button in your app. You can setup a user Action with the according `innerText` like so:
|
||||
2. **Trigger by Button Click:** In a different case, you have a “Cancel Trial" button in your app. You can setup a user Action with the according `Inner Text` like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionText}
|
||||
@@ -104,7 +103,7 @@ Whenever a user visits this page, the survey will be displayed ✅
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Please have a look at our complete [Actions manual](/actions/why) if you have questions.
|
||||
Please have a look at our complete [Actions manual](/app-surveys/actions/) if you have questions.
|
||||
|
||||
### 5. Select Action in the “When to ask” card
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 34 KiB |
@@ -43,9 +43,8 @@ To display an Interview Prompt in your app you want to proceed as follows:
|
||||
3. That’s it! 🎉
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(15mins).](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (15mins).](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Interview Prompt
|
||||
@@ -86,9 +85,7 @@ Save, and move over to the “Audience” tab.
|
||||
### 3. Pre-segment your audience (coming soon)
|
||||
|
||||
<Note>
|
||||
## Filter by attribute coming soon.
|
||||
We're working on pre-segmenting users by attributes. We will update this
|
||||
manual in the next few days.
|
||||
## Filter by attribute coming soon. We're working on pre-segmenting users by attributes. We will update this manual in the next few days.
|
||||
</Note>
|
||||
|
||||
Once you clicked over to the “Audience” tab you can change the settings. In the **Who To Send** card, select “Filter audience by attribute”. This allows you to only show the prompt to a specific segment of your user base.
|
||||
@@ -99,13 +96,13 @@ Great, now only the “Power User” segment will see our Interview Prompt. But
|
||||
|
||||
### 4. Set up a trigger for the Interview Prompt:
|
||||
|
||||
To create the trigger to show your Interview Prompt, go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool No-Code User Action Tracker:
|
||||
To create the trigger to show your Interview Prompt, go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool User Action Tracker:
|
||||
|
||||
<MdxImage src={AddAction} alt="Add action" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
Generally, we have two types of user actions: Page views and clicks. The Interview Prompt, you’ll likely want to display it on a page visit since you already filter who sees the prompt by attributes.
|
||||
|
||||
1. **pageURL:** Whenever a user visits a page the survey will be displayed, as long as the other conditions match. Other conditions are pre-segmentation, if this user has seen a survey in the past 2 weeks, etc.
|
||||
1. **Page view:** Whenever a user visits a page the survey will be displayed, as long as the other conditions match. Other conditions are pre-segmentation, if this user has seen a survey in the past 2 weeks, etc.
|
||||
|
||||
<MdxImage
|
||||
src={ActionPageurl}
|
||||
@@ -114,7 +111,7 @@ Generally, we have two types of user actions: Page views and clicks. The Intervi
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **innerText & CSS-Selector:** When a user clicks an element (like a button) with a specific text content or CSS selector, the prompt will be displayed as long as the other conditions also match.
|
||||
2. **Click(Inner Text & CSS Selector):** When a user clicks an element (like a button) with a specific text content or CSS selector, the prompt will be displayed as long as the other conditions also match.
|
||||
|
||||
<div className="grid max-w-full grid-cols-2 space-x-2 sm:max-w-3xl">
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
@@ -146,8 +143,7 @@ Scroll down to “Recontact Options”. Here you have to choose the correct sett
|
||||
<MdxImage src={Publish} 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 Feedback Box in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
## Formbricks Widget running? You need to have the Formbricks Widget installed to display the Feedback Box in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 55 KiB |
@@ -37,9 +37,8 @@ To display the Product-Market Fit survey in your app you want to proceed as foll
|
||||
3. Setup the user action to display survey at good point in time
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(15mins).](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (15mins).](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new PMF survey
|
||||
@@ -91,7 +90,7 @@ This way you make sure that you separate potentially misleading opinions from va
|
||||
|
||||
### 4. Set up a trigger for the Product-Market Fit survey:
|
||||
|
||||
You need a trigger to display the survey but in this case, the filtering does all the work. It’s up to you to decide to display the survey after the user viewed a specific subpage (pageURL) or after clicking an element. Have a look at the [Actions manual](/actions/why) if you are not sure how to set them up:
|
||||
You need a trigger to display the survey but in this case, the filtering does all the work. It’s up to you to decide to display the survey after the user viewed a specific subpage (pageURL) or after clicking an element. Have a look at the [Actions manual](/app-surveys/actions/) if you are not sure how to set them up:
|
||||
|
||||
<Col>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 56 KiB |
@@ -16,7 +16,7 @@ export const metadata = {
|
||||
|
||||
### Overview
|
||||
|
||||
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. It’s available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. It’s available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
|
||||
### Install
|
||||
|
||||
@@ -26,9 +26,11 @@ The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Survey
|
||||
```js {{ title: 'npm' }}
|
||||
npm install @formbricks/js
|
||||
```
|
||||
|
||||
```js {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
```
|
||||
|
||||
```js {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
```
|
||||
@@ -51,9 +53,10 @@ import formbricks from "@formbricks/js/app";
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>", // required
|
||||
apiHost: "<your-api-host>", // required
|
||||
userId: "<user-id>" // required
|
||||
userId: "<user-id>", // required
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -67,15 +70,16 @@ Formbricks JS is a client SDK meant to be run client-side in their browser so ma
|
||||
|
||||
```js
|
||||
if (window !== undefined) {
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>",
|
||||
apiHost: "<your-api-host>",
|
||||
userId: "<user-id>"
|
||||
});
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>",
|
||||
apiHost: "<your-api-host>",
|
||||
userId: "<user-id>",
|
||||
});
|
||||
} else {
|
||||
console.error("Window object not accessible to init Formbricks");
|
||||
console.error("Window object not accessible to init Formbricks");
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -91,6 +95,7 @@ You can set custom attributes for the identified user. This can be helpful for s
|
||||
```js
|
||||
formbricks.setAttribute("Plan", "Paid");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -104,10 +109,10 @@ Track user actions to trigger surveys based on user interactions, such as button
|
||||
```js
|
||||
formbricks.track("Clicked on Claim");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
|
||||
### Logout
|
||||
|
||||
To log out and deinitialize Formbricks, use the formbricks.logout() function. This action clears the current initialization configuration and erases stored frontend information, such as the surveys a user has viewed or completed. It's an important step when a user logs out of your application or when you want to reset Formbricks.
|
||||
@@ -118,6 +123,7 @@ To log out and deinitialize Formbricks, use the formbricks.logout() function. Th
|
||||
```js
|
||||
formbricks.logout();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -133,6 +139,7 @@ Reset the current instance and fetch the latest surveys and state again:
|
||||
```js
|
||||
formbricks.reset();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -140,7 +147,11 @@ formbricks.reset();
|
||||
|
||||
Listen for page changes and dynamically show surveys configured via no-code actions in the Formbricks app:
|
||||
|
||||
<Note> This is only needed when your framework has a custom routing system and you want to trigger surveys on route changes. For example: NextJs</Note>
|
||||
<Note>
|
||||
{" "}
|
||||
This is only needed when your framework has a custom routing system and you want to trigger surveys on route
|
||||
changes. For example: NextJs
|
||||
</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup>
|
||||
@@ -148,6 +159,7 @@ Listen for page changes and dynamically show surveys configured via no-code acti
|
||||
```js
|
||||
formbricks.registerRouteChange();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -167,7 +179,7 @@ In case you don’t see your survey right away, here's what you can do. Go throu
|
||||
|
||||
### Formbricks Cloud and your app are not connected properly.
|
||||
|
||||
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the Setup Checklist in the Settings. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
|
||||
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the App connection in the Configuration. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui/Accordion";
|
||||
|
||||
import { FaqJsonLdComponent } from "./FAQPageJsonLd";
|
||||
|
||||
const FAQ_DATA = [
|
||||
|
||||
@@ -43,7 +43,6 @@ To be able to keep working on Formbricks over the coming years, we need to colle
|
||||
|
||||
Once you open a PR, you will get a message from the CLA bot to fill out the form. Please note that we can only get your contribution merged when we have a CLA signed by you.
|
||||
|
||||
|
||||
## Setup Dev Environment
|
||||
|
||||
We currently officially support the below methods to set up your development environment for Formbricks.
|
||||
@@ -425,7 +424,7 @@ This usually happens when the Formbricks Widget wasn't correctly or completely b
|
||||
<CodeGroup title="Build js library first and then run again">
|
||||
|
||||
```bash
|
||||
pnpm build --filter=js
|
||||
pnpm build --filter=@formbricks/js
|
||||
|
||||
// Run the app again
|
||||
pnpm dev
|
||||
@@ -441,15 +440,15 @@ Since we're working with a monorepo structure, the repository can get quite big.
|
||||
<CodeGroup title="Only run the required project">
|
||||
|
||||
```bash {{ title: 'Formbricks Web-App' }}
|
||||
pnpm dev --filter=web...
|
||||
pnpm dev --filter=@formbricks/web...
|
||||
```
|
||||
|
||||
```bash {{ title: 'Formbricks Landing Page' }}
|
||||
pnpm dev --filter=formbricks-com...
|
||||
```bash {{ title: 'Formbricks Docs' }}
|
||||
pnpm dev --filter=@formbricks/docs...
|
||||
```
|
||||
|
||||
```bash {{ title: 'Formbricks Demo App' }}
|
||||
pnpm dev --filter=demo...
|
||||
pnpm dev --filter=@formbricks/demo...
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -468,4 +467,3 @@ However, in our experience it's better to run `pnpm dev` than having two termina
|
||||
This happens when you're using the Demo App and delete the Person within the Formbricks app which the widget is currently connected with. We're fixing it, but you can also just logout your test person and reload the page to get rid of it.
|
||||
|
||||
<MdxImage src={Logout} alt="Logout Person" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ export const metadata = {
|
||||
The slack integration allows you to automatically send responses to a Slack channel of your choice.
|
||||
|
||||
<Note>
|
||||
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
|
||||
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow
|
||||
the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
|
||||
</Note>
|
||||
|
||||
## Formbricks Cloud
|
||||
|
||||
@@ -67,8 +67,7 @@ The API requests are authorized with a personal API key. This API key gives you
|
||||
/>
|
||||
|
||||
<Note>
|
||||
### Store API key safely!
|
||||
Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
|
||||
### Store API key safely! Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
|
||||
</Note>
|
||||
|
||||
### Test your API Key
|
||||
@@ -123,7 +122,8 @@ Hit the below request to verify that you are authenticated with your API Key and
|
||||
"id": "cll2m30r60003mx0hnemjfckr",
|
||||
"name": "My Product"
|
||||
},
|
||||
"widgetSetupCompleted": false
|
||||
"appSetupCompleted": false,
|
||||
"websiteSetupCompleted": false,
|
||||
}
|
||||
```
|
||||
```json {{ title: '401 Not Authenticated' }}
|
||||
|
||||
|
After Width: | Height: | Size: 59 KiB |
@@ -1,4 +1,5 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
import I1 from "./images/1-set-up-website-micro-survey-popup.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Website Survey SDK",
|
||||
@@ -142,4 +143,25 @@ This activates detailed debug messages in the browser console, providing deeper
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
In case you don’t see your survey right away, here's what you can do. Go through these to find the error fast:
|
||||
|
||||
### Formbricks Cloud and your website are not connected properly.
|
||||
|
||||
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the Website connection in the Configuration. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="setup checklist ui of survey popup for website surveys"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
**How to fix it:**
|
||||
|
||||
1. Check if your website loads the Formbricks widget correctly.
|
||||
2. Make sure you have `debug` mode enabled in your integration and you should see the Formbricks debug logs in your browser console while being in your app (right click in the browser, `Inspect`, switch to the console tab). If you don’t see them, double check your integration.
|
||||
|
||||
---
|
||||
|
||||
If you have any questions or need help, feel free to reach out to us on our **[Discord](https://formbricks.com/discord)**
|
||||
|
||||
BIN
apps/docs/app/global/access-roles/images/team-settings-menu.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
@@ -67,8 +67,7 @@ The API requests are authorized with a personal API key. This API key gives you
|
||||
/>
|
||||
|
||||
<Note>
|
||||
### Store API key safely! Anyone who has your API key has full control over your account. For security
|
||||
reasons, you cannot view the API key again.
|
||||
### Store API key safely! Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
|
||||
</Note>
|
||||
|
||||
### Test your API Key
|
||||
@@ -123,7 +122,8 @@ Hit the below request to verify that you are authenticated with your API Key and
|
||||
"id": "cll2m30r60003mx0hnemjfckr",
|
||||
"name": "My Product"
|
||||
},
|
||||
"widgetSetupCompleted": false
|
||||
"appSetupCompleted": false,
|
||||
"websiteSetupCompleted": false,
|
||||
}
|
||||
```
|
||||
```json {{ title: '401 Not Authenticated' }}
|
||||
|
||||
@@ -49,7 +49,7 @@ How to deliver a specific language depends on the survey type (app or link surve
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Click on the **Edit Languages** button, to add a new language to your survey
|
||||
2. Click on the **Edit languages** button, to add a new language to your survey
|
||||
|
||||
<MdxImage
|
||||
src={SurveyLanguageSettings}
|
||||
@@ -58,7 +58,7 @@ How to deliver a specific language depends on the survey type (app or link surve
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. Select the preferred language from the dropdown and assign an identifier Alias. Click the **Add Language** button to add the language to your product.
|
||||
3. Select the preferred language from the dropdown and assign an identifier Alias. Click the **Add language** button to add the language to your product.
|
||||
|
||||
<MdxImage
|
||||
src={AddLanguages}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -43,7 +43,7 @@ Set up this feature to control how many users see your survey, using a simple sl
|
||||
4. **Adjust User Visibility Percentage**:
|
||||
|
||||
- Find the **`Show Survey to % of Users`** toggle. Enable it.
|
||||
- Use the slider to select the desired percentage (from 1% to 100%) of users to whom the survey will be shown.
|
||||
- Enter the desired percentage (from 0.01% to 100%) of users to whom the survey will be shown.
|
||||
|
||||
{" "}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 57 KiB |
@@ -28,7 +28,6 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| PRIVACY_URL | URL for privacy policy. | optional | |
|
||||
| TERMS_URL | URL for terms of service. | optional | |
|
||||
| IMPRINT_URL | URL for imprint. | optional | |
|
||||
| SIGNUP_DISABLED | Disables the ability for new users to create an account if set to 1. | optional | |
|
||||
| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to 1. | optional | |
|
||||
| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to 1. | optional | |
|
||||
| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to 1. | optional | |
|
||||
@@ -53,7 +52,6 @@ These variables are present inside your machine’s docker-compose file. Restart
|
||||
| DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | #64748b |
|
||||
| DEFAULT_ORGANIZATION_ID | Automatically assign new users to a specific organization when joining | optional | |
|
||||
| DEFAULT_ORGANIZATION_ROLE | Role of the user in the default organization. | optional | admin |
|
||||
| ONBOARDING_DISABLED | Disables onboarding for new users if set to 1 | optional | |
|
||||
| OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | |
|
||||
| OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
|
||||
| OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
|
||||
@@ -176,10 +174,10 @@ An example configuration for a FusionAuth OpenID Connect in Formbricks would loo
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Formbricks Env for FusionAuth OIDC">
|
||||
```yml {{ title: ".env" }}
|
||||
```yml {{ title: ".env" }}
|
||||
OIDC_CLIENT_ID=59cada54-56d4-4aa8-a5e7-5823bbe0e5b7 OIDC_CLIENT_SECRET=4f4dwP0ZoOAqMW8fM9290A7uIS3E8Xg29xe1umhlB_s
|
||||
OIDC_ISSUER=http://localhost:9011 OIDC_DISPLAY_NAME=FusionAuth OIDC_SIGNING_ALGORITHM=HS256
|
||||
```
|
||||
OIDC_ISSUER=http://localhost:9011 OIDC_DISPLAY_NAME=FusionAuth OIDC_SIGNING_ALGORITHM=HS256
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
|
||||
@@ -8,6 +8,207 @@ export const metadata = {
|
||||
|
||||
# Migration Guide
|
||||
|
||||
## v2.2
|
||||
|
||||
Formbricks v2.2 introduces XM research presets into your products with a brand new product onboarding. Our objective is to make user research “obviously easy”, industry by industry. And we're starting with Software-as-a-Service and E-Commerce.
|
||||
|
||||
### Steps to Migrate
|
||||
|
||||
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly.
|
||||
|
||||
To run all these steps, please navigate to the `formbricks` folder where your `docker-compose.yml` file is located.
|
||||
|
||||
1. **Backup your Database**: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Backup Postgres">
|
||||
|
||||
```bash
|
||||
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.2_$(date +%Y%m%d_%H%M%S).dump
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
<Note>
|
||||
If you run into “No such container”, use `docker ps` to find your container name, e.g.
|
||||
`formbricks_postgres_1`.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
If you prefer storing the backup as an `*.sql` file remove the `-Fc` (custom format) option. In case of a
|
||||
restore scenario you will need to use `psql` then with an empty `formbricks` database.
|
||||
</Note>
|
||||
|
||||
2. Pull the latest version of Formbricks:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
3. Stop the running Formbricks instance & remove the related containers:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
4. Restarting the containers with the latest version of Formbricks:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Restart the containers">
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
5. Now let's migrate the data to the latest schema:
|
||||
|
||||
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.2" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
|
||||
|
||||
6. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
|
||||
|
||||
### Changes in Environment Variables
|
||||
|
||||
- `ONBOARDING_DISABLED` is now deprecated since we replaced the user onboarding with a product onboarding that only runs when creating a new product.
|
||||
|
||||
## v2.1
|
||||
|
||||
Formbricks v2.1 introduces more options for creating No-Code Actions and lays the foundation for easier self-hosting of Formbricks starting with an Onboarding for fresh instances.
|
||||
|
||||
<Note>
|
||||
To improve the user experience in self-hosting instances and to simplify setup, we are moving to a single
|
||||
organization approach for self-hosting instances with this release. This will allow self-hosters to
|
||||
centrally manage their instance and more easily restrict access to the instance. We will soon introduce a
|
||||
new permissions system that will allow more granular access to projects and other resources within an
|
||||
organization. If you have created multiple organizations in the past, you will still be able to switch
|
||||
between them in the UI, but don't have an option to create new organizations.
|
||||
</Note>
|
||||
|
||||
### Steps to Migrate
|
||||
|
||||
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly.
|
||||
|
||||
To run all these steps, please navigate to the `formbricks` folder where your `docker-compose.yml` file is located.
|
||||
|
||||
1. **Backup your Database**: This is a crucial step. Please make sure to backup your database before proceeding with the upgrade. You can use the following command to backup your database:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Backup Postgres">
|
||||
|
||||
```bash
|
||||
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.1_$(date +%Y%m%d_%H%M%S).dump
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
<Note>
|
||||
If you run into “No such container”, use `docker ps` to find your container name, e.g.
|
||||
`formbricks_postgres_1`.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
If you prefer storing the backup as an `*.sql` file remove the `-Fc` (custom format) option. In case of a
|
||||
restore scenario you will need to use `psql` then with an empty `formbricks` database.
|
||||
</Note>
|
||||
|
||||
2. Pull the latest version of Formbricks:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
3. Stop the running Formbricks instance & remove the related containers:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
4. Restarting the containers with the latest version of Formbricks:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Restart the containers">
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
5. Now let's migrate the data to the latest schema:
|
||||
|
||||
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Migrate the data">
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/formbricks/data-migrations:latest && \
|
||||
docker run --rm \
|
||||
--network=formbricks_default \
|
||||
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
|
||||
-e UPGRADE_TO_VERSION="v2.1" \
|
||||
ghcr.io/formbricks/data-migrations:latest
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
|
||||
|
||||
6. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
|
||||
|
||||
### Changes in Environment Variables
|
||||
|
||||
- `SIGNUP_DISABLED` is now deprecated since self-hosting instaces have signup disabled by default and new users can only be invited by the organization owner or admin.
|
||||
- `DEFAULT_TEAM_ID` got renamed to `DEFAULT_ORGANIZATION_ID`
|
||||
- `DEFAULT_TEAM_ROLE` got renamed to `DEFAULT_ORGANIZATION_ROLE`
|
||||
|
||||
## v2.0
|
||||
|
||||
Formbricks v2.0 comes with huge features such as Multi-Language Surveys and Advanced Styling for Surveys. We have also shipped various optimisations, bug fixes & smaller fixes on the way to make your experience more seamless. This guide will help you migrate your existing Formbricks instance to v2.0 without any hassles or build errors.
|
||||
@@ -63,7 +264,7 @@ docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbr
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker-compose pull
|
||||
docker compose pull
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -75,7 +276,7 @@ docker-compose pull
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker compose down
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -87,7 +288,7 @@ docker-compose down
|
||||
<CodeGroup title="Restart the containers">
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -219,7 +420,7 @@ docker exec formbricks-quickstart-postgres-1 pg_dump -Fc -U postgres -d formbric
|
||||
<CodeGroup title="Stop the containers">
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker compose down
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -231,7 +432,7 @@ docker-compose down
|
||||
<CodeGroup title="Restart the containers">
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 53 KiB |
@@ -57,26 +57,44 @@ Formbricks provides an intuitive No-Code interface for configuring actions, enab
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
Here are three types of No-Code actions you can set up:
|
||||
|
||||
### **1. Page URL Action**
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
Triggers a survey when a specific URL page condition is met. Setup options include:
|
||||
### **1. Click Action**
|
||||
|
||||
- **exactMatch**: Triggers the survey when the URL exactly matches the specified string.
|
||||
- **contains**: Triggers the survey when the URL contains a specified substring.
|
||||
- **startsWith**: Triggers the survey when the URL starts with a specified string.
|
||||
- **endsWith**: Triggers the survey when the URL ends with a specified string.
|
||||
- **notMatch**: Triggers the survey when the URL does not match a specified condition.
|
||||
- **notContains**: Triggers the survey when the URL does not contain a specified substring.
|
||||
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.
|
||||
|
||||
### **2. innerText Action**
|
||||
- **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.
|
||||
|
||||
Activated when the innerText of a clicked HTML element, such as a button or link, matches specified text. This is useful for triggering surveys related to specific content interactions.
|
||||
- **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.
|
||||
|
||||
### **3. CSS Selector Action**
|
||||
### **2. Page view Action**
|
||||
|
||||
Triggers a survey when a clicked HTML element matches a specified CSS selector, such as a class, ID, or other CSS selector. This allows for precise control over survey triggers based on site structure and design.
|
||||
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**
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
@@ -22,9 +22,7 @@ export const metadata = {
|
||||
Website Surveys make it easy for your public website visitors to give you feedback. They are a great way to get feedback from your users, without interrupting their workflow. This quickstart guide will show you how to create your first website survey in under 5 minutes.
|
||||
|
||||
<Note>
|
||||
Website Surveys are ideal for **public facing websites**. If you are looking to run surveys in your app
|
||||
where you have user identification & want advanced user targeting, head over to the [App Surveys Quickstart
|
||||
Guide](/app-surveys/quickstart).
|
||||
Website Surveys are ideal for **public facing websites**. If you are looking to run surveys in your app where you have user identification & want advanced user targeting, head over to the [App Surveys Quickstart Guide](/app-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:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { navigation } from "@/lib/navigation";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { DiscordIcon } from "./icons/DiscordIcon";
|
||||
import { GithubIcon } from "./icons/GithubIcon";
|
||||
@@ -116,7 +115,7 @@ const SmallPrint = () => {
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<footer className="mx-auto w-full max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
|
||||
<footer className="my-10 flex-auto pb-16">
|
||||
<PageNavigation />
|
||||
<SmallPrint />
|
||||
</footer>
|
||||
|
||||
@@ -6,7 +6,6 @@ import clsx from "clsx";
|
||||
import { motion, useScroll, useTransform } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { MobileNavigation, useIsInsideMobileNavigation, useMobileNavigationStore } from "./MobileNavigation";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
|
||||
@@ -44,7 +44,7 @@ const Anchor = ({ id, inView, children }: { id: string; inView: boolean; childre
|
||||
);
|
||||
};
|
||||
|
||||
export const Heading = <Level extends 2 | 3>({
|
||||
export const Heading = <Level extends 2 | 3 | 4>({
|
||||
children,
|
||||
tag,
|
||||
label,
|
||||
@@ -59,11 +59,11 @@ export const Heading = <Level extends 2 | 3>({
|
||||
anchor?: boolean;
|
||||
}) => {
|
||||
level = level ?? (2 as Level);
|
||||
let Component = `h${level}` as "h2" | "h3";
|
||||
let ref = useRef<HTMLHeadingElement>(null);
|
||||
let registerHeading = useSectionStore((s) => s.registerHeading);
|
||||
const Component: "h2" | "h3" | "h4" = `h${level}`;
|
||||
const ref = useRef<HTMLHeadingElement>(null);
|
||||
const registerHeading = useSectionStore((s) => s.registerHeading);
|
||||
|
||||
let inView = useInView(ref, {
|
||||
const inView = useInView(ref, {
|
||||
margin: `${remToPx(-3.5)}px 0px 0px 0px`,
|
||||
amount: "all",
|
||||
});
|
||||
@@ -71,8 +71,12 @@ export const Heading = <Level extends 2 | 3>({
|
||||
useEffect(() => {
|
||||
if (level === 2) {
|
||||
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 8 : 6 });
|
||||
} else if (level === 3) {
|
||||
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 7 : 5 });
|
||||
} else if (level === 4) {
|
||||
registerHeading({ id: props.id, ref, offsetRem: tag || label ? 6 : 4 });
|
||||
}
|
||||
});
|
||||
}, [label, level, props.id, registerHeading, tag]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { Navigation } from "@/components/Navigation";
|
||||
import { SideNavigation } from "@/components/SideNavigation";
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { Footer } from "./Footer";
|
||||
import { Header } from "./Header";
|
||||
import { type Section, SectionProvider } from "./SectionProvider";
|
||||
@@ -17,7 +17,7 @@ export const Layout = ({
|
||||
children: React.ReactNode;
|
||||
allSections: Record<string, Array<Section>>;
|
||||
}) => {
|
||||
let pathname = usePathname();
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<SectionProvider sections={allSections[pathname || ""] ?? []}>
|
||||
@@ -35,9 +35,12 @@ export const Layout = ({
|
||||
<Navigation className="hidden lg:mt-10 lg:block" isMobile={false} />
|
||||
</div>
|
||||
</motion.header>
|
||||
<div className="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8">
|
||||
<main className="flex-auto">{children}</main>
|
||||
<Footer />
|
||||
<div className="flex h-screen flex-col">
|
||||
<div className="flex flex-col px-4 pt-14 sm:px-6 lg:w-[calc(100%-20rem)] lg:px-8">
|
||||
<main className="overflow-y-auto overflow-x-hidden">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
<SideNavigation pathname={pathname} />
|
||||
</div>
|
||||
</div>
|
||||
</SectionProvider>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import Image, { ImageProps } from "next/image";
|
||||
import React from "react";
|
||||
|
||||
export const MdxImage = (props: ImageProps) => {
|
||||
return <Image {...props} alt={props.alt} />;
|
||||
return (
|
||||
<Image
|
||||
{...props}
|
||||
alt={props.alt}
|
||||
sizes="100vw"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { useIsInsideMobileNavigation } from "./MobileNavigation";
|
||||
import { useSectionStore } from "./SectionProvider";
|
||||
|
||||
82
apps/docs/components/SideNavigation.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useTableContentObserver } from "@/hooks/useTableContentObserver";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Heading = {
|
||||
id: string;
|
||||
text: string | null;
|
||||
level: number;
|
||||
};
|
||||
|
||||
export const SideNavigation = ({ pathname }) => {
|
||||
const [headings, setHeadings] = useState<Heading[]>([]);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
useTableContentObserver(setSelectedId, pathname);
|
||||
|
||||
useEffect(() => {
|
||||
const getHeadings = () => {
|
||||
// Select all heading elements (h2, h3, h4) with an 'id' attribute
|
||||
const headingElements = document.querySelectorAll("h2[id], h3[id], h4[id]");
|
||||
// Convert the NodeList of heading elements into an array and map them to an array of 'Heading' objects
|
||||
const headings: Heading[] = Array.from(headingElements).map((heading) => ({
|
||||
id: heading.id,
|
||||
text: heading.textContent,
|
||||
level: parseInt(heading.tagName.slice(1)),
|
||||
}));
|
||||
|
||||
// Check if there are any h2 headings in the list
|
||||
const hasH2 = headings.some((heading) => heading.level === 2);
|
||||
|
||||
// Update the 'headings' state with the retrieved headings, but only if there are h2 headings
|
||||
setHeadings(hasH2 ? headings : []);
|
||||
};
|
||||
|
||||
getHeadings();
|
||||
}, [pathname]);
|
||||
|
||||
const renderHeading = (items: Heading[], currentLevel: number) => (
|
||||
<ul className="ml-1 mt-4">
|
||||
{items.map((heading, index) => {
|
||||
if (heading.level === currentLevel) {
|
||||
let nextIndex = index + 1;
|
||||
while (nextIndex < items.length && items[nextIndex].level > currentLevel) {
|
||||
nextIndex++;
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
key={heading.text}
|
||||
className={`mb-4 ml-4 text-slate-900 dark:text-white ml-${heading.level === 2 ? 0 : heading.level === 3 ? 4 : 6}`}>
|
||||
<Link
|
||||
href={`#${heading.id}`}
|
||||
onClick={() => setSelectedId(heading.id)}
|
||||
className={`${
|
||||
heading.id === selectedId
|
||||
? "text-brand font-medium text-opacity-35"
|
||||
: "font-normal text-slate-600 hover:text-slate-950 dark:text-white dark:hover:text-slate-50"
|
||||
}`}>
|
||||
{heading.text}
|
||||
</Link>
|
||||
{nextIndex > index + 1 && renderHeading(items.slice(index + 1, nextIndex), currentLevel + 1)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
|
||||
if (headings.length) {
|
||||
return (
|
||||
<aside className="fixed right-0 top-0 hidden h-[calc(100%-2.5rem)] w-80 overflow-hidden overflow-y-auto pr-8 pt-16 text-sm [scrollbar-width:none] lg:mt-10 lg:block">
|
||||
<div className="border-l border-slate-200 dark:border-slate-700">
|
||||
<h3 className="ml-5 mt-1 text-xs font-semibold uppercase text-slate-400">on this page</h3>
|
||||
{renderHeading(headings, 2)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -19,10 +19,19 @@ export const wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const h2 = (props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) => {
|
||||
return <Heading level={2} {...props} />;
|
||||
const createHeadingComponent = (level: 2 | 3 | 4) => {
|
||||
const Component = (props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) => {
|
||||
return <Heading level={level} {...props} />;
|
||||
};
|
||||
|
||||
Component.displayName = `H${level}`;
|
||||
return Component;
|
||||
};
|
||||
|
||||
export const h2 = createHeadingComponent(2);
|
||||
export const h3 = createHeadingComponent(3);
|
||||
export const h4 = createHeadingComponent(4);
|
||||
|
||||
const InfoIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" {...props}>
|
||||
|
||||
71
apps/docs/hooks/useTableContentObserver.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
interface HeadingElement extends IntersectionObserverEntry {
|
||||
target: HTMLHeadingElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom hook that sets up an IntersectionObserver to track the visibility of headings on the page.
|
||||
*
|
||||
* @param {Function} setActiveId - A function to set the active heading ID.
|
||||
* @param {string} pathname - The current pathname, used as a dependency for the useEffect hook.
|
||||
* @returns {void}
|
||||
*
|
||||
* This hook performs the following tasks:
|
||||
* 1. Creates a map of heading elements, where the key is the heading's ID and the value is the heading element.
|
||||
* 2. Finds the visible headings (i.e., headings that are currently intersecting with the viewport).
|
||||
* 3. If there is only one visible heading, sets it as the active heading using the `setActiveId` function.
|
||||
* 4. If there are multiple visible headings, sets the active heading to the one that is highest on the page (i.e., the one with the lowest index in the `headingElements` array).
|
||||
* 5. Cleans up the IntersectionObserver and the `headingElementsRef` when the component is unmounted.
|
||||
*/
|
||||
export const useTableContentObserver = (setActiveId: (id: string) => void, pathname: string) => {
|
||||
const headingElementsRef = useRef<Record<string, HeadingElement>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const callback = (headings: HeadingElement[]) => {
|
||||
// Create a map of heading elements, where the key is the heading's ID and the value is the heading element
|
||||
headingElementsRef.current = headings.reduce(
|
||||
(map, headingElement) => {
|
||||
return { ...map, [headingElement.target.id]: headingElement };
|
||||
},
|
||||
{} as Record<string, HeadingElement>
|
||||
);
|
||||
|
||||
// Find the visible headings (i.e., headings that are currently intersecting with the viewport)
|
||||
const visibleHeadings: HeadingElement[] = [];
|
||||
Object.keys(headingElementsRef.current).forEach((key) => {
|
||||
const headingElement = headingElementsRef.current[key];
|
||||
if (headingElement.isIntersecting) visibleHeadings.push(headingElement);
|
||||
});
|
||||
|
||||
// Define a function to get the index of a heading element in the headingElements array
|
||||
const getIndexFromId = (id: string) => headingElements.findIndex((heading) => heading.id === id);
|
||||
|
||||
// If there is only one visible heading, set it as the active heading
|
||||
if (visibleHeadings.length === 1) {
|
||||
setActiveId(visibleHeadings[0].target.id);
|
||||
}
|
||||
// If there are multiple visible headings, set the active heading to the one that is highest on the page
|
||||
else if (visibleHeadings.length > 1) {
|
||||
const sortedVisibleHeadings = visibleHeadings.sort((a, b) => {
|
||||
const aIndex = getIndexFromId(a.target.id);
|
||||
const bIndex = getIndexFromId(b.target.id);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
setActiveId(sortedVisibleHeadings[0].target.id);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
rootMargin: "-40px 0px -40% 0px",
|
||||
});
|
||||
|
||||
const headingElements = Array.from(document.querySelectorAll("h2[id], h3[id], h4[id]"));
|
||||
headingElements.forEach((element) => observer.observe(element));
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
headingElementsRef.current = {};
|
||||
};
|
||||
}, [setActiveId, pathname]);
|
||||
};
|
||||
@@ -46,9 +46,9 @@ const rehypeShiki = () => {
|
||||
|
||||
const rehypeSlugify = () => {
|
||||
return (tree) => {
|
||||
let slugify = slugifyWithCounter();
|
||||
const slugify = slugifyWithCounter();
|
||||
visit(tree, "element", (node) => {
|
||||
if (node.tagName === "h2" && !node.properties.id) {
|
||||
if (["h2", "h3", "h4"].includes(node.tagName) && !node.properties.id) {
|
||||
node.properties.id = slugify(toString(node));
|
||||
}
|
||||
});
|
||||
@@ -83,15 +83,15 @@ const rehypeAddMDXExports = (getExports) => {
|
||||
};
|
||||
|
||||
const getSections = (node) => {
|
||||
let sections = [];
|
||||
const sections = [];
|
||||
|
||||
for (let child of node.children ?? []) {
|
||||
if (child.type === "element" && child.tagName === "h2") {
|
||||
for (const child of node.children ?? []) {
|
||||
if (child.type === "element" && ["h2", "h3", "h4"].includes(child.tagName)) {
|
||||
sections.push(`{
|
||||
title: ${JSON.stringify(toString(child))},
|
||||
id: ${JSON.stringify(child.properties.id)},
|
||||
...${child.properties.annotation}
|
||||
}`);
|
||||
title: ${JSON.stringify(toString(child))},
|
||||
id: ${JSON.stringify(child.properties.id)},
|
||||
...${child.properties.annotation}
|
||||
}`);
|
||||
} else if (child.children) {
|
||||
sections.push(...getSections(child));
|
||||
}
|
||||
|
||||
@@ -12,34 +12,34 @@
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"@algolia/autocomplete-core": "^1.17.0",
|
||||
"@calcom/embed-react": "^1.4.0",
|
||||
"@algolia/autocomplete-core": "^1.17.2",
|
||||
"@calcom/embed-react": "^1.5.0",
|
||||
"@docsearch/css": "3",
|
||||
"@docsearch/react": "^3.6.0",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "^2.0.3",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@headlessui/react": "^2.0.4",
|
||||
"@headlessui/tailwindcss": "^0.2.1",
|
||||
"@mapbox/rehype-prism": "^0.9.0",
|
||||
"@mdx-js/loader": "^3.0.1",
|
||||
"@mdx-js/react": "^3.0.1",
|
||||
"@next/mdx": "14.2.3",
|
||||
"@next/mdx": "14.2.4",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"acorn": "^8.11.3",
|
||||
"acorn": "^8.12.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"clsx": "^2.1.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"flexsearch": "^0.7.43",
|
||||
"framer-motion": "11.1.9",
|
||||
"framer-motion": "11.2.11",
|
||||
"lottie-web": "^5.12.2",
|
||||
"lucide": "^0.378.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"lucide": "^0.395.0",
|
||||
"lucide-react": "^0.395.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"mdx-annotations": "^0.1.4",
|
||||
"next": "14.2.3",
|
||||
"next": "14.2.4",
|
||||
"next-plausible": "^3.12.0",
|
||||
"next-seo": "^6.5.0",
|
||||
"next-sitemap": "^4.2.3",
|
||||
@@ -59,15 +59,15 @@
|
||||
"sharp": "^0.33.4",
|
||||
"shiki": "^0.14.7",
|
||||
"simple-functional-loader": "^1.2.1",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"unist-util-filter": "^5.0.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/react-highlight-words": "^0.16.7",
|
||||
"eslint-config-formbricks": "workspace:*"
|
||||
"@types/react-highlight-words": "^0.20.0",
|
||||
"@formbricks/eslint-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import forms from "@tailwindcss/forms";
|
||||
import typographyPlugin from "@tailwindcss/typography";
|
||||
import { type Config } from "tailwindcss";
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
|
||||
import typographyStyles from "./typography";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"extends": "@formbricks/config-typescript/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../../packages/types/*.d.ts"],
|
||||
"exclude": ["../../.env", "node_modules"],
|
||||
"compilerOptions": {
|
||||
|
||||
6
apps/formbricks-com/next-env.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@@ -1,118 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||
<url><loc>https://formbricks.com</loc><lastmod>2024-04-08T07:33:12.606Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/author/johannes</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/author/shubham</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/author/sudhanshu</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/best-feedback-app-and-how-to-use-them</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/best-hotjar-alternatives-2024-incl-open-source</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/best-open-source-survey-software-2023</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/best-website-feedback-tools-2024</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/experience-management-open-source</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/first-end-to-endcommunity-feature</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/github-accelerator-experience</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/granular-targeting-in-app-surveys</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/how-smart-writers-use-formbricks-open-source-tool-to-measure-the-quality-of-their-newsletter-content</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/inaugural-batch-github-accelerator</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/join-the-formtribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/open-source-forms-will-save-the-world</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/open-source-qualtrics-beats-typeform</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/remove-branding-for-free</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/snoopforms-becomes-formbricks</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/terraform-ecs-aws-self-hosting</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/the-perfect-waitlist-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/understanding-formbricks-self-hosting</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/blog/why-open-source-no-code-is-the-future-of-enterprise-gov-software</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/careers</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/ContributorGrid</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/HallOfFame</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/HeaderTribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/LayoutTribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/LevelCard</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/LevelGrid</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/community/Roadmap</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/concierge</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs-feedback</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/feature-chaser</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/feedback-box</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/gdpr</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/gdpr-guide</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/imprint</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/improve-trial-conversion</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/in-app-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/interview-prompt</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/learn-from-churn</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/measure-product-market-fit</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/onboarding-segmentation</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/open-source-form-builder</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/oss-friends</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/pricing</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/privacy-policy</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/terms</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/vs-formspree</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/vs-google-forms</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/vs-ohmyform</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/website-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/client/responses</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/action-classes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/attribute-classes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/me</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/people</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/client/overview</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/responses</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/webhooks</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/client/displays</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/cancel-subscription</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/client/people</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/feedback-box</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/feature-chaser</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/docs-feedback</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/improve-trial-cr</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/api-key-setup</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/improve-email-content</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/pmf-survey</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/best-practices/interview-prompt</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/demo</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/how-we-code</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/introduction</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/creating-a-service</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/setup</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/additional-features/multi-language-surveys</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/contributing/troubleshooting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/getting-started/quickstart-in-app-survey</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/faq</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/in-app-surveys/actions</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/client/actions</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/getting-started/framework-guides</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/in-app-surveys/advanced-targeting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/in-app-surveys/user-identification</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/getting-started/troubleshooting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/in-app-surveys/attributes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/api/management/surveys</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/make</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/airtable</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/notion</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/zapier</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/data-prefilling</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/wordpress</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/what-is-formbricks</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/how-it-works</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/introduction/why-is-it-better</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/single-use-links</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/hidden-fields</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/quickstart</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/source-tracking</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/docker</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/deployment</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/start-at-question</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/link-surveys/user-identification</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/enterprise</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/external-auth-providers</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/migration-guide</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/production</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/self-hosting/license</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/n8n</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://formbricks.com/docs/integrations/google-sheets</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap><loc>https://formbricks.com/sitemap-0.xml</loc></sitemap>
|
||||
</sitemapindex>
|
||||
@@ -1,14 +1,16 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["react-refresh"],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
2
apps/storybook/.gitignore
vendored
@@ -24,3 +24,5 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
storybook-static
|
||||
|
||||
*storybook.log
|
||||
@@ -5,23 +5,26 @@ import { dirname, join } from "path";
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
|
||||
*/
|
||||
const getAbsolutePath = (value: string): any => {
|
||||
const getAbsolutePath = (value: string) => {
|
||||
return dirname(require.resolve(join(value, "package.json")));
|
||||
};
|
||||
|
||||
export const config: StorybookConfig = {
|
||||
stories: ["../../../packages/ui/**/stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
const config: StorybookConfig = {
|
||||
stories: [
|
||||
"../src/**/*.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
|
||||
"../../../packages/ui/**/stories.@(js|jsx|mjs|ts|tsx)",
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-onboarding"),
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
getAbsolutePath("@storybook/addon-essentials"),
|
||||
getAbsolutePath("@storybook/addon-onboarding"),
|
||||
getAbsolutePath("@chromatic-com/storybook"),
|
||||
getAbsolutePath("@storybook/addon-interactions"),
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath("@storybook/react-vite"),
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
import "../../web/app/globals.css";
|
||||
|
||||
import "../src/index.css";
|
||||
|
||||
export const preview: Preview = {
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
@@ -13,3 +11,5 @@ export const preview: Preview = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -12,24 +12,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@storybook/addon-essentials": "^8.0.10",
|
||||
"@storybook/addon-interactions": "^8.0.10",
|
||||
"@storybook/addon-links": "^8.0.10",
|
||||
"@storybook/addon-onboarding": "^8.0.10",
|
||||
"@storybook/blocks": "^8.0.10",
|
||||
"@storybook/react": "^8.0.10",
|
||||
"@storybook/react-vite": "^8.0.10",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"esbuild": "^0.21.1",
|
||||
"tsup": "^8.0.2",
|
||||
"vite": "^5.2.11"
|
||||
"@chromatic-com/storybook": "^1.5.0",
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@storybook/addon-essentials": "^8.1.10",
|
||||
"@storybook/addon-interactions": "^8.1.10",
|
||||
"@storybook/addon-links": "^8.1.10",
|
||||
"@storybook/addon-onboarding": "^8.1.10",
|
||||
"@storybook/blocks": "^8.1.10",
|
||||
"@storybook/react": "^8.1.10",
|
||||
"@storybook/react-vite": "^8.1.10",
|
||||
"@storybook/test": "^8.1.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"esbuild": "^0.21.5",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"storybook": "^8.1.10",
|
||||
"tsup": "^8.1.0",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import "./App.css";
|
||||
|
||||
export const App = () => {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
|
||||
|
||||
364
apps/storybook/src/stories/Configure.mdx
Normal file
@@ -0,0 +1,364 @@
|
||||
import { Meta } from "@storybook/blocks";
|
||||
|
||||
import Github from "./assets/github.svg";
|
||||
import Discord from "./assets/discord.svg";
|
||||
import Youtube from "./assets/youtube.svg";
|
||||
import Tutorials from "./assets/tutorials.svg";
|
||||
import Styling from "./assets/styling.png";
|
||||
import Context from "./assets/context.png";
|
||||
import Assets from "./assets/assets.png";
|
||||
import Docs from "./assets/docs.png";
|
||||
import Share from "./assets/share.png";
|
||||
import FigmaPlugin from "./assets/figma-plugin.png";
|
||||
import Testing from "./assets/testing.png";
|
||||
import Accessibility from "./assets/accessibility.png";
|
||||
import Theming from "./assets/theming.png";
|
||||
import AddonLibrary from "./assets/addon-library.png";
|
||||
|
||||
export const RightArrow = () => <svg
|
||||
viewBox="0 0 14 14"
|
||||
width="8px"
|
||||
height="14px"
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
display: 'inline-block',
|
||||
shapeRendering: 'inherit',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor',
|
||||
'path fill': 'currentColor'
|
||||
}}
|
||||
>
|
||||
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
|
||||
</svg>
|
||||
|
||||
<Meta title="Configure your project" />
|
||||
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Configure your project
|
||||
|
||||
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
|
||||
</div>
|
||||
<div className="sb-section">
|
||||
<div className="sb-section-item">
|
||||
<img
|
||||
src={Styling}
|
||||
alt="A wall of logos representing different styling technologies"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
|
||||
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/styling-and-css"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img
|
||||
src={Context}
|
||||
alt="An abstraction representing the composition of data for a component"
|
||||
/>
|
||||
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
|
||||
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-stories/decorators#context-for-mocking"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Assets} alt="A representation of typography and image assets" />
|
||||
<div>
|
||||
<h4 className="sb-section-item-heading">Load assets and resources</h4>
|
||||
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
|
||||
`staticDirs` configuration option to specify folders to load when
|
||||
starting Storybook.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/images-and-assets"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-container">
|
||||
<div className='sb-section-title'>
|
||||
# Do more with Storybook
|
||||
|
||||
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
|
||||
</div>
|
||||
|
||||
<div className="sb-section">
|
||||
<div className="sb-features-grid">
|
||||
<div className="sb-grid-item">
|
||||
<img src={Docs} alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated" />
|
||||
<h4 className="sb-section-item-heading">Autodocs</h4>
|
||||
<p className="sb-section-item-paragraph">Auto-generate living,
|
||||
interactive reference documentation from your components and stories.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-docs/autodocs"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Share} alt="A browser window showing a Storybook being published to a chromatic.com URL" />
|
||||
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
|
||||
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/sharing/publish-storybook#publish-storybook-with-chromatic"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={FigmaPlugin} alt="Windows showing the Storybook plugin in Figma" />
|
||||
<h4 className="sb-section-item-heading">Figma Plugin</h4>
|
||||
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
|
||||
implementation in one place.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/sharing/design-integrations#embed-storybook-in-figma-with-the-plugin"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Testing} alt="Screenshot of tests passing and failing" />
|
||||
<h4 className="sb-section-item-heading">Testing</h4>
|
||||
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
|
||||
complex.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-tests"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Accessibility} alt="Screenshot of accessibility tests passing and failing" />
|
||||
<h4 className="sb-section-item-heading">Accessibility</h4>
|
||||
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/writing-tests/accessibility-testing"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-grid-item">
|
||||
<img src={Theming} alt="Screenshot of Storybook in light and dark mode" />
|
||||
<h4 className="sb-section-item-heading">Theming</h4>
|
||||
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/docs/react/configure/theming"
|
||||
target="_blank"
|
||||
>Learn more<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='sb-addon'>
|
||||
<div className='sb-addon-text'>
|
||||
<h4>Addons</h4>
|
||||
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
|
||||
<a
|
||||
href="https://storybook.js.org/integrations/"
|
||||
target="_blank"
|
||||
>Discover all addons<RightArrow /></a>
|
||||
</div>
|
||||
<div className='sb-addon-img'>
|
||||
<img src={AddonLibrary} alt="Integrate your tools with Storybook to connect workflows." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sb-section sb-socials">
|
||||
<div className="sb-section-item">
|
||||
<img src={Github} alt="Github logo" className="sb-explore-image"/>
|
||||
Join our contributors building the future of UI development.
|
||||
|
||||
<a
|
||||
href="https://github.com/storybookjs/storybook"
|
||||
target="_blank"
|
||||
>Star on GitHub<RightArrow /></a>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Discord} alt="Discord logo" className="sb-explore-image"/>
|
||||
<div>
|
||||
Get support and chat with frontend developers.
|
||||
|
||||
<a
|
||||
href="https://discord.gg/storybook"
|
||||
target="_blank"
|
||||
>Join Discord server<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Youtube} alt="Youtube logo" className="sb-explore-image"/>
|
||||
<div>
|
||||
Watch tutorials, feature previews and interviews.
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@chromaticui"
|
||||
target="_blank"
|
||||
>Watch on YouTube<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sb-section-item">
|
||||
<img src={Tutorials} alt="A book" className="sb-explore-image"/>
|
||||
<p>Follow guided walkthroughs on for key workflows.</p>
|
||||
|
||||
<a
|
||||
href="https://storybook.js.org/tutorials/"
|
||||
target="_blank"
|
||||
>Discover tutorials<RightArrow /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sb-container {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sb-section-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.sb-section a:not(h1 a, h2 a, h3 a) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sb-section-item, .sb-grid-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-section-item-heading {
|
||||
padding-top: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.sb-section-item-paragraph {
|
||||
margin: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-chevron {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px 20px;
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sb-explore-image {
|
||||
max-height: 32px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background-color: #EEF3F8;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: #EEF3F8;
|
||||
height: 180px;
|
||||
margin-bottom: 48px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 48px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.sb-addon-text h4 {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
position: absolute;
|
||||
left: 345px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 650px;
|
||||
transform: rotate(-15deg);
|
||||
margin-left: 40px;
|
||||
margin-top: -72px;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.sb-addon-img {
|
||||
left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.sb-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sb-features-grid {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.sb-socials {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.sb-addon {
|
||||
height: 280px;
|
||||
align-items: flex-start;
|
||||
padding-top: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sb-addon-text {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.sb-addon-img {
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 130px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
width: 124%;
|
||||
}
|
||||
|
||||
.sb-addon-img img {
|
||||
width: 1200px;
|
||||
transform: rotate(-12deg);
|
||||
margin-left: 0;
|
||||
margin-top: 48px;
|
||||
margin-bottom: -40px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
BIN
apps/storybook/src/stories/assets/accessibility.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
5
apps/storybook/src/stories/assets/accessibility.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Accessibility</title>
|
||||
<circle cx="24.334" cy="24" r="24" fill="#A849FF" fill-opacity="0.3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.8609 11.585C27.8609 9.59506 26.2497 7.99023 24.2519 7.99023C22.254 7.99023 20.6429 9.65925 20.6429 11.585C20.6429 13.575 22.254 15.1799 24.2519 15.1799C26.2497 15.1799 27.8609 13.575 27.8609 11.585ZM21.8922 22.6473C21.8467 23.9096 21.7901 25.4788 21.5897 26.2771C20.9853 29.0462 17.7348 36.3314 17.3325 37.2275C17.1891 37.4923 17.1077 37.7955 17.1077 38.1178C17.1077 39.1519 17.946 39.9902 18.9802 39.9902C19.6587 39.9902 20.253 39.6293 20.5814 39.0889L20.6429 38.9874L24.2841 31.22C24.2841 31.22 27.5529 37.9214 27.9238 38.6591C28.2948 39.3967 28.8709 39.9902 29.7168 39.9902C30.751 39.9902 31.5893 39.1519 31.5893 38.1178C31.5893 37.7951 31.3639 37.2265 31.3639 37.2265C30.9581 36.3258 27.698 29.0452 27.0938 26.2771C26.8975 25.4948 26.847 23.9722 26.8056 22.7236C26.7927 22.333 26.7806 21.9693 26.7653 21.6634C26.7008 21.214 27.0231 20.8289 27.4097 20.7005L35.3366 18.3253C36.3033 18.0685 36.8834 16.9773 36.6256 16.0144C36.3678 15.0515 35.2722 14.4737 34.3055 14.7305C34.3055 14.7305 26.8619 17.1057 24.2841 17.1057C21.7062 17.1057 14.456 14.7947 14.456 14.7947C13.4893 14.5379 12.3937 14.9873 12.0715 15.9502C11.7493 16.9131 12.3293 18.0044 13.3604 18.3253L21.2873 20.7005C21.674 20.8289 21.9318 21.214 21.9318 21.6634C21.9174 21.9493 21.9053 22.2857 21.8922 22.6473Z" fill="#A470D5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
apps/storybook/src/stories/assets/addon-library.png
Normal file
|
After Width: | Height: | Size: 456 KiB |