Compare commits
379 Commits
codeql2
...
fix-date-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b9a884364 | ||
|
|
da4211f0b0 | ||
|
|
b21827cb32 | ||
|
|
4424a8a21d | ||
|
|
eb030f9ed6 | ||
|
|
333372d61c | ||
|
|
48a92f3e55 | ||
|
|
ddc767e53e | ||
|
|
432425ea59 | ||
|
|
6075fd3ef8 | ||
|
|
f099a46f83 | ||
|
|
fe54ef66c6 | ||
|
|
4eb0e930f6 | ||
|
|
fae925aa25 | ||
|
|
764a3d2fde | ||
|
|
b5a51f1304 | ||
|
|
140aee749b | ||
|
|
4113dd1873 | ||
|
|
0e0d3780d3 | ||
|
|
38ff01aedc | ||
|
|
cdf687ad80 | ||
|
|
a399fc7f80 | ||
|
|
c54a48e70b | ||
|
|
884b6f12ae | ||
|
|
5cae0febc9 | ||
|
|
0e898db710 | ||
|
|
40d54d60d4 | ||
|
|
269e026381 | ||
|
|
8245f2f6af | ||
|
|
8c07e8b1a8 | ||
|
|
e94b0845a2 | ||
|
|
4acc85bd12 | ||
|
|
ffa534d5eb | ||
|
|
fccf0f1e39 | ||
|
|
a5d80d1f02 | ||
|
|
803a73afb6 | ||
|
|
1eb8049d04 | ||
|
|
f9ed0c487f | ||
|
|
fa7d33351f | ||
|
|
e3084760b8 | ||
|
|
8e5addad5c | ||
|
|
6e741018e5 | ||
|
|
98c7c78421 | ||
|
|
16c588138c | ||
|
|
1373863af5 | ||
|
|
75315ea2c5 | ||
|
|
9f6fb8a387 | ||
|
|
b84d3d5806 | ||
|
|
5c2c1bbfcd | ||
|
|
54e84858b5 | ||
|
|
833d0789d7 | ||
|
|
1a974f3dd8 | ||
|
|
146173883f | ||
|
|
ebb02a5723 | ||
|
|
c96f7fed18 | ||
|
|
861eff3cd2 | ||
|
|
b66c0d17d0 | ||
|
|
0e748050f3 | ||
|
|
ae3524b79f | ||
|
|
0ce58b592a | ||
|
|
578346840e | ||
|
|
56bcb46d6c | ||
|
|
91405c48e0 | ||
|
|
b40dff621a | ||
|
|
7d4409e2b4 | ||
|
|
64a385b835 | ||
|
|
ee2573d128 | ||
|
|
d082e7c44d | ||
|
|
cd65850308 | ||
|
|
b91ae7e9b1 | ||
|
|
9baab1bf08 | ||
|
|
fdd4d8b926 | ||
|
|
c26c42d67f | ||
|
|
e1553becbc | ||
|
|
2e845ab0c0 | ||
|
|
79062a5476 | ||
|
|
840495b0e6 | ||
|
|
dd8fdbc7e3 | ||
|
|
a18c2aadf6 | ||
|
|
c7b653f073 | ||
|
|
8201df66c3 | ||
|
|
30e9baf0df | ||
|
|
a92881645b | ||
|
|
9522545152 | ||
|
|
33a09874c1 | ||
|
|
933755b81d | ||
|
|
8a1352d149 | ||
|
|
b47b7d7ea8 | ||
|
|
27cc4b7a5d | ||
|
|
249cc7f276 | ||
|
|
7ee9e92f7e | ||
|
|
588e80a237 | ||
|
|
e3d2d355de | ||
|
|
e8e00691c0 | ||
|
|
216be571e2 | ||
|
|
0176e82b3c | ||
|
|
30d3297002 | ||
|
|
6aa94a55ab | ||
|
|
a747ca42ad | ||
|
|
cd7a056bf1 | ||
|
|
baab774ede | ||
|
|
877c974108 | ||
|
|
d8033762d7 | ||
|
|
47a265016c | ||
|
|
ef78c68ac1 | ||
|
|
bc3f261076 | ||
|
|
4fa8a66c5f | ||
|
|
5885afc4f8 | ||
|
|
f3d21c50ab | ||
|
|
30729db47a | ||
|
|
0ff690a344 | ||
|
|
bec138c4f0 | ||
|
|
2976810fdf | ||
|
|
ae70ff2901 | ||
|
|
ee2642825f | ||
|
|
9cf98df425 | ||
|
|
1229904188 | ||
|
|
9152181a00 | ||
|
|
4843cb8789 | ||
|
|
6d3a489dff | ||
|
|
ea8e98fa0d | ||
|
|
614385da75 | ||
|
|
f6ef3ba7c5 | ||
|
|
22e8a137ef | ||
|
|
69734cc922 | ||
|
|
a9fe05d64a | ||
|
|
8bb3bd9409 | ||
|
|
5219065b8e | ||
|
|
cb8497229d | ||
|
|
25b8920d20 | ||
|
|
9203db88ab | ||
|
|
36378e9c23 | ||
|
|
9c33e77755 | ||
|
|
88cb4c742f | ||
|
|
475cce8253 | ||
|
|
a86c1738d1 | ||
|
|
96a4d02c80 | ||
|
|
bb6df783ab | ||
|
|
26cca5c2f8 | ||
|
|
7e3dd7d624 | ||
|
|
db9a53f923 | ||
|
|
92ae4786f0 | ||
|
|
b35cf14d32 | ||
|
|
14374b55d2 | ||
|
|
5a919018c5 | ||
|
|
6ac73d3f25 | ||
|
|
510fe3902e | ||
|
|
2bc23594ad | ||
|
|
06e00f3066 | ||
|
|
9b3d409695 | ||
|
|
f7f5737abf | ||
|
|
9160d63ad4 | ||
|
|
458f135ee1 | ||
|
|
b6fc104357 | ||
|
|
8e116bf62d | ||
|
|
f1d697a83f | ||
|
|
69a7a57f41 | ||
|
|
24de1559a5 | ||
|
|
ec29abfcaf | ||
|
|
eac97db665 | ||
|
|
d8386328e7 | ||
|
|
d28f321aa2 | ||
|
|
e691c076a1 | ||
|
|
ad842e0e80 | ||
|
|
dcf4109c5b | ||
|
|
05287c135e | ||
|
|
6ff8ec21cf | ||
|
|
7b6e22aa04 | ||
|
|
ee56914285 | ||
|
|
a2e9cd3c43 | ||
|
|
359f29a264 | ||
|
|
576b15fec0 | ||
|
|
42434290da | ||
|
|
62c6189dfd | ||
|
|
21c9ebbca3 | ||
|
|
658d4687f9 | ||
|
|
3775453db8 | ||
|
|
edcaf8e639 | ||
|
|
3aa658a64e | ||
|
|
58fc66ad1c | ||
|
|
f68f87645f | ||
|
|
25f99da172 | ||
|
|
5da6faa972 | ||
|
|
02b25138ef | ||
|
|
21644f5ad8 | ||
|
|
d3adc1629c | ||
|
|
7c60c57c60 | ||
|
|
7006a790dc | ||
|
|
2575b649a0 | ||
|
|
8399391aaa | ||
|
|
dfbec20016 | ||
|
|
17ac777e9b | ||
|
|
01edc0e6e0 | ||
|
|
d6b58a5e66 | ||
|
|
517ef9515c | ||
|
|
7d94861db9 | ||
|
|
cb1e4fa583 | ||
|
|
dd5fced6c4 | ||
|
|
01a4d91167 | ||
|
|
2e2f0fdbb5 | ||
|
|
3ca1a72c6a | ||
|
|
9905199055 | ||
|
|
5970ff917f | ||
|
|
d197c91995 | ||
|
|
6e1ee6df12 | ||
|
|
b44d6e60bd | ||
|
|
1d0e49d5b6 | ||
|
|
ced7b2aa2c | ||
|
|
6aa473c316 | ||
|
|
aebe36b9e8 | ||
|
|
71c92766d3 | ||
|
|
a3f5f7645a | ||
|
|
7e8514e7be | ||
|
|
7715789d0f | ||
|
|
a93fed448f | ||
|
|
10738a7af0 | ||
|
|
2c00c55e5d | ||
|
|
80ba02851f | ||
|
|
5fd3190a2d | ||
|
|
117ec317de | ||
|
|
f4f2836bdb | ||
|
|
e194a2feb8 | ||
|
|
93e9ec867c | ||
|
|
1fe625a9b4 | ||
|
|
1c1ef56e00 | ||
|
|
838fe16845 | ||
|
|
7f3c45f85a | ||
|
|
9a4f6721e2 | ||
|
|
a4ffc03e55 | ||
|
|
c4c98bda31 | ||
|
|
63aa2fd307 | ||
|
|
99d0b1786d | ||
|
|
843fed5ffd | ||
|
|
9b8112b478 | ||
|
|
2433d40918 | ||
|
|
2c1f473bbe | ||
|
|
7605a4d835 | ||
|
|
a1e2fddd5c | ||
|
|
74b770a937 | ||
|
|
bd8724c1e2 | ||
|
|
e508159255 | ||
|
|
4649921b1d | ||
|
|
1c58ac3704 | ||
|
|
4676b4cd25 | ||
|
|
15f36651d8 | ||
|
|
7b11ef9b40 | ||
|
|
3b90f085b1 | ||
|
|
36bf445370 | ||
|
|
8936ce928f | ||
|
|
28aec8852b | ||
|
|
5d1224e438 | ||
|
|
a0d02a843e | ||
|
|
08f22983e7 | ||
|
|
07289667c0 | ||
|
|
f49375dce4 | ||
|
|
9910cafe78 | ||
|
|
9996a1579b | ||
|
|
fd913ad1fa | ||
|
|
412a873c47 | ||
|
|
22046f4cfb | ||
|
|
030debe9d9 | ||
|
|
9db1d548a0 | ||
|
|
da167642b7 | ||
|
|
971053c90d | ||
|
|
65d9220df6 | ||
|
|
264ebb1d4c | ||
|
|
3509d3594f | ||
|
|
2a268accff | ||
|
|
37966880fd | ||
|
|
6699c92082 | ||
|
|
9ca1c5c14b | ||
|
|
af1a5f7361 | ||
|
|
c349a3b869 | ||
|
|
8d2edf91a1 | ||
|
|
39b686a13b | ||
|
|
139965d6ca | ||
|
|
e79e692735 | ||
|
|
797c56585b | ||
|
|
e38391ade0 | ||
|
|
f344715381 | ||
|
|
1e95b8cf9e | ||
|
|
8ffaf0748e | ||
|
|
88357a3aeb | ||
|
|
dfe025ab8e | ||
|
|
97a66168c0 | ||
|
|
a9983e1fe0 | ||
|
|
793ce0afd8 | ||
|
|
4d7cc26983 | ||
|
|
c72ce9b446 | ||
|
|
f3c628ba76 | ||
|
|
9a66e26b00 | ||
|
|
8b7f2f102f | ||
|
|
56d8c3f50f | ||
|
|
61f5c66444 | ||
|
|
a2f7b1a780 | ||
|
|
35b2d12e18 | ||
|
|
5dcd32050a | ||
|
|
1099d67bf1 | ||
|
|
69625ae832 | ||
|
|
a1c7c4a310 | ||
|
|
a3bea3b7da | ||
|
|
a3043c1f6d | ||
|
|
d0da7858ec | ||
|
|
7bba09c16d | ||
|
|
78813d53b1 | ||
|
|
35eac02545 | ||
|
|
a5b0d39adf | ||
|
|
f80d1b32b7 | ||
|
|
7598a16b75 | ||
|
|
b83b54eee1 | ||
|
|
eb2621f72a | ||
|
|
44980d21a9 | ||
|
|
e1d2e1357b | ||
|
|
f80c7d03e2 | ||
|
|
4ca6ee358b | ||
|
|
9dad06222d | ||
|
|
37ef6be4c3 | ||
|
|
f0a4fad878 | ||
|
|
06ae035e11 | ||
|
|
91b5177bdb | ||
|
|
30bd427985 | ||
|
|
a92762ff47 | ||
|
|
0a5c98aba0 | ||
|
|
6f041bf693 | ||
|
|
23c9dc304a | ||
|
|
97377fe8bd | ||
|
|
36cf16ce90 | ||
|
|
ab3ef63097 | ||
|
|
7f8549124f | ||
|
|
43b1cb904d | ||
|
|
762a3ca626 | ||
|
|
91f0d00ba2 | ||
|
|
41f42f4427 | ||
|
|
98181bfe6c | ||
|
|
a8ab4aaf2e | ||
|
|
78dca7a2bf | ||
|
|
844ea40c3a | ||
|
|
7a6dedf452 | ||
|
|
b641b37308 | ||
|
|
8c1f8bfb42 | ||
|
|
1f1563401d | ||
|
|
9fd585ee07 | ||
|
|
609dcabf77 | ||
|
|
80d338c998 | ||
|
|
306784c31b | ||
|
|
bcd68e0f19 | ||
|
|
5764148753 | ||
|
|
e7edfe3ba1 | ||
|
|
da6f54eede | ||
|
|
ade5c3d80e | ||
|
|
cc2600cfba | ||
|
|
9b191ef3e4 | ||
|
|
c450c35baf | ||
|
|
b35b82f4ee | ||
|
|
ea52624ab2 | ||
|
|
0484bccfd1 | ||
|
|
d094f63faa | ||
|
|
dc6bc61442 | ||
|
|
5918c42cf9 | ||
|
|
c34a08561e | ||
|
|
7213c726b4 | ||
|
|
f650ac4e76 | ||
|
|
2ff1be2c4a | ||
|
|
61ac306ef3 | ||
|
|
022569e404 | ||
|
|
ebf22df7b6 | ||
|
|
adcc596875 | ||
|
|
1bcdf06b43 | ||
|
|
3be78f0312 | ||
|
|
5633499834 | ||
|
|
88847a153b | ||
|
|
3e7c3a45c3 | ||
|
|
1af1a92fec | ||
|
|
7b38923b7d | ||
|
|
38500e1d79 | ||
|
|
2e82fc3ead | ||
|
|
e88ae4aa3d | ||
|
|
5f55c922dc | ||
|
|
fc3c044e00 |
@@ -1,16 +0,0 @@
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT=20
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
# RUN su node -c "npm install -g <your-package-list-here>"
|
||||
|
||||
RUN su node -c "npm install -g pnpm"
|
||||
@@ -1,28 +1,6 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node-postgres
|
||||
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
|
||||
{
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": ["dbaeumer.vscode-eslint"]
|
||||
}
|
||||
},
|
||||
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or with the host.
|
||||
"forwardPorts": [3000, 5432, 8025],
|
||||
|
||||
"name": "Node.js & PostgreSQL",
|
||||
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
|
||||
|
||||
// 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 && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace"
|
||||
"features": {},
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"postAttachCommand": "pnpm go",
|
||||
"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 && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev"
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# Update 'VARIANT' to pick an LTS version of Node.js: 20, 18, 16, 14.
|
||||
# Append -bullseye or -buster to pin to an OS version.
|
||||
# Use -bullseye variants on local arm64/Apple Silicon.
|
||||
VARIANT: "20"
|
||||
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
|
||||
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
|
||||
network_mode: service:db
|
||||
# Uncomment the next line to use a non-root user for all processes.
|
||||
# user: node
|
||||
|
||||
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg17
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: formbricks
|
||||
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
network_mode: service:app
|
||||
logging:
|
||||
driver:
|
||||
"none" # disable saving logs
|
||||
# ports:
|
||||
# - 8025:8025 # web ui
|
||||
# 1025:1025 # smtp server
|
||||
|
||||
volumes:
|
||||
postgres-data: null
|
||||
@@ -1,39 +1,56 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
# **/node_modules
|
||||
**/node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store/
|
||||
|
||||
# testing
|
||||
coverage
|
||||
**/coverage
|
||||
|
||||
# next.js
|
||||
**/.next
|
||||
**/out
|
||||
**/.next/
|
||||
**/out/
|
||||
**/build
|
||||
|
||||
# node
|
||||
**/dist
|
||||
**/dist/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
*.pem
|
||||
Zone.Identifier
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
# local env files
|
||||
**/.env
|
||||
**/.env.local
|
||||
**/.env.development.local
|
||||
**/.env.test.local
|
||||
**/.env.production.local
|
||||
!packages/database/.env
|
||||
!apps/web/.env
|
||||
|
||||
# nixos stuff
|
||||
# build tools
|
||||
.turbo
|
||||
**/*vite.config.*.timestamp-*
|
||||
|
||||
# environment specific
|
||||
.direnv
|
||||
|
||||
.vscode
|
||||
.github
|
||||
**/.turbo
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
.env
|
||||
# project specific
|
||||
packages/lib/uploads
|
||||
apps/web/public/js
|
||||
packages/database/migrations
|
||||
branch.json
|
||||
27
.env.example
@@ -46,6 +46,9 @@ SMTP_SECURE_ENABLED=0
|
||||
SMTP_USER=smtpUser
|
||||
SMTP_PASSWORD=smtpPassword
|
||||
|
||||
# If set to 0, the server will not require SMTP_USER and SMTP_PASSWORD(default is 1)
|
||||
# SMTP_AUTHENTICATED=
|
||||
|
||||
# If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs (default is 1).
|
||||
# SMTP_REJECT_UNAUTHORIZED_TLS=0
|
||||
|
||||
@@ -101,6 +104,11 @@ PASSWORD_RESET_DISABLED=1
|
||||
PRIVACY_URL=
|
||||
TERMS_URL=
|
||||
IMPRINT_URL=
|
||||
IMPRINT_ADDRESS=
|
||||
|
||||
# Configure Turnstile in signup flow
|
||||
# NEXT_PUBLIC_TURNSTILE_SITE_KEY=
|
||||
# TURNSTILE_SECRET_KEY=
|
||||
|
||||
# Configure Github Login
|
||||
GITHUB_ID=
|
||||
@@ -122,6 +130,9 @@ AZUREAD_TENANT_ID=
|
||||
# OIDC_DISPLAY_NAME=
|
||||
# OIDC_SIGNING_ALGORITHM=
|
||||
|
||||
# Configure SAML SSO
|
||||
# SAML_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/formbricks-saml
|
||||
|
||||
# Configure this when you want to ship JS & CSS files from a complete URL instead of the current domain
|
||||
# ASSET_PREFIX_URL=
|
||||
|
||||
@@ -157,11 +168,11 @@ ENTERPRISE_LICENSE_KEY=
|
||||
# Insert an existing organization id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn)
|
||||
# (Role Management is an Enterprise feature)
|
||||
# DEFAULT_ORGANIZATION_ID=
|
||||
# DEFAULT_ORGANIZATION_ROLE=admin
|
||||
# DEFAULT_ORGANIZATION_ROLE=owner
|
||||
|
||||
# Send new users to customer.io
|
||||
# CUSTOMER_IO_API_KEY=
|
||||
# CUSTOMER_IO_SITE_ID=
|
||||
# Send new users to Brevo
|
||||
# BREVO_API_KEY=
|
||||
# BREVO_LIST_ID=
|
||||
|
||||
# Ignore Rate Limiting across the Formbricks app
|
||||
# RATE_LIMITING_DISABLED=1
|
||||
@@ -178,6 +189,9 @@ UNSPLASH_ACCESS_KEY=
|
||||
# The below is used for Rate Limiting (uses In-Memory LRU Cache if not provided) (You can use a service like Webdis for this)
|
||||
# REDIS_HTTP_URL:
|
||||
|
||||
# The below is used for Rate Limiting for management API
|
||||
UNKEY_ROOT_KEY=
|
||||
|
||||
# Disable custom cache handler if necessary (e.g. if deployed on Vercel)
|
||||
# CUSTOM_CACHE_DISABLED=1
|
||||
|
||||
@@ -185,4 +199,7 @@ UNSPLASH_ACCESS_KEY=
|
||||
# AI_AZURE_RESSOURCE_NAME=
|
||||
# AI_AZURE_API_KEY=
|
||||
# AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID=
|
||||
# AI_AZURE_LLM_DEPLOYMENT_ID=
|
||||
# AI_AZURE_LLM_DEPLOYMENT_ID=
|
||||
|
||||
# NEXT_PUBLIC_INTERCOM_APP_ID=
|
||||
# INTERCOM_SECRET_KEY=
|
||||
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Bug report
|
||||
description: "Found a bug? Please fill out the sections below. \U0001F44D"
|
||||
labels:
|
||||
- bug
|
||||
type: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue-summary
|
||||
@@ -10,6 +9,13 @@ body:
|
||||
description: A summary of the issue. This needs to be a clear detailed-rich summary.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: issue-expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other-information
|
||||
attributes:
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Feature request
|
||||
description: "Suggest an idea for this project \U0001F680"
|
||||
labels:
|
||||
- enhancement
|
||||
type: feature
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
@@ -30,4 +29,4 @@ body:
|
||||
### Additional resources 🤓
|
||||
|
||||
- Check out our [Contributor Docs](https://formbricks.com/docs/developer-docs/contributing/get-started)
|
||||
- Anything unclear? [Ask in Discord](https://formbricks.com/discord)
|
||||
- Anything unclear? [Ask in Github Discussions](https://github.com/formbricks/formbricks/discussions)
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Task (internal)
|
||||
description: "Template for creating a task. Used by the Formbricks Team only \U0001f4e5"
|
||||
type: task
|
||||
body:
|
||||
- type: textarea
|
||||
id: task-summary
|
||||
attributes:
|
||||
label: Task description
|
||||
description: A clear detailed-rich description of the task.
|
||||
validations:
|
||||
required: true
|
||||
28
.github/workflows/build-docs.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Build Docs
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
pnpm build --filter=@formbricks/docs...
|
||||
shell: bash
|
||||
4
.github/workflows/build-web.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build Web
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Formbricks-web
|
||||
|
||||
92
.github/workflows/codeql.yml
vendored
@@ -1,92 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '17 1 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
10
.github/workflows/cron-surveyStatusUpdate.yml
vendored
@@ -4,9 +4,13 @@ on:
|
||||
workflow_dispatch:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||
# schedule:
|
||||
# Runs “At 00:00.” (see https://crontab.guru)
|
||||
# - cron: "0 0 * * *"
|
||||
schedule:
|
||||
# Runs "At 00:00." (see https://crontab.guru)
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cron-weeklySummary:
|
||||
env:
|
||||
|
||||
2
.github/workflows/cron-weeklySummary.yml
vendored
@@ -9,6 +9,8 @@ on:
|
||||
- cron: "0 8 * * 1"
|
||||
jobs:
|
||||
cron-weeklySummary:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
APP_URL: ${{ secrets.APP_URL }}
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
|
||||
62
.github/workflows/e2e.yml
vendored
@@ -1,9 +1,28 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
AZURE_CLIENT_ID:
|
||||
required: false
|
||||
AZURE_TENANT_ID:
|
||||
required: false
|
||||
AZURE_SUBSCRIPTION_ID:
|
||||
required: false
|
||||
PLAYWRIGHT_SERVICE_URL:
|
||||
required: false
|
||||
# Add other secrets if necessary
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TELEMETRY_DISABLED: 1
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Run E2E Tests
|
||||
@@ -60,11 +79,12 @@ jobs:
|
||||
|
||||
- name: Apply Prisma Migrations
|
||||
run: |
|
||||
pnpm prisma migrate deploy
|
||||
# pnpm prisma migrate deploy
|
||||
pnpm db:migrate:dev
|
||||
|
||||
- name: Run App
|
||||
run: |
|
||||
NODE_ENV=test pnpm start --filter=@formbricks/web &
|
||||
NODE_ENV=test pnpm start --filter=@formbricks/web | tee app.log 2>&1 &
|
||||
sleep 10 # Optional: gives some buffer for the app to start
|
||||
for attempt in {1..10}; do
|
||||
if [ $(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/health) -eq 200 ]; then
|
||||
@@ -82,13 +102,47 @@ jobs:
|
||||
- name: Install Playwright
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Run E2E Tests
|
||||
- name: Set Azure Secret Variables
|
||||
run: |
|
||||
if [[ -n "${{ secrets.AZURE_CLIENT_ID }}" && -n "${{ secrets.AZURE_TENANT_ID }}" && -n "${{ secrets.AZURE_SUBSCRIPTION_ID }}" ]]; then
|
||||
echo "AZURE_ENABLED=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "AZURE_ENABLED=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Azure login
|
||||
if: env.AZURE_ENABLED == 'true'
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Run E2E Tests (Azure)
|
||||
if: env.AZURE_ENABLED == 'true'
|
||||
env:
|
||||
PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}
|
||||
run: |
|
||||
pnpm test-e2e:azure
|
||||
|
||||
- name: Run E2E Tests (Local)
|
||||
if: env.AZURE_ENABLED == 'false'
|
||||
run: |
|
||||
pnpm test:e2e
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: app-logs
|
||||
path: app.log
|
||||
|
||||
- name: Output App Logs
|
||||
if: failure()
|
||||
run: cat app.log
|
||||
|
||||
10
.github/workflows/lint.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Lint
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Linters
|
||||
@@ -8,16 +12,16 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
46
.github/workflows/pr.yml
vendored
@@ -1,5 +1,13 @@
|
||||
name: PR Update
|
||||
|
||||
# Update permissions to include all necessary ones
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
checks: write
|
||||
id-token: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -12,63 +20,35 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect changes
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
has-files-requiring-all-checks:
|
||||
- "!(**.md|.github/CODEOWNERS)"
|
||||
|
||||
test:
|
||||
name: Run Unit Tests
|
||||
needs: [changes]
|
||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||
uses: ./.github/workflows/test.yml
|
||||
secrets: inherit
|
||||
|
||||
lint:
|
||||
name: Run Linters
|
||||
needs: [changes]
|
||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||
uses: ./.github/workflows/lint.yml
|
||||
secrets: inherit
|
||||
|
||||
build:
|
||||
name: Build Formbricks-web
|
||||
needs: [changes]
|
||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||
uses: ./.github/workflows/build-web.yml
|
||||
secrets: inherit
|
||||
|
||||
docs:
|
||||
name: Build Docs
|
||||
needs: [changes]
|
||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||
uses: ./.github/workflows/build-docs.yml
|
||||
secrets: inherit
|
||||
|
||||
e2e-test:
|
||||
name: Run E2E Tests
|
||||
needs: [changes]
|
||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
secrets: inherit
|
||||
|
||||
required:
|
||||
name: PR Check Summary
|
||||
needs: [lint, test, build, e2e-test, docs]
|
||||
needs: [lint, test, build, e2e-test]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
statuses: write
|
||||
steps:
|
||||
- name: fail if conditional jobs failed
|
||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
||||
|
||||
62
.github/workflows/prepare-release.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Prepare release
|
||||
run-name: Prepare release ${{ inputs.next_version }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
next_version:
|
||||
required: true
|
||||
type: string
|
||||
description: "Version name"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
prepare_release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --local user.email "github-actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
cd apps/web
|
||||
pnpm version ${{ inputs.next_version }} --no-workspaces-update
|
||||
|
||||
- name: Commit changes and create a branch
|
||||
run: |
|
||||
branch_name="release-v${{ inputs.next_version }}"
|
||||
git checkout -b "$branch_name"
|
||||
git add .
|
||||
git commit -m "chore: release v${{ inputs.next_version }}"
|
||||
git push origin "$branch_name"
|
||||
|
||||
- name: Create pull request
|
||||
run: |
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "release-v${{ inputs.next_version }}" \
|
||||
--title "chore: bump version to v${{ inputs.next_version }}" \
|
||||
--body "This PR contains the changes for the v${{ inputs.next_version }} release."
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
5
.github/workflows/release-changesets.yml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
packages: write
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
env:
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
name: Docker for Data Migrations
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: formbricks/data-migrations
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public"
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=${{ github.ref_name }}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/database/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
DATABASE_URL=${{ env.DATABASE_URL }}
|
||||
|
||||
- name: Sign the published Docker image
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:latest
|
||||
2
.github/workflows/release-docker.yml
vendored
@@ -8,6 +8,8 @@ on:
|
||||
jobs:
|
||||
release-image-on-dockerhub:
|
||||
name: Release on Dockerhub
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
76
.github/workflows/scorecard.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: "17 17 * * 6"
|
||||
push:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Add this permission
|
||||
actions: write # Required for artifact upload
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sarif
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
53
.github/workflows/sonarqube.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: SonarQube
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
merge_group:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
sonarqube:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
|
||||
run: |
|
||||
RANDOM_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
|
||||
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
|
||||
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
cd apps/web
|
||||
pnpm test:coverage
|
||||
cd ../../
|
||||
# The Vitest coverage config is in your vite.config.mts
|
||||
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
2
.github/workflows/test.yml
vendored
@@ -6,6 +6,8 @@ jobs:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
46
.github/workflows/tolgee-missing-key-check.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Check Missing Translations
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check-missing-translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Tolgee CLI
|
||||
run: npm install -g @tolgee/cli
|
||||
|
||||
- name: Compare Tolgee Keys
|
||||
id: compare
|
||||
run: |
|
||||
tolgee compare --api-key ${{ secrets.TOLGEE_API_KEY }} > compare_output.txt
|
||||
cat compare_output.txt
|
||||
|
||||
- name: Check for Missing Translations
|
||||
run: |
|
||||
if grep -q "new key found" compare_output.txt; then
|
||||
echo "New keys found that may require translations:"
|
||||
exit 1
|
||||
else
|
||||
echo "No new keys found."
|
||||
fi
|
||||
85
.github/workflows/tolgee.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Tolgee Tagging on PR Merge
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
tag-production-keys:
|
||||
name: Tag Production Keys
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # This ensures we get the full git history
|
||||
|
||||
- name: Get source branch name
|
||||
id: branch-name
|
||||
run: |
|
||||
RAW_BRANCH="${{ github.head_ref }}"
|
||||
SOURCE_BRANCH=$(echo "$RAW_BRANCH" | sed 's/[^a-zA-Z0-9._\/-]//g')
|
||||
|
||||
|
||||
# Safely add to environment variables using GitHub's recommended method
|
||||
# This prevents environment variable injection attacks
|
||||
echo "SOURCE_BRANCH<<EOF" >> $GITHUB_ENV
|
||||
echo "$SOURCE_BRANCH" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
echo "Detected source branch: $SOURCE_BRANCH"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18 # Ensure compatibility with your project
|
||||
|
||||
- name: Install Tolgee CLI
|
||||
run: npm install -g @tolgee/cli
|
||||
|
||||
- name: Tag Production Keys
|
||||
run: |
|
||||
npx tolgee tag \
|
||||
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||
--filter-extracted \
|
||||
--filter-tag "draft:${SOURCE_BRANCH}" \
|
||||
--tag production \
|
||||
--untag "draft:${SOURCE_BRANCH}"
|
||||
--verbose
|
||||
|
||||
- name: Tag unused production keys as Deprecated
|
||||
run: |
|
||||
npx tolgee tag \
|
||||
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||
--filter-not-extracted --filter-tag production \
|
||||
--tag deprecated --untag production
|
||||
--verbose
|
||||
|
||||
- name: Tag unused draft:current-branch keys as Deprecated
|
||||
run: |
|
||||
npx tolgee tag \
|
||||
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||
--filter-not-extracted --filter-tag "draft:${SOURCE_BRANCH}" \
|
||||
--tag deprecated --untag "draft:${SOURCE_BRANCH}"
|
||||
--verbose
|
||||
|
||||
- name: Sync with backup
|
||||
run: |
|
||||
npx tolgee sync \
|
||||
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||
--backup ./tolgee-backup \
|
||||
--continue-on-warning \
|
||||
--yes
|
||||
|
||||
- name: Upload backup as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tolgee-backup-${{ github.sha }}
|
||||
path: ./tolgee-backup
|
||||
retention-days: 90
|
||||
@@ -3,7 +3,7 @@ name: "Welcome new contributors"
|
||||
on:
|
||||
issues:
|
||||
types: opened
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: opened
|
||||
|
||||
permissions:
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |-
|
||||
Thank you so much for making your first Pull Request and taking the time to improve Formbricks! 🚀🙏❤️
|
||||
Feel free to join the conversation at [Discord](https://formbricks.com/discord)
|
||||
Feel free to join the conversation on [Github Discussions](https://github.com/formbricks/formbricks/discussions) if you need any help or have any questions. 😊
|
||||
issue-message: |
|
||||
Thank you for opening your first issue! 🙏❤️ One of our team members will review it and get back to you as soon as it possible. 😊
|
||||
|
||||
45
.gitignore
vendored
@@ -1,25 +1,26 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
**/node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store/
|
||||
|
||||
# testing
|
||||
coverage
|
||||
**/coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
**/.next/
|
||||
**/out/
|
||||
**/build
|
||||
|
||||
# node
|
||||
dist/
|
||||
**/dist/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
*.pem
|
||||
Zone.Identifier
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
@@ -27,36 +28,30 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
**/.env
|
||||
**/.env.local
|
||||
**/.env.development.local
|
||||
**/.env.test.local
|
||||
**/.env.production.local
|
||||
!packages/database/.env
|
||||
!apps/web/.env
|
||||
|
||||
# Prisma generated files
|
||||
packages/database/zod
|
||||
|
||||
# turbo
|
||||
# build tools
|
||||
.turbo
|
||||
**/*vite.config.*.timestamp-*
|
||||
|
||||
# nixos stuff
|
||||
# environment specific
|
||||
.direnv
|
||||
|
||||
Zone.Identifier
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
# uploads
|
||||
# project specific
|
||||
packages/lib/uploads
|
||||
|
||||
# Vite Timestamps
|
||||
*vite.config.*.timestamp-*
|
||||
|
||||
# js compiled assets
|
||||
apps/web/public/js
|
||||
packages/database/migrations
|
||||
branch.json
|
||||
.vercel
|
||||
|
||||
6
.gitpod.Dockerfile
vendored
@@ -1,6 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
# Install custom tools, runtime, etc.
|
||||
RUN brew install yq
|
||||
|
||||
RUN pnpm install turbo --global
|
||||
74
.gitpod.yml
@@ -1,74 +0,0 @@
|
||||
tasks:
|
||||
- name: demo
|
||||
init: |
|
||||
gp sync-await init-install &&
|
||||
bash .gitpod/setup-demo.bash
|
||||
command: |
|
||||
cd apps/demo &&
|
||||
cp .env.example .env &&
|
||||
sed -i -r "s#^(NEXT_PUBLIC_FORMBRICKS_API_HOST=).*#\1 $(gp url 3000)#" .env &&
|
||||
gp sync-await init &&
|
||||
turbo --filter "@formbricks/demo" go
|
||||
|
||||
- name: Init Formbricks
|
||||
init: |
|
||||
cp .env.example .env &&
|
||||
bash .gitpod/init.bash &&
|
||||
turbo --filter "@formbricks/js" build &&
|
||||
gp sync-done init-install
|
||||
command: |
|
||||
gp sync-done init &&
|
||||
gp tasks list &&
|
||||
gp ports await 3002 && gp ports await 3000 && gp open apps/demo/.env && gp preview $(gp url 3002) --external
|
||||
|
||||
- name: web
|
||||
init: |
|
||||
gp sync-await init-install &&
|
||||
bash .gitpod/setup-web.bash &&
|
||||
turbo --filter "@formbricks/database" db:down
|
||||
command: |
|
||||
gp sync-await init &&
|
||||
cp .env.example .env &&
|
||||
sed -i -r "s#^(WEBAPP_URL=).*#\1 $(gp url 3000)#" .env &&
|
||||
RANDOM_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
sed -i 's/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY='"$RANDOM_ENCRYPTION_KEY"'/' .env
|
||||
turbo --filter "@formbricks/web" go
|
||||
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
visibility: public
|
||||
onOpen: open-browser
|
||||
- port: 3001
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 3002
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 5432
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 1025
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 8025
|
||||
visibility: public
|
||||
onOpen: open-browser
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
master: true
|
||||
pullRequests: true
|
||||
addComment: true
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- "ban.spellright"
|
||||
- "bradlc.vscode-tailwindcss"
|
||||
- "DavidAnson.vscode-markdownlint"
|
||||
- "dbaeumer.vscode-eslint"
|
||||
- "esbenp.prettier-vscode"
|
||||
- "Prisma.prisma"
|
||||
- "yzhang.markdown-all-in-one"
|
||||
2
.husky/post-checkout
Normal file
@@ -0,0 +1,2 @@
|
||||
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ./branch.json
|
||||
prettier --write ./branch.json
|
||||
@@ -1 +1,21 @@
|
||||
pnpm lint-staged
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Load environment variables from .env files
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
. .env
|
||||
set +a
|
||||
fi
|
||||
|
||||
pnpm lint-staged
|
||||
|
||||
# Run tolgee-pull if branch.json exists and NEXT_PUBLIC_TOLGEE_API_KEY is not set
|
||||
if [ -f branch.json ]; then
|
||||
if [ -z "$NEXT_PUBLIC_TOLGEE_API_KEY" ]; then
|
||||
echo "Skipping tolgee-pull: NEXT_PUBLIC_TOLGEE_API_KEY is not set"
|
||||
else
|
||||
pnpm run tolgee-pull
|
||||
git add packages/lib/messages
|
||||
fi
|
||||
fi
|
||||
39
.tolgeerc.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "https://docs.tolgee.io/cli-schema.json",
|
||||
"format": "JSON_TOLGEE",
|
||||
"patterns": ["./apps/web/**/*.ts?(x)"],
|
||||
"projectId": 10304,
|
||||
"pull": {
|
||||
"path": "./packages/lib/messages"
|
||||
},
|
||||
"push": {
|
||||
"files": [
|
||||
{
|
||||
"language": "en-US",
|
||||
"path": "./packages/lib/messages/en-US.json"
|
||||
},
|
||||
{
|
||||
"language": "de-DE",
|
||||
"path": "./packages/lib/messages/de-DE.json"
|
||||
},
|
||||
{
|
||||
"language": "fr-FR",
|
||||
"path": "./packages/lib/messages/fr-FR.json"
|
||||
},
|
||||
{
|
||||
"language": "pt-BR",
|
||||
"path": "./packages/lib/messages/pt-BR.json"
|
||||
},
|
||||
{
|
||||
"language": "zh-Hant-TW",
|
||||
"path": "./packages/lib/messages/zh-Hant-TW.json"
|
||||
},
|
||||
{
|
||||
"language": "pt-PT",
|
||||
"path": "./packages/lib/messages/pt-PT.json"
|
||||
}
|
||||
],
|
||||
"forceMode": "OVERRIDE"
|
||||
},
|
||||
"strictNamespace": false
|
||||
}
|
||||
4
.vscode/extensions.json
vendored
@@ -6,6 +6,8 @@
|
||||
"dbaeumer.vscode-eslint", // eslint plugin
|
||||
"esbenp.prettier-vscode", // prettier plugin
|
||||
"Prisma.prisma", // syntax|format|completion for prisma
|
||||
"yzhang.markdown-all-in-one" // nicer markdown support
|
||||
"yzhang.markdown-all-in-one", // nicer markdown support
|
||||
"vitest.explorer", // run tests directly from the code window
|
||||
"sonarsource.sonarlint-vscode" // sonarqube linter for vscode
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ Ready to dive into the code and make a real impact? Here's your path:
|
||||
|
||||
1. **Read our Best Practices**: [It takes 5 minutes](https://formbricks.com/docs/developer-docs/contributing/get-started) but will help you save hours 🤓
|
||||
|
||||
1. **Fork the Repository:** Fork our repository or use [Gitpod](https://formbricks.com/docs/developer-docs/contributing/gitpod) or use [Codespaces](https://formbricks.com/docs/developer-docs/contributing/codespaces)
|
||||
1. **Fork the Repository:** Fork our repository or use [Gitpod](https://gitpod.io) or use [Github Codespaces](https://github.com/features/codespaces) to get started instantly.
|
||||
|
||||
1. **Tweak and Transform:** Work your coding magic and apply your changes.
|
||||
|
||||
1. **Pull Request Act:** If you're ready to go, craft a new pull request closely following our PR template 🙏
|
||||
|
||||
Would you prefer a chat before you dive into a lot of work? Our [Discord server](https://formbricks.com/discord) is your harbor. Share your thoughts, and we'll meet you there with open arms. We're responsive and friendly, promise!
|
||||
Would you prefer a chat before you dive into a lot of work? [Github Discussions](https://github.com/formbricks/formbricks/discussions) is your harbor. Share your thoughts, and we'll meet you there with open arms. We're responsive and friendly, promise!
|
||||
|
||||
## 🚀 Aspiring Features
|
||||
|
||||
|
||||
4
LICENSE
@@ -2,8 +2,8 @@ Copyright (c) 2024 Formbricks GmbH
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/", "apps/web/modules/ee" & "apps/web/app/(ee)" directories of this repository, if these directories exist, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/react-native/" and "packages/api/" directories of this repository, if that directories exist, is licensed under the "MIT" license as defined in the "LICENSE" files of these packages.
|
||||
- All content that resides under the "apps/web/modules/ee" directory of this repository, if these directories exist, is licensed under the license defined in "apps/web/modules/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/react-native/", "packages/android/", "packages/ios/" and "packages/api/" directories of this repository, if that directories exist, is licensed under the "MIT" license as defined in the "LICENSE" files of these packages.
|
||||
- All third party components incorporated into the Formbricks Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||
|
||||
|
||||
12
README.md
@@ -13,14 +13,14 @@
|
||||
<h3 align="center">Formbricks</h3>
|
||||
|
||||
<p align="center">
|
||||
Harvest user-insights, build irresistible experiences.
|
||||
The Open Source Qualtrics Alternative
|
||||
<br />
|
||||
<a href="https://formbricks.com/">Website</a> | <a href="https://formbricks.com/discord">Join Discord community</a>
|
||||
<a href="https://formbricks.com/">Website</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/formbricks/formbricks/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-AGPL-purple" alt="License"></a> <a href="https://formbricks.com/discord"><img src="https://img.shields.io/discord/979077669410979880?label=Discord&logo=discord&logoColor=%23fff" alt="Join Formbricks Discord"></a> <a href="https://github.com/formbricks/formbricks/stargazers"><img src="https://img.shields.io/github/stars/formbricks/formbricks?logo=github" alt="Github Stars"></a>
|
||||
<a href="https://github.com/formbricks/formbricks/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-AGPL-purple" alt="License"></a> <a href="https://github.com/formbricks/formbricks/stargazers"><img src="https://img.shields.io/github/stars/formbricks/formbricks?logo=github" alt="Github Stars"></a>
|
||||
<a href="https://news.ycombinator.com/item?id=32303986"><img src="https://img.shields.io/badge/Hacker%20News-122-%23FF6600" alt="Hacker News"></a>
|
||||
<a href="[https://www.producthunt.com/products/formbricks](https://www.producthunt.com/posts/formbricks)"><img src="https://img.shields.io/badge/Product%20Hunt-455-orange?logo=producthunt&logoColor=%23fff" alt="Product Hunt"></a>
|
||||
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next/"><img src="https://img.shields.io/badge/2023-blue?logo=github&label=Github%20Accelerator" alt="Github Accelerator"></a>
|
||||
@@ -228,14 +228,14 @@ The Formbricks core application is licensed under the [AGPLv3 Open Source Licens
|
||||
|
||||
### The Enterprise Edition
|
||||
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/packages/ee) and [license](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE) for the enterprise functionality can be found in the `/packages/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/apps/web/modules/ee) and [license](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE) for the enterprise functionality can be found in the `/apps/web/modules/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
|
||||
### White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
We currently do not offer Formbricks white-labeled. Any other needs? [Send us an email](mailto:hola@formbricks.com).
|
||||
We currently do not offer Formbricks white-labeled. That means that we don't sell a license which let's other companies resell Formbricks to third parties under their name nor take parts (like the survey editor) out of Formbricks to add to their own software products. Any other needs? [Send us an email](mailto:hola@formbricks.com).
|
||||
|
||||
### Why charge for Enterprise Features?
|
||||
|
||||
The Enterprise Edition and White-Label Licenses allow us to fund the development of Formbricks sustainably. It guarantees that the open-source surveying infrastructure we're building will be around for decades to come.
|
||||
The Enterprise Edition allows us to fund the development of Formbricks sustainably. It guarantees that the free and open-source surveying infrastructure we're building will be around for decades to come.
|
||||
|
||||
<p align="right"><a href="#top">🔼 Back to top</a></p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Security Policy of Formbricks
|
||||
|
||||
This is Formbrick's security policy. Please reach out to us
|
||||
on our Discord or, if privately, via <security@formbricks.com>
|
||||
on Github or, if privately, via <security@formbricks.com>
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -40,7 +40,7 @@ We invite you to report if:
|
||||
|
||||
Avoid reporting if:
|
||||
|
||||
- Assistance is needed to optimize Formbricks for security – please engage on our Discord for this.
|
||||
- Assistance is needed to optimize Formbricks for security – please engage on Github Discussions for this.
|
||||
- Help is required for applying security-related updates.
|
||||
- The concern is not related to security.
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
|
||||
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq
|
||||
EXPO_PUBLIC_APP_URL=http://192.168.0.197:3000
|
||||
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=cm5p0cs7r000819182b32j0a1
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"jsEngine": "hermes",
|
||||
"name": "react-native-demo",
|
||||
"newArchEnabled": true,
|
||||
"orientation": "portrait",
|
||||
"slug": "react-native-demo",
|
||||
"splash": {
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/react-native": "workspace:*",
|
||||
"expo": "51.0.26",
|
||||
"expo-status-bar": "1.12.1",
|
||||
"@react-native-async-storage/async-storage": "2.1.0",
|
||||
"expo": "52.0.28",
|
||||
"expo-status-bar": "2.0.1",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.74.4",
|
||||
"react-native-webview": "13.8.6"
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.6",
|
||||
"react-native-webview": "13.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.25.2",
|
||||
"@types/react": "18.3.11",
|
||||
"typescript": "5.3.3"
|
||||
"@babel/core": "7.26.0",
|
||||
"@types/react": "18.3.18",
|
||||
"typescript": "5.7.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import React, { type JSX } from "react";
|
||||
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
||||
import Formbricks, { track } from "@formbricks/react-native";
|
||||
import Formbricks, {
|
||||
logout,
|
||||
setAttribute,
|
||||
setAttributes,
|
||||
setLanguage,
|
||||
setUserId,
|
||||
track,
|
||||
} from "@formbricks/react-native";
|
||||
|
||||
LogBox.ignoreAllLogs();
|
||||
|
||||
@@ -9,35 +17,92 @@ export default function App(): JSX.Element {
|
||||
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
|
||||
}
|
||||
|
||||
if (!process.env.EXPO_PUBLIC_API_HOST) {
|
||||
throw new Error("EXPO_PUBLIC_API_HOST is required");
|
||||
if (!process.env.EXPO_PUBLIC_APP_URL) {
|
||||
throw new Error("EXPO_PUBLIC_APP_URL is required");
|
||||
}
|
||||
|
||||
const config = {
|
||||
environmentId: process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
apiHost: process.env.EXPO_PUBLIC_API_HOST,
|
||||
userId: "random-user-id",
|
||||
attributes: {
|
||||
language: "en",
|
||||
testAttr: "attr-test",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Formbricks React Native SDK Demo</Text>
|
||||
|
||||
<Button
|
||||
title="Trigger Code Action"
|
||||
onPress={() => {
|
||||
track("code").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error tracking event:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 10,
|
||||
}}>
|
||||
<Button
|
||||
title="Trigger Code Action"
|
||||
onPress={() => {
|
||||
track("code").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error tracking event:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Set User Id"
|
||||
onPress={() => {
|
||||
setUserId("random-user-id").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error setting user id:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Set User Attributess (multiple)"
|
||||
onPress={() => {
|
||||
setAttributes({
|
||||
testAttr: "attr-test",
|
||||
testAttr2: "attr-test-2",
|
||||
testAttr3: "attr-test-3",
|
||||
testAttr4: "attr-test-4",
|
||||
}).catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error setting user attributes:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Set User Attributes (single)"
|
||||
onPress={() => {
|
||||
setAttribute("testSingleAttr", "testSingleAttr").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error setting user attributes:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Logout"
|
||||
onPress={() => {
|
||||
logout().catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error logging out:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Set Language (de)"
|
||||
onPress={() => {
|
||||
setLanguage("de").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error setting language:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<StatusBar style="auto" />
|
||||
<Formbricks initConfig={config} />
|
||||
|
||||
<Formbricks
|
||||
appUrl={process.env.EXPO_PUBLIC_APP_URL as string}
|
||||
environmentId={process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID as string}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
extends: ["@formbricks/eslint-config/next.js"],
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Sidebar } from "./Sidebar";
|
||||
|
||||
export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="min-h-full">
|
||||
{/* Static sidebar for desktop */}
|
||||
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col lg:pl-64">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
ClockIcon,
|
||||
CogIcon,
|
||||
CreditCardIcon,
|
||||
FileBarChartIcon,
|
||||
HelpCircleIcon,
|
||||
HomeIcon,
|
||||
ScaleIcon,
|
||||
ShieldCheckIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
import { classNames } from "../lib/utils";
|
||||
|
||||
const navigation = [
|
||||
{ name: "Home", href: "#", icon: HomeIcon, current: true },
|
||||
{ name: "History", href: "#", icon: ClockIcon, current: false },
|
||||
{ name: "Balances", href: "#", icon: ScaleIcon, current: false },
|
||||
{ name: "Cards", href: "#", icon: CreditCardIcon, current: false },
|
||||
{ name: "Recipients", href: "#", icon: UsersIcon, current: false },
|
||||
{ name: "Reports", href: "#", icon: FileBarChartIcon, current: false },
|
||||
];
|
||||
const secondaryNavigation = [
|
||||
{ name: "Settings", href: "#", icon: CogIcon },
|
||||
{ name: "Help", href: "#", icon: HelpCircleIcon },
|
||||
{ name: "Privacy", href: "#", icon: ShieldCheckIcon },
|
||||
];
|
||||
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
|
||||
<nav
|
||||
className="mt-5 flex flex-1 flex-col divide-y divide-cyan-800 overflow-y-auto"
|
||||
aria-label="Sidebar">
|
||||
<div className="space-y-1 px-2">
|
||||
{navigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={classNames(
|
||||
item.current ? "bg-cyan-800 text-white" : "text-cyan-100 hover:bg-cyan-600 hover:text-white",
|
||||
"group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6"
|
||||
)}
|
||||
aria-current={item.current ? "page" : undefined}>
|
||||
<item.icon className="mr-4 h-6 w-6 flex-shrink-0 text-cyan-200" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-6">
|
||||
<div className="space-y-1 px-2">
|
||||
{secondaryNavigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6 text-cyan-100 hover:bg-cyan-600 hover:text-white">
|
||||
<item.icon className="mr-4 h-6 w-6 text-cyan-200" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
13
apps/demo/components/layout-app.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Sidebar } from "./sidebar";
|
||||
|
||||
export function LayoutApp({ children }: { children: React.ReactNode }): React.JSX.Element {
|
||||
return (
|
||||
<div className="min-h-full">
|
||||
{/* Static sidebar for desktop */}
|
||||
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col lg:pl-64">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
apps/demo/components/sidebar.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
ClockIcon,
|
||||
CogIcon,
|
||||
CreditCardIcon,
|
||||
FileBarChartIcon,
|
||||
HelpCircleIcon,
|
||||
HomeIcon,
|
||||
ScaleIcon,
|
||||
ShieldCheckIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
import { classNames } from "../lib/utils";
|
||||
|
||||
const navigation = [
|
||||
{ name: "Home", href: "#", icon: HomeIcon, current: true },
|
||||
{ name: "History", href: "#", icon: ClockIcon, current: false },
|
||||
{ name: "Balances", href: "#", icon: ScaleIcon, current: false },
|
||||
{ name: "Cards", href: "#", icon: CreditCardIcon, current: false },
|
||||
{ name: "Recipients", href: "#", icon: UsersIcon, current: false },
|
||||
{ name: "Reports", href: "#", icon: FileBarChartIcon, current: false },
|
||||
];
|
||||
const secondaryNavigation = [
|
||||
{ name: "Settings", href: "#", icon: CogIcon },
|
||||
{ name: "Help", href: "#", icon: HelpCircleIcon },
|
||||
{ name: "Privacy", href: "#", icon: ShieldCheckIcon },
|
||||
];
|
||||
|
||||
export function Sidebar(): React.JSX.Element {
|
||||
return (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
|
||||
<nav
|
||||
className="mt-5 flex flex-1 flex-col divide-y divide-cyan-800 overflow-y-auto"
|
||||
aria-label="Sidebar">
|
||||
<div className="space-y-1 px-2">
|
||||
{navigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={classNames(
|
||||
item.current ? "bg-cyan-800 text-white" : "text-cyan-100 hover:bg-cyan-600 hover:text-white",
|
||||
"group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6"
|
||||
)}
|
||||
aria-current={item.current ? "page" : undefined}>
|
||||
<item.icon className="mr-4 h-6 w-6 flex-shrink-0 text-cyan-200" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-6">
|
||||
<div className="space-y-1 px-2">
|
||||
{secondaryNavigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6 text-cyan-100 hover:bg-cyan-600 hover:text-white">
|
||||
<item.icon className="mr-4 h-6 w-6 text-cyan-200" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
apps/demo/globals.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,3 +1,3 @@
|
||||
export const classNames = (...classes: any) => {
|
||||
export function classNames(...classes: string[]): string {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
};
|
||||
}
|
||||
|
||||
2
apps/demo/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -4,22 +4,21 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
"dev": "next dev -p 3002 --turbo",
|
||||
"go": "next dev -p 3002 --turbo",
|
||||
"dev": "next dev -p 3002 --turbopack",
|
||||
"go": "next dev -p 3002 --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "0.452.0",
|
||||
"next": "14.2.15",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
"lucide-react": "0.468.0",
|
||||
"next": "15.1.2",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@formbricks/config-typescript": "workspace:*"
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/eslint-config": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import "@formbricks/ui/globals.css";
|
||||
import "../globals.css";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
export default function App({ Component, pageProps }: AppProps): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -17,6 +17,4 @@ const App = ({ Component, pageProps }: AppProps) => {
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
const Document = () => {
|
||||
export default function Document(): React.JSX.Element {
|
||||
return (
|
||||
<Html lang="en" className="h-full bg-slate-50">
|
||||
<Head />
|
||||
@@ -10,6 +10,4 @@ const Document = () => {
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default Document;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useEffect, useState } from "react";
|
||||
import formbricks from "@formbricks/js";
|
||||
import fbsetup from "../public/fb-setup.png";
|
||||
|
||||
declare const window: any;
|
||||
declare const window: Window;
|
||||
|
||||
const AppPage = ({}) => {
|
||||
export default function AppPage(): React.JSX.Element {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -19,44 +19,53 @@ const AppPage = ({}) => {
|
||||
}, [darkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// enable Formbricks debug mode by adding formbricksDebug=true GET parameter
|
||||
const addFormbricksDebugParam = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has("formbricksDebug")) {
|
||||
urlParams.set("formbricksDebug", "true");
|
||||
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
const initFormbricks = () => {
|
||||
// enable Formbricks debug mode by adding formbricksDebug=true GET parameter
|
||||
const addFormbricksDebugParam = (): void => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has("formbricksDebug")) {
|
||||
urlParams.set("formbricksDebug", "true");
|
||||
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
}
|
||||
};
|
||||
|
||||
addFormbricksDebugParam();
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const userId = "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING";
|
||||
const userInitAttributes = {
|
||||
language: "de",
|
||||
"Init Attribute 1": "eight",
|
||||
"Init Attribute 2": "two",
|
||||
};
|
||||
|
||||
void formbricks.init({
|
||||
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
|
||||
userId,
|
||||
attributes: userInitAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
// Connect next.js router to Formbricks
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const handleRouteChange = formbricks.registerRouteChange;
|
||||
|
||||
router.events.on("routeChangeComplete", () => {
|
||||
void handleRouteChange();
|
||||
});
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", () => {
|
||||
void handleRouteChange();
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
addFormbricksDebugParam();
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const userId = "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING";
|
||||
const userInitAttributes = {
|
||||
language: "de",
|
||||
"Init Attribute 1": "eight",
|
||||
"Init Attribute 2": "two",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
|
||||
userId,
|
||||
attributes: userInitAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
// Connect next.js router to Formbricks
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const handleRouteChange = formbricks?.registerRouteChange;
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
initFormbricks();
|
||||
}, [router.events]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
@@ -74,8 +83,11 @@ const AppPage = ({}) => {
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mt-2 rounded-lg bg-slate-200 px-6 py-1 dark:bg-slate-700 dark:text-slate-100"
|
||||
onClick={() => setDarkMode(!darkMode)}>
|
||||
onClick={() => {
|
||||
setDarkMode(!darkMode);
|
||||
}}>
|
||||
{darkMode ? "Toggle Light Mode" : "Toggle Dark Mode"}
|
||||
</button>
|
||||
</div>
|
||||
@@ -96,8 +108,8 @@ const AppPage = ({}) => {
|
||||
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
|
||||
</strong>
|
||||
<span className="relative ml-2 flex h-3 w-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span>
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75" />
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,9 +120,6 @@ const AppPage = ({}) => {
|
||||
Look at the logs to understand how the widget works.{" "}
|
||||
<strong className="dark:text-white">Open your browser console</strong> to see the logs.
|
||||
</p>
|
||||
{/* <div className="max-h-[40vh] overflow-y-auto py-4">
|
||||
<LogsContainer />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,8 +134,9 @@ const AppPage = ({}) => {
|
||||
</p>
|
||||
<button
|
||||
className="my-4 rounded-lg bg-slate-500 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
formbricks.reset();
|
||||
void formbricks.reset();
|
||||
}}>
|
||||
Reset
|
||||
</button>
|
||||
@@ -138,7 +148,9 @@ const AppPage = ({}) => {
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
<button
|
||||
type="button"
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
No-Code Action
|
||||
</button>
|
||||
</div>
|
||||
@@ -147,6 +159,7 @@ const AppPage = ({}) => {
|
||||
This button sends a{" "}
|
||||
<a
|
||||
href="https://formbricks.com/docs/actions/no-code"
|
||||
rel="noopener noreferrer"
|
||||
className="underline dark:text-blue-500"
|
||||
target="_blank">
|
||||
No Code Action
|
||||
@@ -154,6 +167,7 @@ const AppPage = ({}) => {
|
||||
as long as you created it beforehand in the Formbricks App.{" "}
|
||||
<a
|
||||
href="https://formbricks.com/docs/actions/no-code"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="underline dark:text-blue-500">
|
||||
Here are instructions on how to do it.
|
||||
@@ -164,8 +178,9 @@ const AppPage = ({}) => {
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
formbricks.setAttribute("Plan", "Free");
|
||||
void formbricks.setAttribute("Plan", "Free");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Plan to 'Free'
|
||||
@@ -177,6 +192,7 @@ const AppPage = ({}) => {
|
||||
<a
|
||||
href="https://formbricks.com/docs/attributes/custom-attributes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline dark:text-blue-500">
|
||||
attribute
|
||||
</a>{" "}
|
||||
@@ -187,8 +203,9 @@ const AppPage = ({}) => {
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
formbricks.setAttribute("Plan", "Paid");
|
||||
void formbricks.setAttribute("Plan", "Paid");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Plan to 'Paid'
|
||||
@@ -200,6 +217,7 @@ const AppPage = ({}) => {
|
||||
<a
|
||||
href="https://formbricks.com/docs/attributes/custom-attributes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline dark:text-blue-500">
|
||||
attribute
|
||||
</a>{" "}
|
||||
@@ -210,8 +228,9 @@ const AppPage = ({}) => {
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
formbricks.setEmail("test@web.com");
|
||||
void formbricks.setEmail("test@web.com");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Email
|
||||
@@ -223,6 +242,7 @@ const AppPage = ({}) => {
|
||||
<a
|
||||
href="https://formbricks.com/docs/attributes/identify-users"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline dark:text-blue-500">
|
||||
user email
|
||||
</a>{" "}
|
||||
@@ -234,6 +254,4 @@ const AppPage = ({}) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppPage;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_API_HOST=http://localhost:3000
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID=
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID=
|
||||
|
||||
# Strapi API Key
|
||||
STRAPI_API_KEY=
|
||||
|
||||
NEXT_PUBLIC_DOCSEARCH_APP_ID=
|
||||
NEXT_PUBLIC_DOCSEARCH_API_KEY=
|
||||
NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
};
|
||||
38
apps/docs/.gitignore
vendored
@@ -1,38 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
public/sitemap*.xml
|
||||
public/robots.txt
|
||||
@@ -1,129 +0,0 @@
|
||||
# Tailwind UI License
|
||||
|
||||
## Personal License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission to **one individual** (the Licensee) to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components and Templates to create End Products that are designed to allow an End User to build their own End Products using the Components and Templates or derivatives of the Components and Templates.
|
||||
- Re-distribute the Components and Templates or derivatives of the Components and Templates separately from an End Product, neither in code or as design assets.
|
||||
- Share your access to the Components and Templates with any other individuals.
|
||||
- Use the Components and Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a personal website by yourself.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components (like a conference organization app that uses the components for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of usage **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind UI components or templates (or derivatives based on Tailwind UI components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind UI and making it available either for sale or for free.
|
||||
- Create a Figma or Sketch UI kit based on the Tailwind UI component designs.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind UI.
|
||||
- Creating a theme, template, or project starter kit using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
|
||||
In simple terms, use Tailwind UI for anything you like as long as it doesn't compete with Tailwind UI.
|
||||
|
||||
### Personal License Definitions
|
||||
|
||||
Licensee is the individual who has purchased a Personal License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind UI license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Team License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission for **up to 25 Employees and Contractors of the Licensee** to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components or Templates to create End Products that are designed to allow an End User to build their own End Products using the Components or Templates or derivatives of the Components or Templates.
|
||||
- Re-distribute the Components or Templates or derivatives of the Components or Templates separately from an End Product.
|
||||
- Use the Components or Templates to create End Products that are the property of any individual or entity other than the Licensee or Clients of the Licensee.
|
||||
- Use the Components or Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a website for your company.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components or templates (like a conference organization app that uses the components or a template for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of use **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind UI components or template (or derivatives based on Tailwind UI components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind UI and making it available either for sale or for free.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind UI.
|
||||
- Creating a theme or template using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
- Creating any End Product that is not the sole property of either your company or a client of your company. For example your employees/contractors can't use your company Tailwind UI license to build their own websites or side projects.
|
||||
|
||||
### Team License Definitions
|
||||
|
||||
Licensee is the business entity who has purchased a Team License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind UI license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Employee is a full-time or part-time employee of the Licensee.
|
||||
|
||||
Contractor is an individual or business entity contracted to perform services for the Licensee.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Enforcement
|
||||
|
||||
If you are found to be in violation of the license, access to your Tailwind UI account will be terminated, and a refund may be issued at our discretion. When license violation is blatant and malicious (such as intentionally redistributing the Components or Templates through private warez channels), no refund will be issued.
|
||||
|
||||
The copyright of the Components and Templates is owned by Tailwind Labs Inc. You are granted only the permissions described in this license; all other rights are reserved. Tailwind Labs Inc. reserves the right to pursue legal remedies for any unauthorized use of the Components or Templates outside the scope of this license.
|
||||
|
||||
## Liability
|
||||
|
||||
Tailwind Labs Inc.’s liability to you for costs, damages, or other losses arising from your use of the Components or Templates — including third-party claims against you — is limited to a refund of your license fee. Tailwind Labs Inc. may not be held liable for any consequential damages related to your use of the Components or Templates.
|
||||
|
||||
This Agreement is governed by the laws of the Province of Ontario and the applicable laws of Canada. Legal proceedings related to this Agreement may only be brought in the courts of Ontario. You agree to service of process at the e-mail address on your original order.
|
||||
|
||||
## Questions?
|
||||
|
||||
Unsure which license you need, or unsure if your use case is covered by our licenses?
|
||||
|
||||
Email us at [support@tailwindui.com](mailto:support@tailwindui.com) with your questions.
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,121 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Using Actions in Formbricks",
|
||||
description:
|
||||
"Dive deep into how actions in Formbricks help products and organizations to engage users at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine user targeting and generate richer, more detailed user insights.",
|
||||
};
|
||||
|
||||
# Actions
|
||||
|
||||
Actions are predefined events within your app that prompt Formbricks to display a survey when triggered. These are detected by the Formbricks widget, which then presents the appropriate survey based on your predefined settings.
|
||||
|
||||
## **How Do Actions Work?**
|
||||
|
||||
Actions in Formbricks App Surveys are deeply integrated with user activities within your app. When a user performs a specified action, the Formbricks widget detects this activity and can present a survey to that specific user if the trigger conditions match for that survey. This capability ensures that surveys are triggered at the right time. You can set up these actions through a user-friendly No-Code interface within the Formbricks dashboard.
|
||||
|
||||
## **Why Are Actions Useful?**
|
||||
|
||||
Actions are invaluable for enhancing survey relevance and effectiveness:
|
||||
|
||||
- **Personalized Engagement**: Surveys triggered by user actions ensure content is highly relevant and engaging, matching each user’s current context.
|
||||
- **User Attributes**: By tying surveys to specific user attributes, such as activity levels or feature usage, you can customize the survey experience to reflect individual user profiles.
|
||||
- **User Targeting**: Precise targeting based on user attributes ensures that surveys are shown only to users who meet certain criteria, enhancing the relevance and effectiveness of each survey.
|
||||
|
||||
## **Setting Up No-Code Actions**
|
||||
|
||||
Formbricks offers an intuitive No-Code interface that allows you to configure actions without needing to write any code.
|
||||
|
||||
To add a No-Code Action:
|
||||
|
||||
1. Visit the Formbricks Dashboard & switch to the Actions tab:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="setup checklist ui of survey popup for app surveys"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Now click on “Add Action”
|
||||
|
||||
<MdxImage
|
||||
src={I2}
|
||||
alt="setup checklist ui of survey popup for app surveys"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
### **1. Click Action**
|
||||
|
||||
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
|
||||
|
||||
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
### **2. Page view Action**
|
||||
|
||||
This action is triggered when a user visits a page within your application.
|
||||
|
||||
### **3. Exit Intent Action**
|
||||
|
||||
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
|
||||
|
||||
### **4. 50% Scroll Action**
|
||||
|
||||
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
|
||||
|
||||
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
|
||||
|
||||
<Note>
|
||||
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
|
||||
|
||||
### **URL Match Conditions**
|
||||
|
||||
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
|
||||
- **contains**: Activates when the URL contains the specified substring.
|
||||
- **startsWith**: Fires when the URL starts with the specified string.
|
||||
- **endsWith**: Executes when the URL ends with the specified string.
|
||||
- **notMatch**: Triggers when the URL does not match the specified condition.
|
||||
- **notContains**: Activates when the URL does not contain the specified substring.
|
||||
|
||||
</Note>
|
||||
|
||||
## **Setting Up Code Actions**
|
||||
|
||||
For more granular control, you can implement actions directly in your codebase:
|
||||
|
||||
1. **Configure the Action**: First, add the action via the Formbricks web interface to make it available for survey configuration.
|
||||
After that you can fire an action using `formbricks.track()`
|
||||
|
||||
2. **Track an Action**: Use formbricks.track() to send an action event to Formbricks.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Track an action">
|
||||
|
||||
```javascript
|
||||
formbricks.track("Action Name");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Here is an example of how to fire an action when a user clicks a button:
|
||||
<Col>
|
||||
<CodeGroup title="Track Button Click">
|
||||
|
||||
```javascript
|
||||
const handleClick = () => {
|
||||
formbricks.track("Button Clicked");
|
||||
};
|
||||
|
||||
return <button onClick={handleClick}>Click Me</button>;
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,29 +0,0 @@
|
||||
export const metadata = {
|
||||
title: "Advanced Targeting for App Surveys | Formbricks",
|
||||
description:
|
||||
"Advanced Targeting allows you to show surveys to just the right group of people. You can target surveys based on user attributes, metadata, and other segments. This helps you get more relevant feedback and make data-driven decisions.",
|
||||
};
|
||||
|
||||
# Advanced Targeting
|
||||
|
||||
Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, device type, and more instead of spraying and praying. This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code.
|
||||
|
||||
# How to setup Advanced Targeting
|
||||
|
||||
<Note>Advanced Targeting is only available on the Pro plan!</Note>
|
||||
|
||||
1. On the Formbricks dashboard, click on **People** tab from the top navigation bar.
|
||||
|
||||
2. Switch to the **Segments** tab & click on **Create Segment**.
|
||||
|
||||
3. Give your segment a title & a description to help you remember what this segment is about.
|
||||
|
||||
4. Now click on the **Add Filter** button to add a filter. You can filter based on user attributes, other segments, devices, and more.
|
||||
|
||||
5. To group a set of filters together, click on the Three Dots icon on the right side of the filter and click on **Create Group**.
|
||||
|
||||
6. Try playing around with different filters & conditions that we have provided to see how the segment size changes.
|
||||
|
||||
7. Once you are happy with the segment, click on **Save Segment**.
|
||||
|
||||
8. Now, when you create a survey, you can select this segment to target your survey to.
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,66 +0,0 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import logoHtml from "@/images/frameworks/html5.svg";
|
||||
import logoNextjs from "@/images/frameworks/nextjs.svg";
|
||||
import logoReactJs from "@/images/frameworks/reactjs.svg";
|
||||
import logoVueJs from "@/images/frameworks/vuejs.svg";
|
||||
import Image from "next/image";
|
||||
|
||||
const libraries = [
|
||||
{
|
||||
href: "#html",
|
||||
name: "HTML",
|
||||
description: "All you need to do is add 3 lines of code to your HTML script and thats it, you're done!",
|
||||
logo: logoHtml,
|
||||
},
|
||||
{
|
||||
href: "#react-js",
|
||||
name: "React.js",
|
||||
description: "Load the our Js library with your environment ID and you're ready to go!",
|
||||
logo: logoReactJs,
|
||||
},
|
||||
{
|
||||
href: "#next-js",
|
||||
name: "Next.js",
|
||||
description:
|
||||
"Natively add us to your NextJs project with support for both App as well as Pages project structure!",
|
||||
logo: logoNextjs,
|
||||
},
|
||||
{
|
||||
href: "#vue-js",
|
||||
name: "Vue.js",
|
||||
description: "Simply add us to your router change and sit back!",
|
||||
logo: logoVueJs,
|
||||
},
|
||||
{
|
||||
href: "#react-native",
|
||||
name: "React Native",
|
||||
description: "Easily integrate our SDK with your React Native app for seamless survey support!",
|
||||
logo: logoReactJs,
|
||||
},
|
||||
];
|
||||
|
||||
export const Libraries = () => {
|
||||
return (
|
||||
<div className="my-16 xl:max-w-none">
|
||||
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-slate-900/5 xl:max-w-none xl:grid-cols-2 2xl:grid-cols-3 dark:border-white/5">
|
||||
{libraries.map((library) => (
|
||||
<a
|
||||
key={library.name}
|
||||
href={library.href}
|
||||
className="flex flex-row-reverse gap-6 rounded-2xl p-6 transition-all duration-100 ease-in-out hover:cursor-pointer hover:bg-slate-100/50 dark:hover:bg-slate-800/50">
|
||||
<div className="flex-auto">
|
||||
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{library.name}</h3>
|
||||
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">{library.description}</p>
|
||||
<p className="mt-4">
|
||||
<Button href={library.href} variant="text" arrow="right">
|
||||
Read more
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
<Image src={library.logo} alt="" className="h-12 w-12" unoptimized />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,453 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import { Libraries } from "./components/Libraries";
|
||||
|
||||
import ReactApp from "./images/react-in-app-survey-app-popup-form.webp";
|
||||
import WidgetConnected from "./images/widget-connected.webp";
|
||||
import WidgetNotConnected from "./images/widget-not-connected.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Integrate Formbricks: Comprehensive Framework Guide & Integration Tutorial",
|
||||
description:
|
||||
"Master the integration of Formbricks into your application with our detailed guides. From HTML to ReactJS, NextJS, and VueJS, get step-by-step instructions and ensure seamless setup.",
|
||||
};
|
||||
|
||||
# Framework Guides
|
||||
|
||||
One can integrate Formbricks App Survey SDK into their app using multiple options! Checkout the options below that we provide! If you are looking
|
||||
for something else, please [join our Discord!](https://formbricks.com/discord) and we would be glad to help.
|
||||
|
||||
<Libraries />
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before getting started, make sure you have:
|
||||
|
||||
1. A web application (behind your user authentication system) in your desired framework is set up and running.
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings.
|
||||
|
||||
---
|
||||
|
||||
## HTML
|
||||
|
||||
All you need to do is copy a `<script>` tag to your HTML head, and that’s about it!
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="HTML">
|
||||
```html {{ title: 'index.html' }}
|
||||
<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){
|
||||
var apiHost = "https://app.formbricks.com";
|
||||
var environmentId = "<your-environment-id>";
|
||||
var userId = "<your-user-id>"; //optional
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/js/formbricks.umd.cjs";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## ReactJS
|
||||
|
||||
Install the Formbricks SDK using one of the package managers ie `npm`,`pnpm`,`yarn`. Note that zod is required as a peer dependency must also be installed in your project.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Now, update your App.js/ts file to initialise Formbricks.
|
||||
<Col>
|
||||
<CodeGroup title="src/App.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
function App() {
|
||||
// your own app
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## NextJS
|
||||
|
||||
NextJs projects typically follow two main conventions: the App Directory and the Pages Directory.
|
||||
To ensure smooth integration with the Formbricks SDK, which operates solely on the client side, follow the
|
||||
guidelines for each convention below:
|
||||
|
||||
- App directory: You will have to define a new component in `app/formbricks.tsx` file and call it in your `app/layout.tsx` file.
|
||||
- Pages directory: You will have to visit your `_app.tsx` and just initialise Formbricks there.
|
||||
|
||||
Code snippets for the integration for both conventions are provided to further assist you.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
### App Directory
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="app/formbricks.tsx">
|
||||
|
||||
```tsx {{title: 'Typescript'}}
|
||||
"use client";
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
export default function FormbricksProvider() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
formbricks?.registerRouteChange();
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="app/layout.tsx">
|
||||
```tsx {{title: 'Typescript'}}
|
||||
// other imports
|
||||
import FormbricksProvider from "./formbricks";
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<FormbricksProvider />
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
### Pages Directory
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="src/pages/_app.tsx">
|
||||
|
||||
```tsx {{ title: 'Typescript' }}
|
||||
// other import
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// Connect next.js router to Formbricks
|
||||
const handleRouteChange = formbricks?.registerRouteChange;
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, []);
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
First we initialize the Formbricks SDK, ensuring that it only runs on the client side.
|
||||
To connect the Next.js router to Formbricks and ensure the SDK can keep track of every page change, we are registering the route change event.
|
||||
|
||||
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## VueJs
|
||||
|
||||
Integrating the Formbricks SDK with Vue.js is a straightforward process.
|
||||
We will make sure the SDK is only loaded and used on the client side, as it's not intended for server-side usage.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js
|
||||
````
|
||||
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
```
|
||||
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="src/formbricks.js">
|
||||
|
||||
```js
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
export default formbricks;
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="src/main.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import formbricks from "@/formbricks";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
if (typeof formbricks !== "undefined") {
|
||||
formbricks.registerRouteChange();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
## React Native
|
||||
|
||||
Install the Formbricks React Native SDK using one of the package managers, i.e., npm, pnpm, or yarn.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/react-native
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/react-native
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/react-native
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Now, update your App.js/App.tsx file to initialize Formbricks:
|
||||
<Col>
|
||||
<CodeGroup title="src/App.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import Formbricks from "@formbricks/react-native";
|
||||
|
||||
const config = {
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<>
|
||||
{/* Your app content */}
|
||||
<Formbricks initConfig={config} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
---
|
||||
|
||||
## Validate your setup
|
||||
|
||||
Once you have completed the steps above, you can validate your setup by checking the **Setup Checklist** in the Settings. Your widget status indicator should go from this:
|
||||
|
||||
<MdxImage
|
||||
src={WidgetNotConnected}
|
||||
alt="Widget isnt connected"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
To this:
|
||||
|
||||
<MdxImage
|
||||
src={WidgetConnected}
|
||||
alt="Widget is connected"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Debugging Formbricks Integration
|
||||
|
||||
Enabling Formbricks debug mode in your browser is a useful troubleshooting step for identifying and resolving complex issues. This section outlines how to activate debug mode, covers common use cases, and provides insights into specific debug log messages.
|
||||
|
||||
### Activate Debug Mode
|
||||
|
||||
To activate Formbricks debug mode:
|
||||
|
||||
1. **Via URL Parameter:**
|
||||
|
||||
- Enable debug mode mode by adding `?formbricksDebug=true` to your application's URL (e.g. `https://example.com?formbricksDebug=true` or `https://example.com?page=123&formbricksDebug=true`). This parameter will enable debugging for the current page.
|
||||
|
||||
2. **View Debug Logs:**
|
||||
|
||||
- Open your browser's developer tools by pressing `F12` or right-clicking and selecting "Inspect."
|
||||
- Navigate to the "Console" tab to view Formbricks debugging information.
|
||||
|
||||
**How to Open Browser Console:**
|
||||
|
||||
- **Google Chrome:** Press `F12` or right-click, select "Inspect," and go to the "Console" tab.
|
||||
- **Firefox:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
|
||||
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
Debug mode is beneficial for scenarios such as:
|
||||
|
||||
- Verifying Formbricks initialization.
|
||||
- Identifying survey trigger issues.
|
||||
- Troubleshooting unexpected behavior.
|
||||
|
||||
### Debug Log Messages
|
||||
|
||||
Debug log messages provide insights into:
|
||||
|
||||
- API calls and responses.
|
||||
- Event tracking, survey triggers and form interactions.
|
||||
- Initialization errors.
|
||||
|
||||
**Can’t figure it out? [Join our Discord!](https://formbricks.com/discord)**
|
||||
|
||||
---
|
||||
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,115 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
import I3 from "./images/I3.webp";
|
||||
import I3_1 from "./images/I3_1.webp";
|
||||
import I4 from "./images/I4.webp";
|
||||
import I5 from "./images/I5.webp";
|
||||
import I6 from "./images/I6.webp";
|
||||
import I7 from "./images/I7.webp";
|
||||
import I8 from "./images/I8.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Quickstart Guide: App Surveys Made Easier & Faster",
|
||||
description:
|
||||
"Formbricks is the easiest way to create and manage app surveys. This quickstart guide will show you how to create your first app survey in under 5 minutes.",
|
||||
};
|
||||
|
||||
# Quickstart
|
||||
|
||||
App surveys have 6-10x better conversion rates than emailed surveys. This tutorial explains how to run a survey in both your web app and mobile app (React Native) in just 10 to 15 minutes. Let’s go!
|
||||
|
||||
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, choose the "Formbricks Surveys" option:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="Choose Formbricks Surveys"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **Choose your Product Channel**: On this step, you have to choose between the various channels that you want your product to be created in, you can create both app and link surveys from all the channels, but for the onboarding, please choose between the app surveys or the public website options, upon doing this, you'll be prompted to connect your app / website to formbricks.
|
||||
|
||||
<MdxImage
|
||||
src={I2}
|
||||
alt="Choose between app and website surveys product channels"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Connect your App/Website**: Once you get through a couple of onboarding steps, you’ll be asked to connect your app or website. This is where you’ll find the code snippet for both HTML as well as the npm package which you need to embed in your app:
|
||||
|
||||
<MdxImage
|
||||
src={I3}
|
||||
alt="Code snippet for connecting app with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Paste the code snippet in your app and reload the page. You should now see a message being displayed that your app or website is now connected with formbricks.
|
||||
|
||||
<MdxImage
|
||||
src={I3_1}
|
||||
alt="App connected with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Onboarding is complete! Now let’s create our first survey as you should see templates to choose from after clicking on **Next**:
|
||||
|
||||
<MdxImage
|
||||
src={I4}
|
||||
alt="Choose a survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. **Create your first survey**: To be able to see a survey in your app, you need to create one. We’ll choose one of the templates and head over to the survey settings:
|
||||
|
||||
Pick the Survey Type as **App Survey**.
|
||||
|
||||
<MdxImage
|
||||
src={I5}
|
||||
alt="Survey settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
|
||||
|
||||
<MdxImage
|
||||
src={I6}
|
||||
alt="Survey trigger settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once to each user to prevent survey fatigue:
|
||||
|
||||
<Note>
|
||||
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
|
||||
</Note>
|
||||
|
||||
<MdxImage
|
||||
src={I7}
|
||||
alt="Recontact options for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
7. **Publish your survey**: Now hit **Publish** and you’ll be forwarded to the Summary Page. This is where you’ll find the responses to this survey.
|
||||
|
||||
<MdxImage
|
||||
src={I8}
|
||||
alt="Survey published successfully"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
- We offer framework guides for various frontend tech, head over to the the [App Survey Framework Guides](/app-surveys/framework-guides) to get started with your app survey.
|
||||
- Head over to our JS SDK documentation to get started with the [JS SDK](/developer-docs/js-sdk).
|
||||
|
||||
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!
|
||||
@@ -1,105 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import AppSurvey from "./app-survey.webp";
|
||||
import GlobalWaitTime from "./global-wait-time.webp";
|
||||
import IgnoreWaitTime from "./ignore-wait-time.webp";
|
||||
import SurveyRecontact from "./survey-recontact.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Recontact Options in Formbricks: When to show a survey again to a user",
|
||||
description:
|
||||
"Explore how to configure Recontact options in Formbricks to control the frequency of survey exposure to users, ensuring effective feedback collection without compromising user experience.",
|
||||
};
|
||||
|
||||
# Recontact Options
|
||||
|
||||
Recontact options in Formbricks enable you to manage how often and under what conditions a survey is shown to a user. This feature is crucial for balancing effective feedback collection with a positive user experience by preventing survey fatigue.
|
||||
|
||||
## When do Recontact Options come into play?
|
||||
|
||||
Recontact options are the last layer of the logic that determines if a survey is shown to the current user. The logic goes as follows:
|
||||
|
||||
1. Targeting: Does the current user targeted to fill out this survey? If yes...
|
||||
2. Trigger: Is the survey triggered? If yes...
|
||||
3. **Recontact Options:** Should the survey be shown (again) to this user? That's dependent on:
|
||||
|
||||
- Did the user see any survey recently (meaning, has Global Waiting Time passed)?
|
||||
- Did the user see this specific survey already?
|
||||
- How many times did the user see this specific survey already?
|
||||
- Has the user already responded to this survey?
|
||||
|
||||
As you can see, there are a lot of different cases to cover. Let's have a closer look 👇
|
||||
|
||||
## Recontact Options
|
||||
|
||||
<Note>By default, a survey is shown to each user only once.</Note>
|
||||
|
||||
You can adjust the default behavior by modifying the Recontact Options for each survey in the settings:
|
||||
|
||||
1. Open the Survey Editor for the survey you want to see & modify the Recontact Options for.
|
||||
2. Select the Settings Tab.
|
||||
3. Ensure your Survey type is set to **App Survey**.
|
||||
|
||||
<MdxImage
|
||||
src={AppSurvey}
|
||||
alt="Choose Survey Type as App Survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. Scroll down to the Recontact Options section.
|
||||
|
||||
Available Recontact Options include:
|
||||
|
||||
- **Show only once**: (default) Displays the survey a single time, regardless of whether it was completed.
|
||||
- **Until they Submit a Response**: If tareting matches and trigger fires, Formbricks keeps showing the survey until the user submits a response.
|
||||
- **Keep Showing while Conditions Match**: Always shows the survey while specific conditions are met. Useful for continuous feedback collection, such as in [Docs Feedback Survey](/best-practices/docs-feedback) or the [Feedback Box](/best-practices/feedback-box).
|
||||
|
||||
<MdxImage
|
||||
src={SurveyRecontact}
|
||||
alt="Choose Recontanct Options for the Survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Product-wide Global Waiting Time
|
||||
|
||||
The Global Waiting Time is a universal blocker to make sure that no user sees too many surveys. This is particularly helpful when several teams of large organisations use Formbricks at the same time.
|
||||
|
||||
<Note>The default Global Waiting Time is set to 7 days.</Note>
|
||||
|
||||
To adjust the Global Waiting Time:
|
||||
|
||||
1. Visit Formbricks Settings
|
||||
2. Go to Product Settings
|
||||
3. Find the **Recontact Waiting Time** section
|
||||
4. Modify the interval (in days) as needed.
|
||||
|
||||
<MdxImage
|
||||
src={GlobalWaitTime}
|
||||
alt="Formbricks Product-Wide Wait Time"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Overriding Global Waiting Time for a Specific Survey
|
||||
|
||||
For specific surveys, you may need to override the default waiting time. Below is how you can do that:
|
||||
|
||||
1. In the Survey Editor, access the Settings Tab.
|
||||
2. Find the Ignore Waiting Time between Surveys toggle under Recontact Options.
|
||||
3. Enable this toggle to bypass the global setting.
|
||||
4. Set a custom recontact period:
|
||||
- **Always Show Survey**: Displays the survey whenever triggered, ignoring the waiting time.
|
||||
- **Wait `X` days before showing this survey again**: Sets a specific interval before the survey can be shown again.
|
||||
|
||||
<MdxImage
|
||||
src={IgnoreWaitTime}
|
||||
alt="Ignore Global Waiting Time for a Specific Survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!
|
||||
@@ -1,116 +0,0 @@
|
||||
export const metadata = {
|
||||
title: "Identifying Users in Formbricks App Surveys",
|
||||
description:
|
||||
"Dive into the importance of user identification in surveys. Boost your survey response rates and target the right users with Formbricks.",
|
||||
};
|
||||
|
||||
# User Identification
|
||||
|
||||
User Identification helps you to not only segment your users but also to see more information about the user who responded to a survey. This helps you to target surveys to specific user segments and see more information about the user who responded to a survey.
|
||||
|
||||
### Understanding Identified vs Unidentified Users
|
||||
|
||||
In Formbricks, understanding the distinction between identified and unidentified users is crucial for effective survey segmentation and targeted feedback collection.
|
||||
|
||||
| Feature | Unidentified Users | Identified Users |
|
||||
| -------------------------------------------------------- | ------------------ | ---------------- |
|
||||
| Show surveys based on **trigger** actions | ✅ | ✅ |
|
||||
| Set **recontact details** to avoid survey fatique | ✅ | ✅ |
|
||||
| Target a subset of users using **attributes & segments** | ❌ | ✅ |
|
||||
| Collect **user information** in Formbricks | ❌ | ✅ |
|
||||
| Track **custom attributes** | ❌ | ✅ |
|
||||
| Counts towards your **monthly tacked user (MTU)** limit | ❌ | ✅ |
|
||||
| Recommended for **public-facing websites** | ✅ | ❌ |
|
||||
| Recommended for **web apps after login** | ❌ | ✅ |
|
||||
|
||||
## Identified Users
|
||||
|
||||
Identified users are those for whom specific information has been set, notably the userId. This identification allows for more precise targeting of surveys and a deeper understanding of the feedback provided. When enabled, all information specified by you and all actions are sent to Formbricks.
|
||||
|
||||
This method is recommended for applications where users are required to log in and will often return.
|
||||
|
||||
### Setting User ID
|
||||
|
||||
To enable the User identification feature you need to set the `userId` in the init() call of Formbricks. Only when the `userId` is set the person will be visible in the Formbricks dashboard. The `userId` can be any string and it's best to use the default identifier you use in your app (e.g. unique id from database or the email address if it's unique) but you can also anonymize these as long as they are unique for every user.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Setting User ID">
|
||||
|
||||
```javascript
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user_id>",
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
### Enhanced Initialization with User Attributes
|
||||
|
||||
In addition to setting the `userId`, Formbricks allows you to set user attributes right at the initialization. This ensures that your user data is seamlessly integrated from the start. Here's how you can include user attributes in the `init()` function:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Enhanced Initialization with User Attributes">
|
||||
|
||||
```javascript
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user_id>",
|
||||
attributes: {
|
||||
// your custom attributes
|
||||
Plan: "premium",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Setting Custom User Attributes
|
||||
|
||||
You can use the setAttribute function to set any custom attribute for the user (e.g. name, plan, etc.):
|
||||
|
||||
<Note>
|
||||
Please note that the number of different attribute classes (e.g., "Plan," "First Name," etc.) is currently
|
||||
limited to 150 attributes per environment.
|
||||
</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Setting Custom Attributes">
|
||||
|
||||
```javascript
|
||||
formbricks.setAttribute("Plan", "free");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
Generally speaking, the setAttribute function works like this:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Setting Custom Attributes">
|
||||
|
||||
```javascript
|
||||
formbricks.setAttribute("attribute_key", "attribute_value");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Where `attributeName` is the name of the attribute you want to set, and `attributeValue` is the value of the attribute you want to set.
|
||||
|
||||
### Logging Out Users
|
||||
|
||||
When a user logs out of your webpage, make sure to log them out of Formbricks as well. This will prevent new activity from being associated with an incorrect user. Use the logout function:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Logging out User">
|
||||
|
||||
```javascript
|
||||
formbricks.logout();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
@@ -1,158 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import ChangeText from "./change-text.webp";
|
||||
import CreateChurnFlow from "./create-cancel-flow.webp";
|
||||
import PublishSurvey from "./publish-survey.webp";
|
||||
import RecontactOptions from "./recontact-options.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
import TriggerCSS from "./trigger-css-selector.webp";
|
||||
import TriggerInnerText from "./trigger-inner-text.webp";
|
||||
import TriggerPageUrl from "./trigger-page-url.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Mastering Churn Surveys with Formbricks | Essential Tips & Steps",
|
||||
description:
|
||||
"Learn how to effectively utilize Formbricks' Churn Surveys to gain deeper insights into user departures. Dive into a step-by-step guide to craft, trigger, and optimize your churn surveys, ensuring you capture invaluable feedback at critical junctures",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Learn from Churn
|
||||
|
||||
Churn is hard, but can teach you a lot. Whenever a user decides that your product isn’t worth it anymore, you have a unique opportunity to get deep insights. These insights are pure gold to reduce churn.
|
||||
|
||||
## Purpose
|
||||
|
||||
The Churn Survey is among the most effective ways to identify weaknesses in your offering. People were willing to pay but now are not anymore: What changed? Let’s find out!
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Ask at exactly the right point in time
|
||||
- Follow-up to prevent bad reviews
|
||||
- Coming soon: Make survey mandatory
|
||||
|
||||
## Overview
|
||||
|
||||
To run the Churn Survey in your app you want to proceed as follows:
|
||||
|
||||
1. Create new Churn Survey at [app.formbricks.com](https://app.formbricks.com/)
|
||||
2. Set up the user action to display survey at right point in time
|
||||
3. Choose correct recontact options to never miss a feedback
|
||||
4. Prevent that churn!
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running? We assume that you have already installed the Formbricks Widget in your web app. 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
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Click on "Create Survey" and choose the template “Churn Survey”:
|
||||
|
||||
<MdxImage
|
||||
src={CreateChurnFlow}
|
||||
alt="Create churn survey by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update questions (if you like)
|
||||
|
||||
You’re free to update the question and answer options. However, based on our experience, we suggest giving the provided template a go 😊
|
||||
|
||||
<MdxImage
|
||||
src={ChangeText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
_Want to change the button color? You can do so in the product settings._
|
||||
|
||||
Save, and move over to the “Audience” tab.
|
||||
|
||||
### 3. Pre-segment your audience
|
||||
|
||||
In this case, you don’t really need to pre-segment your audience. You likely want to ask everyone who hits the “Cancel subscription” button.
|
||||
|
||||
### 4. Set up a trigger
|
||||
|
||||
To create the trigger for your Churn Survey, you have three options to choose from:
|
||||
|
||||
1. **Trigger by Inner Text:** You likely have a “Cancel Subscription” button in your app. You can setup a user Action with the according `Inner Text` to trigger the survey, like so:
|
||||
|
||||
<MdxImage
|
||||
src={TriggerInnerText}
|
||||
alt="Set the trigger by inner Text"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **Trigger by CSS Selector:** In case you have more than one button saying “Cancel Subscription” in your app and only want to display the survey when one of them is clicked, you want to be more specific. The best way to do that is to give this button the HTML `id=“cancel-subscription”` and set your user action up like so:
|
||||
|
||||
<MdxImage
|
||||
src={TriggerCSS}
|
||||
alt="Set the trigger by CSS Selector"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
1. **Trigger by page view filters:** Lastly, you could also display your survey on a subpage “/subscription-cancelled” where you forward users once they cancelled the trial subscription. You can then create a user Action with the type `Page View` and add select `Limit to specific pages` to add url filters, with the following settings:
|
||||
|
||||
<MdxImage
|
||||
src={TriggerPageUrl}
|
||||
alt="Set the trigger by page URL"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Whenever a user visits this page, matches the filter conditions above and the recontact options (below) the survey will be displayed ✅
|
||||
|
||||
Here is our complete [Actions manual](/app-surveys/actions/) covering [No-Code](/app-surveys/actions#setting-up-no-code-actions) and [Code](/app-surveys/actions#setting-up-code-actions) Actions.
|
||||
|
||||
<Note>
|
||||
## Pre-churn flow coming soon We’re currently building full-screen survey pop-ups. You’ll be able to prevent
|
||||
users from closing the survey unless they respond to it. It’s certainly debatable if you want that but you
|
||||
could force them to click through the survey before letting them cancel 🤷
|
||||
</Note>
|
||||
|
||||
### 5. Select Action in the “When to ask” card
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select feedback button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 6. Last step: Set Recontact Options correctly
|
||||
|
||||
Lastly, scroll down to “Recontact Options”. Here you have to choose the correct settings to make sure you milk these super valuable insights. You want to make sure that this survey is always displayed, no matter if the user has already seen a survey in the past days:
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
These settings make sure the survey is always displayed, when a user wants to Cancel their subscription.
|
||||
|
||||
### 7. Congrats! You’re ready to publish your survey 💃
|
||||
|
||||
<MdxImage
|
||||
src={PublishSurvey}
|
||||
alt="Publish survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running? You need to have the Formbricks Widget installed to display the Churn Survey
|
||||
in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# Get those insights! 🎉
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,171 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
import AddQuestion from "./images/add-question.webp";
|
||||
import EmailField from "./images/email-field.webp";
|
||||
import EmbedImage from "./images/embed.webp";
|
||||
import MessageField from "./images/message-field.webp";
|
||||
import NameField from "./images/name-field.webp";
|
||||
import QueryImage from "./images/query-form.webp";
|
||||
import SingleSelect from "./images/single-select-questionare.webp";
|
||||
import ToggleButton from "./images/welcome1.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Creating a Contact Form with Formbricks: A Step-by-Step Guide",
|
||||
description:
|
||||
"Learn how to easily create a professional contact form using Formbricks, perfect for beginners and experts alike.",
|
||||
};
|
||||
|
||||
# Creating a Contact Form with Formbricks: A Step-by-Step Guide
|
||||
|
||||
Welcome to this comprehensive guide on creating a contact form using Formbricks. Whether you're just starting out or you're a seasoned developer, this tutorial will walk you through every step of building an engaging and effective contact form.
|
||||
|
||||
## What We’ll Build
|
||||
|
||||
By the end of this tutorial, you'll have created a simple contact form featuring:
|
||||
1. A welcoming introduction.
|
||||
2. Fields for collecting the user's name and email.
|
||||
3. A question to find out why they’re contacting you.
|
||||
4. A message field for users to share their queries.
|
||||
|
||||
### Setting Up Your Form
|
||||
|
||||
First, let's lay the groundwork for your form:
|
||||
|
||||
1. Head to the Surveys page and click on **New Survey**.
|
||||
2. Select **Start from Scratch** to create a new form.
|
||||
3. In the form editor, click the three dots next to a question, then select **Change Question Type** and choose **Statement (Call to Action)**.
|
||||
|
||||
<MdxImage
|
||||
src={ToggleButton}
|
||||
alt="Toggle button for Statement (Call to Action)"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. Add a welcoming statement to greet your users and explain the form's purpose.
|
||||
5. Personalize the greeting to make it inviting and encourage engagement.
|
||||
|
||||
<Note>
|
||||
A warm welcome sets the tone for your form. Make it friendly to encourage users to participate.
|
||||
</Note>
|
||||
|
||||
### Adding the Name Field
|
||||
|
||||
Next, let's capture the user's name:
|
||||
|
||||
1. Click **Add Question**.
|
||||
<MdxImage
|
||||
src={AddQuestion}
|
||||
alt="Adding a question in Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
2. Enter the prompts for the name field.
|
||||
3. Turn off the **Long Answer** option at the bottom right.
|
||||
4. Adjust any **settings**, such as making the field required.
|
||||
|
||||
<MdxImage
|
||||
src={NameField}
|
||||
alt="Name field configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### Adding the Email Field
|
||||
|
||||
Now, let’s add a field to collect the user's email address:
|
||||
|
||||
1. Click **Add Question** again.
|
||||
2. Select Email as the input type.
|
||||
3. Enter a prompt for the email field.
|
||||
|
||||
<MdxImage
|
||||
src={EmailField}
|
||||
alt="Email field configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### Adding a Reason for Contact
|
||||
|
||||
Let’s now understand why the user is contacting you:
|
||||
|
||||
1. Click **Add Question** once again.
|
||||
2. Select **Change Question** Type and choose **Single Select**.
|
||||
3. Add the question "Why are you contacting us today?"
|
||||
|
||||
<MdxImage
|
||||
src={SingleSelect}
|
||||
alt="Single Select question configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
<Note>Predefined options help categorize inquiries, making it easier for you to respond appropriately.</Note>
|
||||
|
||||
4. Add options like "General Inquiry," "Support," and "Feedback."
|
||||
|
||||
<MdxImage
|
||||
src={QueryImage}
|
||||
alt="Single Select question configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### Adding a Message Field
|
||||
|
||||
Finally, let’s provide a space for the user’s message:
|
||||
|
||||
1. Click **Add Question** for the last time.
|
||||
2. Add the question: "Your Message."
|
||||
3. Set the placeholder text to something like "Please write your message here."
|
||||
|
||||
<MdxImage
|
||||
src={MessageField}
|
||||
alt="Message field configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. Consider setting a minimum character count to ensure detailed messages.
|
||||
|
||||
### Finalizing Your Form
|
||||
|
||||
Once your form is complete, follow these final steps:
|
||||
|
||||
1. Review and rearrange the questions if necessary.
|
||||
2. Test the form by filling it out as a user.
|
||||
3. Customize the **Thank You** message for submissions.
|
||||
4. Publish the form to get a shareable link.
|
||||
5. Enable submission notifications:
|
||||
- Go to your Formbricks account settings.
|
||||
- Verify your email address.
|
||||
- Ensure that **Survey** notifications are enabled.
|
||||
|
||||
### Integrating the Contact Form into Your Website
|
||||
|
||||
After publishing the form, follow these steps to integrate it into your site:
|
||||
|
||||
1. **Copy the Shareable Link**
|
||||
- Find your form in the Formbricks dashboard, and click Share.
|
||||
- Select Embed in a Web Page.
|
||||
|
||||
<MdxImage
|
||||
src={EmbedImage}
|
||||
alt="Embed Image configuration"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **Embed the Code**
|
||||
- Copy the provided code and paste it into your website where you want the form to appear.
|
||||
|
||||
<Note>Note: There is an options toggle button called "Embed Mode." When enabled, it updates the `src` to `"?embed=true"` and displays your survey in a minimalist design, removing padding and background for a cleaner look.</Note>
|
||||
|
||||
3. **Test the Integration**
|
||||
- Check if the form displays correctly on your site.
|
||||
- Submit a test entry to ensure everything works and notifications are received.
|
||||
|
||||
## Conclusion
|
||||
Congratulations! You’ve successfully created and integrated a professional contact form using Formbricks. This form will help you collect valuable information from your visitors in an efficient, user-friendly way.
|
||||
|
||||
A great contact form strikes the balance between collecting necessary details and being simple enough to encourage submissions. **You’ve achieved just that!**
|
||||
@@ -1,441 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import AddAction from "./add-action.webp";
|
||||
import ChangeId from "./change-id.webp";
|
||||
import CopyIds from "./copy-ids.webp";
|
||||
import DocsNavi from "./docs-navi.webp";
|
||||
import DocsTemplate from "./docs-template.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
import SurveyTrigger from "./survey-trigger.webp";
|
||||
import SwitchToDev from "./switch-to-dev.webp";
|
||||
|
||||
export const metadata = {
|
||||
title:
|
||||
"Integrate Docs Feedback in Your Website: A Step-by-Step Guide on getting feedback on your Documentation with Formbricks",
|
||||
description:
|
||||
"Learn the step-by-step process to effectively measure the clarity of your documentation using Formbricks Cloud. Dive into best practices, setting up cloud environments, integrating feedback widgets on your frontend, and connecting to the Formbricks API for a seamless user experience.",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Docs Feedback
|
||||
|
||||
Docs Feedback allows you to measure how clear your documentation is.
|
||||
|
||||
## Purpose
|
||||
|
||||
Unlike yourself, your users don't spend 5-7 days per week thinking about your product. To fight the "Curse of Knowledge" you have to measure how clear your docs are.
|
||||
|
||||
## Installation
|
||||
|
||||
To get this running, you'll need a bit of time. Here are the steps we're going through:
|
||||
|
||||
1. Set up Formbricks Cloud
|
||||
2. Build the frontend
|
||||
3. Connect to API
|
||||
4. Test
|
||||
|
||||
## 1. Setting up Formbricks Cloud
|
||||
|
||||
1. To get started, create an account for the [Formbricks Cloud](https://app.formbricks.com/auth/signup).
|
||||
|
||||
2. In the Menu (top right) you see that you can toggle switch between a “Development” and a “Production” environment. These are two separate environments so your test data doesn’t mess up the insights from prod. Switch to “Development”:
|
||||
|
||||
<MdxImage
|
||||
src={SwitchToDev}
|
||||
alt="switch to dev environment"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xs"
|
||||
/>
|
||||
|
||||
3. Then, create a survey using the template “Docs Feedback”:
|
||||
|
||||
<MdxImage
|
||||
src={DocsTemplate}
|
||||
alt="select docs template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. Change the Internal Question ID of the first question to **“isHelpful”** to make your life easier 😉
|
||||
|
||||
<MdxImage
|
||||
src={ChangeId}
|
||||
alt="switch to dev environment"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. In the same way, you can change the Internal Question ID of the _Please elaborate_ question to **“additionalFeedback”** and the one of the _Page URL_ question to **“pageUrl”**.
|
||||
|
||||
<Note>
|
||||
Answers need to be identical If you want different answers than “Yes 👍” and “No 👎” you need to update the
|
||||
choices accordingly. They have to be identical to the frontend we're building in the next step.
|
||||
</Note>
|
||||
|
||||
6. Click on “Continue to Settings or select the audience tab manually. Scroll down to “Survey Trigger” and create a new Action:
|
||||
|
||||
<MdxImage
|
||||
src={SurveyTrigger}
|
||||
alt="set up when to ask card"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
7. Our goal is to create an event that never fires. This is a bit nonsensical because it is a workaround. Stick with me 😃 Fill the action out like on the screenshot:
|
||||
|
||||
<MdxImage src={AddAction} alt="add action" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
8. Select the Non-Event in the dropdown. Now you see that the “Publish survey” button is active. Publish your survey 🤝
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="select nonevent"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
**You’re all setup in Formbricks Cloud for now 👍**
|
||||
|
||||
## 2. Build the frontend
|
||||
|
||||
<Note>
|
||||
Your frontend might work differently Your frontend likely looks and works differently. This is an example
|
||||
specific to our tech stack. We want to illustrate what you should consider building yours 😊
|
||||
</Note>
|
||||
|
||||
Before we start, lets talk about the widget. It works like this:
|
||||
|
||||
- Once the user selects yes/no, a partial response is sent to the Formbricks API. It includes the feedback and the current page url.
|
||||
- Then the user is presented with an additional open text field to further explain their choice. Once it's submitted, the previous response is updated with the additional feedback.
|
||||
|
||||
This allows us to capture and analyze partial feedback where the user is not willing to provide additional information.
|
||||
|
||||
**Let's do this 👇**
|
||||
|
||||
1. Open the code editor where you handle your docs page.
|
||||
|
||||
2. Likely, you have a template file or similar which renders the navigation at the bottom of the page:
|
||||
|
||||
<MdxImage src={DocsNavi} alt="doc navigation" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
Locate that file. We are using the [Tailwind Template “Syntax”](https://tailwindui.com/templates/syntax) for our docs.
|
||||
|
||||
3. Write the frontend code for the widget. Here is the full component (we break it down right below):
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Entire Widget">
|
||||
|
||||
```tsx
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/components/Popover";
|
||||
import { handleFeedbackSubmit, updateFeedback } from "../../lib/handleFeedbackSubmit";
|
||||
|
||||
export const DocsFeedback = () => {
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [sharedFeedback, setSharedFeedback] = useState(false);
|
||||
const [responseId, setResponseId] = useState(null);
|
||||
const [freeText, setFreeText] = useState("");
|
||||
|
||||
if (
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID ||
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST ||
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 inline-flex cursor-default items-center rounded-md border border-slate-200 bg-white p-4 text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300">
|
||||
{!sharedFeedback ? (
|
||||
<div>
|
||||
Was this page helpful?
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<div className="ml-4 inline-flex space-x-3">
|
||||
{["Yes 👍", " No 👎"].map((option) => (
|
||||
<PopoverTrigger
|
||||
className="rounded border border-slate-200 bg-slate-50 px-4 py-2 text-slate-900 hover:bg-slate-100 hover:text-slate-600 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1 dark:border-slate-700 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600 dark:hover:text-slate-300"
|
||||
onClick={async () => {
|
||||
const id = await handleFeedbackSubmit(option, router.asPath);
|
||||
setResponseId(id);
|
||||
}}>
|
||||
{option}
|
||||
</PopoverTrigger>
|
||||
))}
|
||||
</div>
|
||||
<PopoverContent className="border-slate-300 bg-white dark:border-slate-500 dark:bg-slate-700">
|
||||
<form>
|
||||
<textarea
|
||||
value={freeText}
|
||||
onChange={(e) => setFreeText(e.target.value)}
|
||||
placeholder="Please explain why..."
|
||||
className="focus:border-brand-dark focus:ring-brand-dark mb-2 w-full rounded-md bg-white text-sm text-slate-900 dark:bg-slate-600 dark:text-slate-200 dark:placeholder:text-slate-200"
|
||||
/>
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
updateFeedback(freeText, responseId);
|
||||
setIsOpen(false);
|
||||
setFreeText("");
|
||||
setSharedFeedback(true);
|
||||
}}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
) : (
|
||||
<div>Thanks a lot, boss! 🤝</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
**Let’s break it down!**
|
||||
|
||||
Setting the local states and getting the current URL:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="State Management">
|
||||
|
||||
```tsx
|
||||
const router = useRouter(); // to get the URL of the current docs page
|
||||
const [isOpen, setIsOpen] = useState(false); // to close Popover after
|
||||
const [sharedFeedback, setSharedFeedback] = useState(false); // to display Thank You message
|
||||
const [responseId, setResponseId] = useState(null); // to store responseID (will explain more)
|
||||
const [freeText, setFreeText] = useState(""); // to locally store the additional info provided by user
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Disabling feedback if config environment variables are not set properly:
|
||||
<Col>
|
||||
<CodeGroup title="Disable feedback if incorrect config env vars">
|
||||
|
||||
```tsx
|
||||
if (
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID ||
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST ||
|
||||
!process.env.NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
The actual frontend (read comments):
|
||||
<Col>
|
||||
<CodeGroup title="Actual Frontend">
|
||||
|
||||
```tsx
|
||||
return (
|
||||
<div className="mt-6 inline-flex cursor-default items-center rounded-md border border-slate-200 bg-white p-4 text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300">
|
||||
{!sharedFeedback ? ( // displays Feedback buttons or Thank You message
|
||||
<div>
|
||||
Was this page helpful?
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<div className="ml-4 inline-flex space-x-3">
|
||||
{["Yes 👍", " No 👎"].map((option) => ( // Popup Trigger is a button as well. This is a workaround to open the same form but send a different response to the API
|
||||
<PopoverTrigger
|
||||
className="rounded border border-slate-200 bg-slate-50 px-4 py-2 text-slate-900 hover:bg-slate-100 hover:text-slate-600 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1 dark:border-slate-700 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600 dark:hover:text-slate-300"
|
||||
onClick={async () => {
|
||||
const id = await handleFeedbackSubmit(option, router.asPath); // handleFeedbackSubmit sends the Yes / No choice as well as the current URL to Formbricks and returns the responseId
|
||||
setResponseId(id); // add responseId to local state so we can use it if user decides to add more feedback in free text field
|
||||
}}>
|
||||
{option} // "Yes 👍" or "No 👎" - they have to be identical with the choices in the survey on app.formbricks.com for it to work (!)
|
||||
</PopoverTrigger>
|
||||
))}
|
||||
</div>
|
||||
<PopoverContent className="border-slate-300 bg-white dark:border-slate-500 dark:bg-slate-700">
|
||||
<form> // Form to handle additional feedback by user
|
||||
<textarea
|
||||
value={freeText}
|
||||
onChange={(e) => setFreeText(e.target.value)}
|
||||
placeholder="Please explain why..."
|
||||
className="focus:border-brand-dark focus:ring-brand-dark mb-2 w-full rounded-md bg-white text-sm text-slate-900 dark:bg-slate-600 dark:text-slate-200 dark:placeholder:text-slate-200"
|
||||
/>
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // prevent page from reloading (default HTML behaviour)
|
||||
updateFeedback(freeText, responseId); // update initial Yes / No response with free text feedback
|
||||
setIsOpen(false); // close Popover
|
||||
setFreeText(""); // remove feedback from free text field local state
|
||||
setSharedFeedback(true); // display Thank You message
|
||||
}}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
) : (
|
||||
<div>Thanks a lot, boss! 🤝</div> // Thank You message
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
## 3. Connecting to the Formbricks API
|
||||
|
||||
The last step is to hook up your sparkling new frontend to the Formbricks API. To do so, we followed the “[Create Response](/api/client/responses#create-a-response)” and “[Update Response](/api/client/responses#update-a-response)” pages in our docs.
|
||||
|
||||
Here is the code for the `handleFeedbackSubmit` function with comments:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="handleFeedbackSubmit() function definition">
|
||||
|
||||
```tsx
|
||||
export const handleFeedbackSubmit = async (YesNo, pageUrl) => {
|
||||
const response_data = {
|
||||
data: {
|
||||
isHelpful: YesNo, // the "Yes 👍" or "No 👎" response. Remember: They have to be identical with the choices in the survey on app.formbricks.com for it to work.
|
||||
pageUrl: pageUrl, // So you know which page the user gives feedback about.
|
||||
},
|
||||
};
|
||||
|
||||
const payload = {
|
||||
response: response_data,
|
||||
surveyId: process.env.NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID, // For testing, replace this with the survey ID of your survey (more info below)
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST}/api/v1/client/environments/${process.env.NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID}/responses`, // For testing, replace this with the API host and environemnt ID of your Development environment on app.formbricks.com
|
||||
};
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
);
|
||||
|
||||
if (res.ok) {
|
||||
const responseJson = await res.json();
|
||||
return responseJson.id; // Returns the response ID
|
||||
} else {
|
||||
console.error("Error submitting form");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
And this is the `updateFeedback` function with comments:
|
||||
<Col>
|
||||
<CodeGroup title="updateFeedback() function definition">
|
||||
|
||||
```tsx
|
||||
export const updateFeedback = async (freeText, responseId) => {
|
||||
if (!responseId) {
|
||||
console.error("No response ID available"); // If there is not response ID, no response can be updated.
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
response: {
|
||||
data: {
|
||||
additionalInfo: freeText,
|
||||
},
|
||||
finished: true, // Lets Formbricks calculate Completion Rate
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_FORMBRICKS_COM_API_HOST}/api/v1/client/environments/${process.env.NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID}/responses/${responseId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("Error updating response");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating response:", error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
That’s almost it! 🤸
|
||||
|
||||
## 4. Setting it up for testing
|
||||
|
||||
Before you roll it out in production, you want to test it. To do so, you need two things:
|
||||
|
||||
1. Environment ID (1) of the development environment on app.formbricks.com
|
||||
2. Survey ID (2) of your test survey
|
||||
|
||||
When you are on the survey detail page, you’ll find both of them in the URL:
|
||||
|
||||
<MdxImage src={CopyIds} alt="copy IDs" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
Now, you have to replace the IDs and the API host accordingly in your `handleFeedbackSubmit`:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Replace the ID and API accordingly">
|
||||
|
||||
```tsx
|
||||
const payload = {
|
||||
response: response_data,
|
||||
surveyId: clgwfv4a7002el50ihyuss38x, // This is an example, replace with yours
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
// Note that we also updated the API host to 'https://app.formbricks.com/'
|
||||
`https:app.formbricks.com/api/v1/client/environments/clgwcwp4z000lpf0hur7uxbuv/responses`,
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
And lastly, in the `updateFeedback` function
|
||||
<Col>
|
||||
<CodeGroup title="Replace the ID and API here as well">
|
||||
|
||||
```tsx
|
||||
try {
|
||||
const res = await fetch(
|
||||
// Note that we also updated the API host to 'https://app.formbricks.com/'
|
||||
`https:app.formbricks.com/api/v1/client/environments/clgwcwp4z000lpf0hur7uxbuv/responses/${responseId}`, // Note that we also updated the API host to 'https://app.formbricks.com/'
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### You’re good to go! 🎉
|
||||
|
||||
Something doesn’t work? Check your browser console for the error.
|
||||
|
||||
Can’t figure it out? [Join our Discord!](https://formbricks.com/discord)
|
||||
@@ -1,132 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import ActionCSS from "./action-css.webp";
|
||||
import ActionText from "./action-innertext.webp";
|
||||
import ChangeText from "./change-text.webp";
|
||||
import CreateSurvey from "./create-survey.webp";
|
||||
import Publish from "./publish.webp";
|
||||
import RecontactOptions from "./recontact-options.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Setting Up Feature Chaser Surveys with Formbricks: A Comprehensive Guide",
|
||||
description:
|
||||
"Learn how to harness the power of Formbricks to gather targeted user feedback on specific features. Dive deep into creating, triggering, and publishing the Feature Chaser survey to enhance your product with actionable insights for specific users.",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Feature Chaser
|
||||
|
||||
Following up on specific features only makes sense with very targeted surveys. Formbricks is built for that.
|
||||
|
||||
## Purpose
|
||||
|
||||
Product analytics never tell you why a feature is used - and why not. Following up on specific features with highly relevant questions is a great way to gather feedback and improve your product.
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Trigger survey at exactly the right point in the user journey
|
||||
- Never ask twice, keep your data clean
|
||||
- Prevent survey fatigue with global waiting period
|
||||
|
||||
## Overview
|
||||
|
||||
To run the Feature Chaser survey in your app you want to proceed as follows:
|
||||
|
||||
1. Create new Feature Chaser survey at [app.formbricks.com](https://app.formbricks.com/)
|
||||
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)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Feature Chaser
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Click on "Create Survey" and choose the template “Feature Chaser”:
|
||||
|
||||
<MdxImage
|
||||
src={CreateSurvey}
|
||||
alt="Create survey by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update questions
|
||||
|
||||
The questions you want to ask are dependent on your feature and can be very specific. In the template, we suggest a high-level check on how easy it was for the user to achieve their goal. We also add an opportunity to provide context:
|
||||
|
||||
<MdxImage
|
||||
src={ChangeText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Save, and move over to where the magic happens: The “Audience” tab.
|
||||
|
||||
### 3. Set up a trigger for the Feature Chaser survey:
|
||||
|
||||
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](/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 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}
|
||||
alt="Set the trigger by inner Text"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **Trigger by CSS Selector:** In case you have more than one button saying “Export Report” in your app and only want to display the survey when one of them is clicked, you want to be more specific. The best way to do that is to give this button the HTML `id=“export-report-featurename”` and set your user action up like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionCSS}
|
||||
alt="Set the trigger by CSS Selector"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Please follow our [Actions manual](/actions/why) for an in-depth description of how Actions work.
|
||||
|
||||
### 4. Select Action in the “When to ask” card
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select PMF trigger button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 5. Last step: Set Recontact Options correctly
|
||||
|
||||
Lastly, scroll down to “Recontact Options”. Here you have full freedom to decide who you want to ask. Generally, you only want to ask every user once and prevent survey fatigue. It's up to you to decide if you want to ask again, when the user did not yet reply:
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-2xl"
|
||||
/>
|
||||
|
||||
### 7. Congrats! You’re ready to publish your survey 💃
|
||||
|
||||
<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 Feature Chaser
|
||||
in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# Get those insights! 🎉
|
||||
@@ -1,119 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
import Link from "next/link";
|
||||
|
||||
import ActionCSS from "./action-css.webp";
|
||||
import ActionText from "./action-innertext.webp";
|
||||
import AddAction from "./add-action.webp";
|
||||
import ChangeTextContent from "./change-text-content.webp";
|
||||
import CreateFeedbackBox from "./create-feedback-box-by-template.webp";
|
||||
import PublishSurvey from "./publish-survey.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
import RecontactOptions from "./set-recontact-options.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Implementing the Feedback Box with Formbricks: A Step-by-Step Tutorial",
|
||||
description:
|
||||
"Unlock user insights effortlessly! Discover how to set up the Feedback Box in your app using Formbricks, allowing your users to provide real-time feedback. Follow our comprehensive guide to enhance user experience and respond rapidly to feedback",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Feedback Box
|
||||
|
||||
The Feedback Box gives your users a direct channel to share their feedback and feel heard.
|
||||
|
||||
## Purpose
|
||||
|
||||
A low friction way to gather feedback helps catching even the smallest points of frustration in user experiences. Use automations to react rapidly and make users feel heard.
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Make it easy: 2 clicks to share feedback
|
||||
- Pipe insights where team can see them and react quickly
|
||||
|
||||
## Installation
|
||||
|
||||
To add the Feedback Box to your app, you need to perform these steps:
|
||||
|
||||
1. Create new Feedback Box at app.formbricks.com
|
||||
2. Add user action to trigger Feedback Box
|
||||
3. Update recontact settings to display correctly
|
||||
|
||||
### 1. Create new Feedback Box
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Then, create a new survey and look for the "Feedback Box" template:
|
||||
|
||||
<MdxImage
|
||||
src={CreateFeedbackBox}
|
||||
alt="Create feedback box by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update question content
|
||||
|
||||
Change the questions and answer options according to your preference:
|
||||
|
||||
<MdxImage
|
||||
src={ChangeTextContent}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 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 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. **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.
|
||||
|
||||
<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
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select feedback button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 5. Set Recontact Options correctly
|
||||
|
||||
Scroll down to “Recontact Options”. Here you have to choose the right settings so that the Feedback Box pops up every time the user action is performed. (Our default is that every user sees every survey only once):
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 6. You’re ready publish your survey!
|
||||
|
||||
<MdxImage
|
||||
src={PublishSurvey}
|
||||
alt="Publish survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Setting up the Widget
|
||||
|
||||
<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.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# That’s it! 🎉
|
||||
@@ -1,110 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import NewsletterSurveyType from "./choose-survey-type.webp";
|
||||
import NewsletterSurveyEmbedCode from "./embed-survey-code-in-your-email.webp";
|
||||
import NewsletterSurveyEmbedPrompt from "./embed-survey-prompt.webp";
|
||||
import NewsletterSurveyEditor from "./improve-newsletter-content-editor-formbricks.webp";
|
||||
import NewsletterSurvey from "./improve-newsletter-content-survey-location.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Measure email content quality with Formbricks",
|
||||
description:
|
||||
"Measuring the content quality of both transactional and marketing email is a key element for improving customer communication.",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Improve Email Content
|
||||
|
||||
Email remains the predominant way to communicate with your customers. Measure the effectiveness to improve your offering.
|
||||
|
||||
## Purpose
|
||||
|
||||
Measuring the content quality of both transactional and marketing email is a key element for improving customer communication.
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Embed the survey into your email so it’s part of the newsletter.
|
||||
- Use link prefilling to store the answer users clicked on in the email.
|
||||
- Dynamic user identification to append reader's email for personalized profiles and follow ups.
|
||||
|
||||
## Installation
|
||||
|
||||
To embed the newsletter survey into your email, follow these steps:
|
||||
|
||||
1. Create new 'Improve Newsletter Content' survey at [app.formbricks.com](https://app.formbricks.com/)
|
||||
2. Select how you where you want to display the survey.
|
||||
3. Copy the embed code anywhere you want in your newsletter.
|
||||
|
||||
### 1. Create new 'Improve Newsletter Content' Survey
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Then, create a new survey and look for the "Improve Newsletter Content" template:
|
||||
|
||||
<MdxImage
|
||||
src={NewsletterSurvey}
|
||||
alt="Create Improve Newsletter Content by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Customize Survey questions
|
||||
|
||||
Customize survey questions, emojis or stars however you like:
|
||||
|
||||
<MdxImage
|
||||
src={NewsletterSurveyEditor}
|
||||
alt="Edit Improve Newsletter Content template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 3. Configure Survey Settings
|
||||
|
||||
When you are done customizing your survey questions, navigate to the Settings tab and choose the type of survey you want. You need to choose Link Survey:
|
||||
|
||||
<MdxImage
|
||||
src={NewsletterSurveyType}
|
||||
alt="Choose survey type"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 4. Choose how you want to embed your survey
|
||||
|
||||
After publishing your survey, a modal that prompts you to embed your survey will pop up.
|
||||
|
||||
<MdxImage
|
||||
src={NewsletterSurveyEmbedPrompt}
|
||||
alt="Embed newsletter survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Select the Embed Survey card and you will be directed to another modal, where the first embed option displayed will be to embed the survey in an email.
|
||||
|
||||
### 5. Copy code to embed the survey in your newsletter
|
||||
|
||||
Click the button with the “View Embed Code” text at the top right corner of the modal and simply paste the HTML code for your survey anywhere you want it in your newsletter. You can see the preview in the below image:
|
||||
|
||||
<MdxImage
|
||||
src={NewsletterSurveyEmbedCode}
|
||||
alt="Embed survey code"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
And you're done! Send a test email to yourself and try it out 🤓
|
||||
|
||||
## Learn about data prefilling
|
||||
|
||||
<Note>
|
||||
## How does data prefilling work? Learn about how link prefilling and user identification maximize your
|
||||
insights in [this detailed
|
||||
guide](/blog/how-smart-writers-use-formbricks-open-source-tool-to-measure-the-quality-of-their-newsletter-content).
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# That’s it! 🎉
|
||||
@@ -1,139 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import ActionText from "./action-innertext.webp";
|
||||
import ActionPageurl from "./action-pageurl.webp";
|
||||
import ChangeText from "./change-text.webp";
|
||||
import CreateSurvey from "./create-survey.webp";
|
||||
import Publish from "./publish.webp";
|
||||
import RecontactOptions from "./recontact-options.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Boost Your Trial Conversion Rates with Formbricks: Comprehensive Guide",
|
||||
description:
|
||||
"Unlock the secret to converting more trial users into paying customers using Formbricks. Understand insights behind trial cancellations and tailor your offering to fit user needs. Dive into our step-by-step tutorial and improve your conversion strategy today",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Improve Trial Conversion
|
||||
|
||||
When a user doesn't convert, you want to know why. A micro-survey displayed at exactly the right time gives you a window into understanding the most relevant question: To pay or not to pay?
|
||||
|
||||
## Purpose
|
||||
|
||||
The better you understand why free users don’t convert to paid users, the higher your revenue. You can make an informed decision about what to change in your offering to make more people pay for your service.
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Ask at exactly the right point in time
|
||||
- Ask to understand the problem, don’t ask for solutions
|
||||
|
||||
## Installation
|
||||
|
||||
To display the Trial Conversion Survey in your app you want to proceed as follows:
|
||||
|
||||
1. Create new Trial Conversion Survey at [app.formbricks.com](https://app.formbricks.com/)
|
||||
2. Set up the user action to display survey at right point in time
|
||||
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)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Trial Conversion Survey
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Click on "Create Survey" and choose the template “Improve Trial Conversion”:
|
||||
|
||||
<MdxImage
|
||||
src={CreateSurvey}
|
||||
alt="Create survey by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update questions (if you like)
|
||||
|
||||
You’re free to update the questions and answer options. However, based on our experience, we suggest giving the provided template a go 😊
|
||||
|
||||
<MdxImage
|
||||
src={ChangeText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
_Want to change the button color? You can do so in the product settings!_
|
||||
|
||||
Save, and move over to the “Audience” tab.
|
||||
|
||||
### 3. Pre-segment your audience (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 days.
|
||||
</Note>
|
||||
|
||||
Pre-segmentation isn't relevant for this survey because you likely want to solve all people who cancel their trial. You probably have a specific user action e.g. clicking on "Cancel Trial" you can use to only display the survey to users trialing your product.
|
||||
|
||||
### 4. Set up a trigger for the Trial Conversion Survey:
|
||||
|
||||
How you trigger your survey depends on your product. There are two options:
|
||||
|
||||
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}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
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 `Inner Text` like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
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
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select feedback button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 6. Last step: Set Recontact Options correctly
|
||||
|
||||
Lastly, scroll down to “Recontact Options”. Here you have to choose the correct settings to make sure you gather as many insights as possible. You want to make sure that this survey is always displayed, no matter if the user has already seen a survey in the past days:
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 7. Congrats! You’re ready to publish your survey 💃
|
||||
|
||||
<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.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# Go get 'em 🎉
|
||||
@@ -1,151 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import ActionCSS from "./action-css.webp";
|
||||
import ActionInner from "./action-innertext.webp";
|
||||
import ActionPageurl from "./action-pageurl.webp";
|
||||
import AddAction from "./add-action.webp";
|
||||
import ChangeText from "./change-text.webp";
|
||||
import CreatePrompt from "./create-prompt.webp";
|
||||
import InterviewExample from "./interview-example.webp";
|
||||
import Publish from "./publish-survey.webp";
|
||||
import RecontactOptions from "./recontact-options.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Maximize User Interview Participation with App Interview Prompts",
|
||||
description:
|
||||
"Engage with your power users seamlessly using Formbricks' App Interview Prompt. Ditch traditional email invites and experience way more more respondents. Dive into our comprehensive guide on setting up auto-scheduled interviews today and enhance your user understanding",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# App Interview Prompt
|
||||
|
||||
The Interview Prompt allows you to pick a specific user segment (e.g. Power Users) and invite them to a user interview. Bye, bye spammy email invites, benefit from up to 6x more respondents.
|
||||
|
||||
## Purpose
|
||||
|
||||
Product analytics and app surveys are incomplete without user interviews. Set the scheduling on autopilot for a continuous stream of interviews.
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Pre-segment users with custom attributes. Only invite highly relevant users.
|
||||
- In-app prompts (app surveys or website surveys) have a 6x higher conversion rate than email invites.
|
||||
- Set scheduling user interviews on auto pilot.
|
||||
- Soon: Integrate directly with your [Cal.com](http://Cal.com) account.
|
||||
|
||||
## Installation
|
||||
|
||||
To display an Interview Prompt in your app you want to proceed as follows:
|
||||
|
||||
1. Create new Interview Prompt at [app.formbricks.com](https://app.formbricks.com/)
|
||||
2. Adjust content and settings
|
||||
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)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Interview Prompt
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Click on "Create Survey" and choose the template “Interview Prompt”:
|
||||
|
||||
<MdxImage
|
||||
src={CreatePrompt}
|
||||
alt="Create interview prompt by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update prompt and CTA
|
||||
|
||||
Update the prompt, description and button text to match your products tonality. You can also update the button color in the Product Settings.
|
||||
|
||||
<MdxImage
|
||||
src={ChangeText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
In the button settings you have to make sure it is set to “External URL”. In the URL field, copy your booking link (e.g. https://cal.com/company/user-interview). If you don’t have a booking link yet, head over to [cal.com](http://cal.com) and get one - they have the best free plan out there!
|
||||
|
||||
<MdxImage
|
||||
src={InterviewExample}
|
||||
alt="Add CSS action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
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.
|
||||
</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.
|
||||
|
||||
In our case, we want to select users who we have assigned the attribute “Power User”. To learn how to assign attributes to your users, please [follow this guide](/app-surveys/user-identification).
|
||||
|
||||
Great, now only the “Power User” segment will see our Interview Prompt. But when will they see it?
|
||||
|
||||
### 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 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. **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}
|
||||
alt="Add page URL action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
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" />
|
||||
<MdxImage src={ActionInner} alt="Add inner text action" quality="100" className="rounded-lg" />
|
||||
</div>
|
||||
|
||||
### 5. Select action in the “When to ask” card
|
||||
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select feedback button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 6. Set Recontact Options correctly
|
||||
|
||||
Scroll down to “Recontact Options”. Here you have to choose the correct settings to strike the right balance between asking for user feedback and preventing survey fatigue. Your settings also depend on the size of your user base or segment. If you e.g. have thousands of “Power Users” you can easily afford to only display the prompt once. If you have a smaller user base you might want to ask twice to get a sufficient amount of bookings:
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 7. Congrats! You’re ready to publish your survey 💃 🤸
|
||||
|
||||
<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.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# Learn about them users! 🎉
|
||||
@@ -1,131 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import ActionCSS from "./action-css.webp";
|
||||
import ActionPageurl from "./action-pageurl.webp";
|
||||
import ChangeText from "./change-text.webp";
|
||||
import CreateSurvey from "./create-survey.webp";
|
||||
import Publish from "./publish.webp";
|
||||
import RecontactOptions from "./recontact-options.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "How to Set Up a Product-Market Fit Survey Using Formbricks - Step-by-Step Guide",
|
||||
description:
|
||||
"Learn to leverage Formbricks to create and implement a Product-Market Fit survey in your web app. Follow our detailed step-by-step guide to measure and understand your PMF effectively. Ensure high data quality, efficient triggers, and actionable insights.",
|
||||
};
|
||||
|
||||
#### Best Practices
|
||||
|
||||
# Product-Market Fit Survey
|
||||
|
||||
## Purpose
|
||||
|
||||
Measuring and understanding your PMF is essential to build a large, successful business. It helps you understand what users like, what they’re missing and what to build next. This survey is perfectly suited to measure PMF like [Superhuman](https://review.firstround.com/how-superhuman-built-an-engine-to-find-product-market-fit).
|
||||
|
||||
## Formbricks Approach
|
||||
|
||||
- Pre-segment users to only survey users who have experienced your products value
|
||||
- Never ask twice, keep your data clean
|
||||
- Run on autopilot: Set up once, keep surveying users continuously
|
||||
|
||||
## Overview
|
||||
|
||||
To display the Product-Market Fit survey in your app you want to proceed as follows:
|
||||
|
||||
1. Create new Product-Market Fit survey at [app.formbricks.com](https://app.formbricks.com/)
|
||||
2. Setup pre-segmentation to assure high data quality
|
||||
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)
|
||||
</Note>
|
||||
|
||||
### 1. Create new PMF survey
|
||||
|
||||
If you don't have an account yet, create one at [app.formbricks.com](https://app.formbricks.com/auth/signup)
|
||||
|
||||
Click on "Create Survey" and choose one of the PMF survey templates. The first one is rather short, the latter builds on the ["Product-Market Fit Engine"](https://review.firstround.com/how-superhuman-built-an-engine-to-find-product-market-fit) developed by Superhuman:
|
||||
|
||||
<MdxImage
|
||||
src={CreateSurvey}
|
||||
alt="Create survey by template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 2. Update questions (if you like)
|
||||
|
||||
You’re free to update the question and answer options. However, based on our experience, we suggest giving the provided template a go 😊 Here is a very [detailed description](https://coda.io/@rahulvohra/superhuman-product-market-fit-engine) of what to do with the data you’re collecting.
|
||||
|
||||
<MdxImage
|
||||
src={ChangeText}
|
||||
alt="Change text content"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
_Want to change the button color? You can do so in the product settings!_
|
||||
|
||||
Save, and move over to where the magic happens: The “Audience” tab.
|
||||
|
||||
### 3. Pre-segment your audience
|
||||
|
||||
To run this survey properly, you should pre-segment your user base. As touched upon earlier: if you ask every user you’ll get lots of opinions which are often misleading. You only want to gather feedback from people who invested the time to get to know and use your product:
|
||||
|
||||
**Filter by attribute**: You can keep the logic to decide if a user has (or has not) experienced value in your application. This makes most sense if you want to use historic usage data to decide if a user qualifies or not. Create your logic and if it applies, send an attribute to Formbricks by e.g. `formbricks.setAttribute("Loyalty", "Experienced Value");` Here is the full manual on how to [set attributes](/app-surveys/user-identification).
|
||||
|
||||
### 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](/app-surveys/actions/) if you are not sure how to set them up:
|
||||
|
||||
<Col>
|
||||
|
||||
<div className="grid max-w-full grid-cols-2 space-x-2 sm:max-w-3xl items-end">
|
||||
<MdxImage
|
||||
src={ActionCSS}
|
||||
alt="Add CSS action"
|
||||
quality="100"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<MdxImage
|
||||
src={ActionPageurl}
|
||||
alt="Add inner text action"
|
||||
quality="100"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
### 5. Select Action in the “When to ask” card
|
||||
<Col>
|
||||
<MdxImage
|
||||
src={SelectAction}
|
||||
alt="Select PMF trigger button action"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
</Col>
|
||||
### 6. Last step: Set Recontact Options correctly
|
||||
|
||||
Lastly, scroll down to “Recontact Options”. Here you have to choose the correct settings to make sure your data remains of high quality. You want to make sure that this survey is only responded to once per user. It is up to you to decide if you want to display it several times until the user responds:
|
||||
|
||||
<MdxImage
|
||||
src={RecontactOptions}
|
||||
alt="Set recontact options"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### 7. Congrats! You’re ready to publish your survey 💃
|
||||
|
||||
<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.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
# Get those insights!
|
||||
@@ -1,178 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
import SingleSelect from "./single-select.webp";
|
||||
import Quiz from "./quiz.webp";
|
||||
import Score from "./score.webp"
|
||||
import AddLogic from "./conditional-logic.webp";
|
||||
import WhenThen from "./when-then.webp";
|
||||
import EndingLogic from "./ending-logic.webp";
|
||||
import PassFail from "./pass-fail.webp";
|
||||
|
||||
|
||||
export const metadata = {
|
||||
title: "How to Create a Quiz Using Formbricks - Step-by-Step Guide",
|
||||
description:
|
||||
"Learn to leverage Formbricks to create Quizzes. Follow our detailed step-by-step guide to build quizzes with custom logic and multiple endings.",
|
||||
};
|
||||
|
||||
|
||||
# Creating a quiz with Formbricks - Step-by-step Guide
|
||||
|
||||
|
||||
Welcome to this guide on creating engaging quizzes with Formbricks! Quizzes help you capture customer insights, explore user personalities, or simply add fun for your team. With Formbricks, you can personalize quizzes in minutes add scores, customize backgrounds, and more, all without any technical skills!
|
||||
|
||||
|
||||
## What we'll build
|
||||
|
||||
|
||||
By the end of this tutorial, you'll have created a simple trivia Quiz featuring:
|
||||
|
||||
|
||||
1. Score calculations.
|
||||
2. Multiple endings depending on the score.
|
||||
|
||||
|
||||
## Setting up the form
|
||||
|
||||
|
||||
First, make sure you have a Formbricks account. If not, you can create one [here](https://app.formbricks.com):
|
||||
|
||||
|
||||
1. Head to the Surveys page and click on **New Survey**.
|
||||
2. Select Start from Scratch to create a new form.
|
||||
3. Go to the settings and select form type as **Link Survey**
|
||||
4. In the form editor, click the three dots next to a question, then select Change Question Type and choose **Single-Select**.
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={SingleSelect}
|
||||
alt="Change Question type to Single-Select"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-2xl"
|
||||
/>
|
||||
|
||||
|
||||
5. Add a welcoming statement to greet your users and explain the Quiz's purpose.
|
||||
6. Personalize the greeting to make it inviting and encourage engagement.
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** While we’re creating a Link Survey here, the process is similar for Web and App surveys.
|
||||
|
||||
|
||||
## Adding the questions
|
||||
|
||||
|
||||
Next, let's create a question for example with multiple options:
|
||||
|
||||
|
||||
What country has the longest coastline in the world?
|
||||
A) Canada
|
||||
B) Japan
|
||||
C) India
|
||||
D) Nepal
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={Quiz}
|
||||
alt="Sample Question"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
## Calculate Score
|
||||
|
||||
|
||||
Now that we have our question ready, let’s add the logic to calculate scores.
|
||||
|
||||
|
||||
1. Scroll down in the editor and click on variables.
|
||||
2. Create a new variable named `score` with a default value of 0
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={Score}
|
||||
alt="Create Variable named Score image"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
3. Now go back to the question and expand the advanced settings by clicking on `> Show Advanced Settings`.
|
||||
4. Under the conditional logic you should see the option to `Add Logic`. Click on that.
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={AddLogic}
|
||||
alt="Add Logic Button"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
5. Now you should see conditional logic. Customize the logic to match your needs for example:
|
||||
|
||||
|
||||
**When** `question` equals `YOUR_ANSWER_HERE` **Then** `Calculate` `score` `Add +` `01`. So it should look something like this.
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={WhenThen}
|
||||
alt="When-Then Conditional Logic"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
6. Let's duplicate and customize these questions. Click on the duplicate icon at the top of the question.
|
||||
7. Now edit the questions, options, and answers in the **conditional logic**
|
||||
|
||||
|
||||
## Creating Multiple Endings Based on Scores
|
||||
|
||||
|
||||
Once you have all the questions and the calculation logic in place, it’s time to customize the endings. Scroll down to the Ending Card section. We will create two cards for this quiz: one for when the user fails the quiz and another for when the user passes.
|
||||
|
||||
|
||||
1. Customize the ending card.
|
||||
2. Display the score by typing `@score`. ( You can address all the variables or questions by just typing @ ).
|
||||
3. Add logic to the last question. ( this is necessary to redirect the user based on the score ). Kind of like this:
|
||||
|
||||
|
||||
**When** `score` >= `03` **Then** `Jump to` `Pass`. So it should look something like this.
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={EndingLogic}
|
||||
alt="Conditional Logic for ending card"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
4. Ensure that the Fail card is positioned above the Pass card. This allows any condition that does not meet the criteria of being greater than or equal to 3 to jump to the Fail card.
|
||||
|
||||
|
||||
<MdxImage
|
||||
src={PassFail}
|
||||
alt="Pass or Fail ending Cards"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-xl"
|
||||
/>
|
||||
|
||||
|
||||
5. That's it! Now you can save and publish the quiz.
|
||||
|
||||
|
||||
# Wrapping Up
|
||||
|
||||
|
||||
Congratulations! You’ve successfully created a Quiz with Formbricks. You can play around with the quiz that we just created [here](https://app.formbricks.com/s/cm2wwt3vu0001ir8o7ys0bezz).
|
||||
|
||||
|
||||
A great quiz can serve as an excellent lead generator, a job fit checker, or just a fun icebreaker for your team. You now have the skills to build that! If you want to read more about building quizzes and how you can create a Job Fit Quiz check this article [here](https://www.harshbhat.me/blog/formbricks-quiz).
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks API SDK",
|
||||
description:
|
||||
"An overview of all available methods & how to integrate Formbricks API for backend developers in web applications. Learn the key methods, configuration settings, and best practices.",
|
||||
};
|
||||
|
||||
#### Developer Docs
|
||||
|
||||
# SDK: Formbricks API
|
||||
|
||||
### Overview
|
||||
|
||||
The Formbricks Client API Wrapper is a lightweight package designed to simplify the integration of Formbricks API endpoints into your JavaScript (JS) or TypeScript (TS) projects. With this wrapper, you can easily interact with Formbricks API endpoints without the need for complex setup or manual HTTP requests.
|
||||
|
||||
### Install
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="npm">
|
||||
|
||||
```js {{ title: 'npm' }}
|
||||
npm install @formbricks/api
|
||||
```
|
||||
|
||||
```js {{ title: 'yarn' }}
|
||||
yarn add @formbricks/api
|
||||
```
|
||||
|
||||
```js {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/api
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Methods
|
||||
|
||||
### Initialize Formbricks
|
||||
|
||||
Initialize the Formbricks API Client for backend developers to interact with Formbricks API endpoints:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Initialize Formbricks API">
|
||||
|
||||
```javascript
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
|
||||
const api = new FormbricksAPI({
|
||||
apiHost: `https://app.formbricks.com`, // If you have self-hosted Formbricks, change this to your self hosted instance's URL
|
||||
environmentId: "<environment-id>", // Replace this with your Formbricks environment ID
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
The API client is now ready to be used across your project. It can be used to interact with the following models:
|
||||
|
||||
## Displays
|
||||
|
||||
- Create Display
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Crate Display">
|
||||
|
||||
```javascript {{ title: 'Create Display Method Call'}}
|
||||
await api.client.display.create({
|
||||
surveyId: "<your-survey-id>", // required
|
||||
userId: "<your-user-id>", // optional
|
||||
responseId: "<your-response-id>", // optional
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Create Display Method Return Type' }}
|
||||
Promise<{ id: string }, NetworkError | Error>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Responses
|
||||
|
||||
- Create Response
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Create Response">
|
||||
|
||||
```javascript {{ title: 'Create Response Method Call'}}
|
||||
await api.client.response.create({
|
||||
surveyId: "<your-survey-id>", // required
|
||||
finished: boolean, // required
|
||||
data: {
|
||||
// required
|
||||
questionId: "<answer-to-this-question-in-string>",
|
||||
anotherQuestionId: 123, // answer to this question in number
|
||||
yetAnotherQuestionId: ["option1", "option2"], // answer to this question in array,
|
||||
},
|
||||
|
||||
userId: "<your-user-id>",
|
||||
singleUseId: "<your-single-use-id>",
|
||||
ttc: {
|
||||
questionId: 123,
|
||||
},
|
||||
meta: {
|
||||
source: "<your-source>",
|
||||
url: "<your-url>",
|
||||
userAgent: {
|
||||
browser: "<your-browser>",
|
||||
device: "<your-device>",
|
||||
os: "<your-os>",
|
||||
},
|
||||
country: "<your-country>",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Create Response Method Return Type' }}
|
||||
Promise<{ id: string }, NetworkError | Error>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
- Update Response
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Update Response">
|
||||
|
||||
```javascript {{ title: 'Update Response Method Call'}}
|
||||
await api.client.response.update({
|
||||
responseId: "<your-response-id>", // required
|
||||
finished: boolean, // required
|
||||
data: {
|
||||
// required
|
||||
questionId: "<answer-to-this-question-in-string>",
|
||||
anotherQuestionId: 123, // answer to this question in number
|
||||
yetAnotherQuestionId: ["option1", "option2"], // answer to this question in array,
|
||||
},
|
||||
ttc: {
|
||||
// required
|
||||
questionId: 123,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Update Response Method Return Type' }}
|
||||
Promise<{ }, NetworkError | Error]>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Attribute
|
||||
|
||||
- Update Attribute
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Update Attribute">
|
||||
|
||||
```javascript {{ title: 'Update Attribute Method Call'}}
|
||||
await api.client.attribute.update({
|
||||
userId: "<your-user-id>", // required
|
||||
attributes: {
|
||||
key1: "value1",
|
||||
key2: "value2",
|
||||
}, // required
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Update Attribute Method Return Type' }}
|
||||
Promise<{ changed: boolean, message: string }, NetworkError | Error>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## People
|
||||
|
||||
- Create Person
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Create Person">
|
||||
|
||||
```javascript {{ title: 'Create Person Method Call'}}
|
||||
await api.client.people.create({
|
||||
userId: "<your-user-id>", // required
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Create Person Method Return Type' }}
|
||||
Promise<{ }, NetworkError | Error]>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Storage
|
||||
|
||||
- Upload File:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Upload File">
|
||||
|
||||
```javascript {{ title: 'Upload Method Call'}}
|
||||
await api.client.storage.uploadFile(
|
||||
file: File, // required (of interface File of the browser's File API)
|
||||
{
|
||||
allowedFileTypes: ["file-type-allowed", "for-example", "image/jpeg"],
|
||||
surveyId: "<your-survey-id>",
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Upload Method Return Type' }}
|
||||
Promise<fileUrl>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
---
|
||||
|
||||
If you have any questions or need help, feel free to reach out to us on our **[Discord](https://formbricks.com/discord)**
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 62 KiB |
@@ -1,66 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
|
||||
import GithubCodespaceLoading from "./images/loading.webp";
|
||||
import GithubCodespaceNew from "./images/new.webp";
|
||||
import GithubCodespacePorts from "./images/ports.webp";
|
||||
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Open Source Contribution Guide: How to Enhance yourself and Contribute to Formbricks",
|
||||
description:
|
||||
"Join the Formbricks community and learn how to effectively contribute. From raising issues and feature requests to creating PRs, discover the best practices and communicate with our responsive team on Discord",
|
||||
};
|
||||
|
||||
#### Contributing
|
||||
|
||||
# Github Codespaces Guide
|
||||
|
||||
1. After clicking the one-click setup button, you will be redirected to the Github Codespaces page. Review the configuration and click on the 'Create Codespace' button to create a new Codespace.
|
||||
|
||||
<MdxImage
|
||||
src={GithubCodespaceNew}
|
||||
alt="New Github Codespace"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. This will start loading the Codespace. Keep in mind this might take a few minutes to complete depending on your internet connection and the instance availability.
|
||||
|
||||
<MdxImage
|
||||
src={GithubCodespaceLoading}
|
||||
alt="Loading Github Codespace"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. Once the Codespace is loaded, you will be redirected to the VSCode editor. You can start working on your project in this environment.
|
||||
|
||||
4. Monitor the logs in the terminal and once you see the following, you are good to go!
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="The WebApp is running">
|
||||
|
||||
```bash
|
||||
@formbricks/web:dev: ▲ Next.js 13.5.6
|
||||
@formbricks/web:dev: - Local: http://localhost:3000
|
||||
@formbricks/web:dev: - Environments: .env
|
||||
@formbricks/web:dev: - Experiments (use at your own risk):
|
||||
@formbricks/web:dev: · serverActions
|
||||
@formbricks/web:dev:
|
||||
@formbricks/web:dev: ✓ Ready in 9.4s
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
5. Right next to the Terminal, you will see a **Ports** tab, click on it to see the ports and their respective URLs. Now access the Forwarded Address for port 3000 and you should be able to visit your Formbricks App!
|
||||
|
||||
<MdxImage
|
||||
src={GithubCodespacePorts}
|
||||
alt="Github Codespace Ports"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Now make the changes you want to and see them live in action!
|
||||