Compare commits
405 Commits
fix/types
...
ai-fix-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1d4ddb203 | ||
|
|
7337e94538 | ||
|
|
f55df95f8c | ||
|
|
dc894bae2a | ||
|
|
ea4941fe98 | ||
|
|
126a4c8989 | ||
|
|
add567ffdc | ||
|
|
1c10679b71 | ||
|
|
5e17b7919f | ||
|
|
4f5ac2b27f | ||
|
|
0b9d76e243 | ||
|
|
4d225818a6 | ||
|
|
f60ae69ed1 | ||
|
|
897c2de656 | ||
|
|
5c802c2fe8 | ||
|
|
e583aa38ba | ||
|
|
57927f6a3e | ||
|
|
4ffbbad9fe | ||
|
|
d47944ed21 | ||
|
|
a8e1fce3b7 | ||
|
|
7221a12964 | ||
|
|
27fc47144e | ||
|
|
706fa8a04e | ||
|
|
11748cbeba | ||
|
|
0f50c9690c | ||
|
|
316e5f15b0 | ||
|
|
18719746ed | ||
|
|
41fb76e4ff | ||
|
|
24d1f23421 | ||
|
|
6a691e2b68 | ||
|
|
354ec1b887 | ||
|
|
87c584add8 | ||
|
|
5035e3db9d | ||
|
|
b7f4097508 | ||
|
|
26591d9b9f | ||
|
|
655b67c3ad | ||
|
|
2dbd7111a9 | ||
|
|
06ddee42a9 | ||
|
|
47aa84bf8a | ||
|
|
2f7a59817a | ||
|
|
e7a0228bfa | ||
|
|
2367313ff2 | ||
|
|
68e52954e2 | ||
|
|
59ebde49cf | ||
|
|
6ab2560432 | ||
|
|
861d399025 | ||
|
|
fc3886fafa | ||
|
|
ddf7ad8475 | ||
|
|
ad6d5d6c00 | ||
|
|
315aaac395 | ||
|
|
01ceaa13ec | ||
|
|
25774f6f08 | ||
|
|
9a98772210 | ||
|
|
59a29dd3d6 | ||
|
|
e4fceb2e5e | ||
|
|
0b553447e0 | ||
|
|
6b64367d99 | ||
|
|
fe9746ba67 | ||
|
|
e4009d5951 | ||
|
|
b1ed61c247 | ||
|
|
10255aa102 | ||
|
|
774c6f19a5 | ||
|
|
ebf35ea582 | ||
|
|
f13efc954e | ||
|
|
9ee052a229 | ||
|
|
152fbede90 | ||
|
|
c9b8ffa9ef | ||
|
|
04e16d44a1 | ||
|
|
29131f93c2 | ||
|
|
1e2fe7b066 | ||
|
|
426a0a3847 | ||
|
|
80ef504bef | ||
|
|
6ab83e25d3 | ||
|
|
688dc25990 | ||
|
|
c53b58c64f | ||
|
|
61d18edb5d | ||
|
|
07b5dfe28a | ||
|
|
f55cad0121 | ||
|
|
494299cd89 | ||
|
|
7eca969496 | ||
|
|
5cc071e5a8 | ||
|
|
0532f2744b | ||
|
|
43ea26a33a | ||
|
|
ec54e40a8b | ||
|
|
96292130a8 | ||
|
|
4b508f02e3 | ||
|
|
eec7e1b62a | ||
|
|
39e87eb8d3 | ||
|
|
780115ffb8 | ||
|
|
c7c4ba6e49 | ||
|
|
b9a7edf1f5 | ||
|
|
86bf2accc9 | ||
|
|
954c435404 | ||
|
|
538c1bd809 | ||
|
|
e0767881f2 | ||
|
|
7d0cbad326 | ||
|
|
0ce7703ab8 | ||
|
|
4362fdf35a | ||
|
|
4bcca2daf4 | ||
|
|
a1d83ac7b9 | ||
|
|
492729baf3 | ||
|
|
4003d21826 | ||
|
|
b10d398728 | ||
|
|
198df84b89 | ||
|
|
211fc22b2a | ||
|
|
4a0a5c9591 | ||
|
|
8f51afe198 | ||
|
|
40b6642ef0 | ||
|
|
0076cbaf54 | ||
|
|
14e0d57091 | ||
|
|
3e79ec9a61 | ||
|
|
42cd7d3b77 | ||
|
|
6f4c65c178 | ||
|
|
863424ffd7 | ||
|
|
c35cfbd170 | ||
|
|
8891000c64 | ||
|
|
b23088bd2f | ||
|
|
470151d79b | ||
|
|
df054537ee | ||
|
|
12f721982f | ||
|
|
22425726d1 | ||
|
|
94e8c1da68 | ||
|
|
55c305c569 | ||
|
|
ad028947e0 | ||
|
|
9c6aaf5365 | ||
|
|
519f7838c6 | ||
|
|
32d870b063 | ||
|
|
212e0753c8 | ||
|
|
711dc83f5c | ||
|
|
6314eeda0a | ||
|
|
cf783ea480 | ||
|
|
899fbef948 | ||
|
|
4a6d7952a7 | ||
|
|
5443226e27 | ||
|
|
81738b77f5 | ||
|
|
89ffe99dcc | ||
|
|
ede306b88e | ||
|
|
0acc49c57d | ||
|
|
5e9df605e4 | ||
|
|
2bbeb040c2 | ||
|
|
5689c36b12 | ||
|
|
f4a367d2de | ||
|
|
afe042ecfc | ||
|
|
590f9305b8 | ||
|
|
a69df3baf0 | ||
|
|
da124bbbbe | ||
|
|
539e0e2fd3 | ||
|
|
c108cd4780 | ||
|
|
9ffd6d4121 | ||
|
|
d84146fd88 | ||
|
|
30e6316e16 | ||
|
|
6835e585b0 | ||
|
|
49d4f43652 | ||
|
|
70dd9c7724 | ||
|
|
f386e47efa | ||
|
|
ec16159497 | ||
|
|
5ba2959eb4 | ||
|
|
e9c5b00628 | ||
|
|
8e1f43eb8b | ||
|
|
3788293bc0 | ||
|
|
89a2e26f25 | ||
|
|
2c453bd491 | ||
|
|
0aebf234f9 | ||
|
|
3e25ef4b5a | ||
|
|
3dc3edb83e | ||
|
|
4ebe144191 | ||
|
|
49cd06a9b4 | ||
|
|
e0208da0ac | ||
|
|
1f41770060 | ||
|
|
3eff06281c | ||
|
|
5848dfb4f3 | ||
|
|
89f27adce5 | ||
|
|
bbab7fa672 | ||
|
|
fb149796fa | ||
|
|
3939013415 | ||
|
|
7fe18e99c3 | ||
|
|
6ddfd29be8 | ||
|
|
46efad94db | ||
|
|
b7ea073204 | ||
|
|
0d9c90ceeb | ||
|
|
3ba23e1787 | ||
|
|
e365718556 | ||
|
|
d65a49a7e7 | ||
|
|
7960aaf5d5 | ||
|
|
32b3a7d1d0 | ||
|
|
53fb976fb6 | ||
|
|
fffe71aa7e | ||
|
|
75b0a3a407 | ||
|
|
158689672a | ||
|
|
24e43dd1a2 | ||
|
|
b34366aaf7 | ||
|
|
2856c8d125 | ||
|
|
4ac1e1d798 | ||
|
|
b15d23035c | ||
|
|
8b2ea63ccb | ||
|
|
b0c65c76e6 | ||
|
|
cc9ea82e5c | ||
|
|
c65c1af023 | ||
|
|
f10bd9c0d8 | ||
|
|
14e3bb07ec | ||
|
|
a6ac78294b | ||
|
|
ab1fe677d9 | ||
|
|
04c9ead19d | ||
|
|
703260b906 | ||
|
|
3c3798ee98 | ||
|
|
8df722ab02 | ||
|
|
dbe5ca60cd | ||
|
|
ec454dc981 | ||
|
|
0988f2145c | ||
|
|
3416c26bdc | ||
|
|
f1a50b7db3 | ||
|
|
f2fa13ba01 | ||
|
|
93fc3bf39e | ||
|
|
695180a2ef | ||
|
|
aeed138294 | ||
|
|
5d347096cf | ||
|
|
75ade97805 | ||
|
|
864d4b3cb7 | ||
|
|
c1492e3429 | ||
|
|
ae266810c2 | ||
|
|
a1d3fe5e06 | ||
|
|
98886ff074 | ||
|
|
b8b7a374a6 | ||
|
|
9d3647f38a | ||
|
|
5332eec7aa | ||
|
|
6bc933532e | ||
|
|
55053cd2b8 | ||
|
|
2e4317a80c | ||
|
|
5ef61f4621 | ||
|
|
4b47a5030a | ||
|
|
27c2dcbee4 | ||
|
|
e17ab16878 | ||
|
|
8c9fff36c8 | ||
|
|
c37ea3fbb6 | ||
|
|
6d6401d1da | ||
|
|
57f90c8b3b | ||
|
|
53883d290a | ||
|
|
ae0408a6f7 | ||
|
|
742abc9032 | ||
|
|
5652965853 | ||
|
|
da0c811846 | ||
|
|
d60dd5f281 | ||
|
|
7a24badff1 | ||
|
|
ec70c6c613 | ||
|
|
aa40b916ab | ||
|
|
fe5242174b | ||
|
|
026cc29e1f | ||
|
|
6055baa0db | ||
|
|
469e6da29f | ||
|
|
6a7fb2d33d | ||
|
|
271ea89c8d | ||
|
|
f94d7f2b03 | ||
|
|
f084e64aed | ||
|
|
294b817957 | ||
|
|
ed85ed394a | ||
|
|
aa495312db | ||
|
|
8ac9b23de3 | ||
|
|
be7090b29c | ||
|
|
1f9b31a6cd | ||
|
|
e83d27f07c | ||
|
|
816cbd2036 | ||
|
|
ec781969fa | ||
|
|
2fc78c9219 | ||
|
|
7b2470cce6 | ||
|
|
31c3fac7f5 | ||
|
|
9d5a7b7dbd | ||
|
|
614710da69 | ||
|
|
f233066e81 | ||
|
|
32ae38ebb2 | ||
|
|
1459229dde | ||
|
|
b8cd602d7e | ||
|
|
647a05f469 | ||
|
|
7cf9885125 | ||
|
|
da72afcd5f | ||
|
|
9f0cdb7ac1 | ||
|
|
482a85565f | ||
|
|
c563d781d3 | ||
|
|
12a6da6558 | ||
|
|
54accbbeff | ||
|
|
6d0bd4a6ed | ||
|
|
73d403d2f1 | ||
|
|
b8fa581665 | ||
|
|
71f661daa4 | ||
|
|
3927867c81 | ||
|
|
60256533e3 | ||
|
|
6a0cabf249 | ||
|
|
a00c696c1a | ||
|
|
7958ef0d1f | ||
|
|
d935cee4db | ||
|
|
d565160124 | ||
|
|
b4cb27cef6 | ||
|
|
d7c211d98e | ||
|
|
c32a358f43 | ||
|
|
1f4b23b105 | ||
|
|
58df9c6edb | ||
|
|
ce4578a829 | ||
|
|
1885b3ac2e | ||
|
|
205ddc88cb | ||
|
|
9da065e1ec | ||
|
|
a40846f6ed | ||
|
|
c3ff6fadc9 | ||
|
|
601bd5d6e7 | ||
|
|
8731f2afe5 | ||
|
|
323df36a97 | ||
|
|
bcf71b583c | ||
|
|
9268407429 | ||
|
|
1ff87d27ca | ||
|
|
d6e4b7700f | ||
|
|
81a4da6199 | ||
|
|
b8efc442e3 | ||
|
|
e00bdf2f79 | ||
|
|
cea5716c48 | ||
|
|
fc150f8860 | ||
|
|
7fdf8a63c8 | ||
|
|
374f17df63 | ||
|
|
d27da8927e | ||
|
|
785afd4bda | ||
|
|
e28c226308 | ||
|
|
4170e20e21 | ||
|
|
af548aa624 | ||
|
|
13d68bbac0 | ||
|
|
fabea3c813 | ||
|
|
db80e7f7cb | ||
|
|
1ebddbd1de | ||
|
|
6c6061a123 | ||
|
|
0bf38aed9f | ||
|
|
8aedbde36a | ||
|
|
17279cb3fe | ||
|
|
1b4823421f | ||
|
|
deef604325 | ||
|
|
22e44b47e6 | ||
|
|
08052b13cf | ||
|
|
5e7fe96ef6 | ||
|
|
83ff98cd3a | ||
|
|
eb54230445 | ||
|
|
59d57c50eb | ||
|
|
f358254e3c | ||
|
|
3ab8092a82 | ||
|
|
3b08b718ff | ||
|
|
a473719eee | ||
|
|
cd40e655fb | ||
|
|
5d468b4420 | ||
|
|
96806c613f | ||
|
|
6f2a4b2b03 | ||
|
|
d33efa0274 | ||
|
|
9b35ebc114 | ||
|
|
dba62157bf | ||
|
|
556184f442 | ||
|
|
c33532f952 | ||
|
|
8f61ceb4ea | ||
|
|
e19dcdc0c4 | ||
|
|
65f977786d | ||
|
|
95c13fd9a9 | ||
|
|
f8adf28df8 | ||
|
|
9d468379f2 | ||
|
|
8071e82390 | ||
|
|
3ce775ec05 | ||
|
|
1223a30127 | ||
|
|
f3906cab55 | ||
|
|
d33304e3ad | ||
|
|
ebed950392 | ||
|
|
4e6ab1c2bb | ||
|
|
543d85eb28 | ||
|
|
afe01a61ae | ||
|
|
b4c86835ed | ||
|
|
ab80bc1bf2 | ||
|
|
5e5a9fac00 | ||
|
|
09b6805886 | ||
|
|
33822350fd | ||
|
|
8c7b5891a2 | ||
|
|
864b3a34e9 | ||
|
|
a382a18d44 | ||
|
|
70e77247f6 | ||
|
|
915fe00434 | ||
|
|
2e64b0d54f | ||
|
|
ed4b885935 | ||
|
|
5bf5825f30 | ||
|
|
9888d128a2 | ||
|
|
a143e9d1a3 | ||
|
|
4878e8e43a | ||
|
|
b989272bce | ||
|
|
df927555e0 | ||
|
|
5e94d0b4af | ||
|
|
c73d4e82b5 | ||
|
|
a269da4e1c | ||
|
|
b49ca8087a | ||
|
|
7649095c03 | ||
|
|
a8223d2f3c | ||
|
|
53aef9ce0b | ||
|
|
bcceab4858 | ||
|
|
ac4064503a | ||
|
|
d9b115db37 | ||
|
|
a61f294eea | ||
|
|
7b4db30efd | ||
|
|
c8aece8003 | ||
|
|
ad8d473a2d | ||
|
|
55db14e758 | ||
|
|
bbfdba7615 | ||
|
|
681c559c79 | ||
|
|
4e39f45446 | ||
|
|
62c514acf2 | ||
|
|
48638e8ca2 | ||
|
|
cb44b575c2 | ||
|
|
1565fd33f7 | ||
|
|
2bf04e9818 |
@@ -22,7 +22,7 @@
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev",
|
||||
"postAttachCommand": "pnpm dev --filter=web... --filter=demo...",
|
||||
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node"
|
||||
|
||||
@@ -35,3 +35,5 @@ yarn-error.log*
|
||||
.vscode
|
||||
.github
|
||||
**/.turbo
|
||||
|
||||
.env
|
||||
|
||||
47
.env.example
@@ -8,8 +8,8 @@
|
||||
|
||||
WEBAPP_URL=http://localhost:3000
|
||||
|
||||
# Set this if you want to have a shorter link for surveys
|
||||
SHORT_URL_BASE=
|
||||
# Required for next-auth. Should be the same as WEBAPP_URL
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
# Encryption keys
|
||||
# Please set both for now, we will change this in the future
|
||||
@@ -17,24 +17,20 @@ SHORT_URL_BASE=
|
||||
# You can use: `openssl rand -hex 32` to generate one
|
||||
ENCRYPTION_KEY=
|
||||
|
||||
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
|
||||
# You can use: `openssl rand -hex 32` to generate a secure one
|
||||
NEXTAUTH_SECRET=
|
||||
|
||||
# API Secret for running cron jobs. (mandatory)
|
||||
# You can use: `openssl rand -hex 32` to generate a secure one
|
||||
CRON_SECRET=
|
||||
|
||||
##############
|
||||
# DATABASE #
|
||||
##############
|
||||
|
||||
DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=public'
|
||||
|
||||
###############
|
||||
# NEXT AUTH #
|
||||
###############
|
||||
|
||||
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
|
||||
# You can use: `openssl rand -hex 32` to generate one
|
||||
NEXTAUTH_SECRET=RANDOM_STRING
|
||||
|
||||
# Set this to your public-facing URL, e.g., https://example.com
|
||||
# You do not need the NEXTAUTH_URL environment variable in Vercel.
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
################
|
||||
# MAIL SETUP #
|
||||
################
|
||||
@@ -50,6 +46,9 @@ SMTP_SECURE_ENABLED=0
|
||||
SMTP_USER=smtpUser
|
||||
SMTP_PASSWORD=smtpPassword
|
||||
|
||||
# If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs (default is 1).
|
||||
# SMTP_REJECT_UNAUTHORIZED_TLS=0
|
||||
|
||||
########################################################################
|
||||
# ------------------------------ OPTIONAL -----------------------------#
|
||||
########################################################################
|
||||
@@ -71,6 +70,8 @@ S3_BUCKET_NAME=
|
||||
# Configure a third party S3 compatible storage service endpoint like StorJ leave empty if you use Amazon S3
|
||||
# e.g., https://gateway.storjshare.io
|
||||
S3_ENDPOINT_URL=
|
||||
# Force path style for S3 compatible storage (0 for disabled, 1 for enabled)
|
||||
S3_FORCE_PATH_STYLE=0
|
||||
|
||||
#####################
|
||||
# Disable Features #
|
||||
@@ -83,6 +84,7 @@ EMAIL_VERIFICATION_DISABLED=1
|
||||
PASSWORD_RESET_DISABLED=1
|
||||
|
||||
# Signup. Disable the ability for new users to create an account.
|
||||
# Note: This variable is only available to the SaaS setup of Formbricks Cloud. Signup is disable by default for self-hosting.
|
||||
# SIGNUP_DISABLED=1
|
||||
|
||||
# Email login. Disable the ability for users to login with email.
|
||||
@@ -120,9 +122,6 @@ AZUREAD_TENANT_ID=
|
||||
# OIDC_DISPLAY_NAME=
|
||||
# OIDC_SIGNING_ALGORITHM=
|
||||
|
||||
# Cron Secret
|
||||
CRON_SECRET=
|
||||
|
||||
# Configure this when you want to ship JS & CSS files from a complete URL instead of the current domain
|
||||
# ASSET_PREFIX_URL=
|
||||
|
||||
@@ -160,9 +159,6 @@ ENTERPRISE_LICENSE_KEY=
|
||||
# DEFAULT_ORGANIZATION_ID=
|
||||
# DEFAULT_ORGANIZATION_ROLE=admin
|
||||
|
||||
# set to 1 to skip onboarding for new users
|
||||
# ONBOARDING_DISABLED=1
|
||||
|
||||
# Send new users to customer.io
|
||||
# CUSTOMER_IO_API_KEY=
|
||||
# CUSTOMER_IO_SITE_ID=
|
||||
@@ -177,7 +173,16 @@ ENTERPRISE_LICENSE_KEY=
|
||||
UNSPLASH_ACCESS_KEY=
|
||||
|
||||
# The below is used for Next Caching (uses In-Memory from Next Cache if not provided)
|
||||
# REDIS_URL:
|
||||
# REDIS_URL=redis://localhost:6379
|
||||
|
||||
# The below is used for Rate Limiting (uses In-Memory LRU Cache if not provided) (You can use a service like Webdis for this)
|
||||
# REDIS_HTTP_URL:
|
||||
|
||||
# Disable custom cache handler if necessary (e.g. if deployed on Vercel)
|
||||
# CUSTOM_CACHE_DISABLED=1
|
||||
|
||||
# Azure AI settings
|
||||
# AI_AZURE_RESSOURCE_NAME=
|
||||
# AI_AZURE_API_KEY=
|
||||
# AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID=
|
||||
# AI_AZURE_LLM_DEPLOYMENT_ID=
|
||||
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
packages/config-eslint/
|
||||
10
.eslintrc.js
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// This tells ESLint to load the config from the package `eslint-config-formbricks`
|
||||
extends: ["formbricks"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/"],
|
||||
},
|
||||
},
|
||||
};
|
||||
33
.github/ISSUE_TEMPLATE/oss-gg-hack-submission.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: oss.gg hack submission 🕹️
|
||||
description: "Submit your contribution for the for the oss.gg hackathon"
|
||||
title: "[🕹️]"
|
||||
labels: 🕹️ oss.gg, player submission, hacktoberfest
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: contribution-name
|
||||
attributes:
|
||||
label: What side quest or challenge are you solving?
|
||||
description: Add the name of the side quest or challenge.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: points
|
||||
attributes:
|
||||
label: Points
|
||||
description: How many points are assigned to this contribution?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What's the task your performed?
|
||||
validations:
|
||||
- type: textarea
|
||||
id: proof
|
||||
attributes:
|
||||
label: Provide proof that you've completed the task
|
||||
description: Screenshots, loom recordings, links to the content you shared or interacted with.
|
||||
validations:
|
||||
required: true
|
||||
24
.github/actions/cache-build-web/action.yml
vendored
@@ -30,6 +30,10 @@ runs:
|
||||
**/dist/**
|
||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}
|
||||
|
||||
- name: Set Cache Hit Status
|
||||
run: echo "cache-hit=${{ steps.cache-build.outputs.cache-hit }}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -37,7 +41,7 @@ runs:
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v4
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -49,20 +53,18 @@ runs:
|
||||
run: cp .env.example .env
|
||||
shell: bash
|
||||
|
||||
- name: Add E2E Testing Mode
|
||||
- name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env
|
||||
run: |
|
||||
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
echo "ENTERPRISE_LICENSE_KEY=$SECRET" >> $GITHUB_ENV
|
||||
RANDOM_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
|
||||
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
|
||||
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
|
||||
sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${RANDOM_KEY}/" .env
|
||||
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> .env
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
pnpm build --filter=web...
|
||||
pnpm build --filter=@formbricks/web...
|
||||
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
|
||||
24
.github/workflows/build-web.yml
vendored
@@ -11,24 +11,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
- name: Build & Cache Web Binaries
|
||||
uses: ./.github/actions/cache-build-web
|
||||
id: cache-build-web
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Formbricks-web
|
||||
run: pnpm build --filter=web...
|
||||
e2e_testing_mode: "0"
|
||||
|
||||
30
.github/workflows/chromatic.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: "Chromatic"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
chromatic:
|
||||
name: Run Chromatic
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
- name: Run Chromatic
|
||||
uses: chromaui/action@latest
|
||||
with:
|
||||
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
workingDir: apps/storybook
|
||||
24
.github/workflows/cron-reportUsageToStripe.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Cron - Report usage to Stripe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||
# schedule:
|
||||
# This will run the job at 20:00 UTC every day of every month.
|
||||
# - cron: "0 20 * * *"
|
||||
jobs:
|
||||
cron-reportUsageToStripe:
|
||||
env:
|
||||
APP_URL: ${{ secrets.APP_URL }}
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: cURL request
|
||||
if: ${{ env.APP_URL && env.CRON_SECRET }}
|
||||
run: |
|
||||
curl ${{ env.APP_URL }}/api/cron/report-usage \
|
||||
-X POST \
|
||||
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
|
||||
-H 'Cache-Control: no-cache' \
|
||||
--fail
|
||||
6
.github/workflows/cron-weeklySummary.yml
vendored
@@ -4,9 +4,9 @@ on:
|
||||
workflow_dispatch:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||
# schedule:
|
||||
# Runs “At 08:00 on Monday.” (see https://crontab.guru)
|
||||
# - cron: "0 8 * * 1"
|
||||
schedule:
|
||||
# Runs “At 08:00 on Monday.” (see https://crontab.guru)
|
||||
- cron: "0 8 * * 1"
|
||||
jobs:
|
||||
cron-weeklySummary:
|
||||
env:
|
||||
|
||||
84
.github/workflows/e2e.yml
vendored
@@ -2,67 +2,83 @@ name: E2E Tests
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
env:
|
||||
TELEMETRY_DISABLED: 1
|
||||
jobs:
|
||||
build:
|
||||
name: Run E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
env:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd="pg_isready -U testuser"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Build & Cache Web Binaries
|
||||
uses: ./.github/actions/cache-build-web
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
e2e_testing_mode: "1"
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
shell: bash
|
||||
|
||||
- name: Start PostgreSQL
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
shell: bash
|
||||
|
||||
- name: Fill ENCRYPTION_KEY, ENTERPRISE_LICENSE_KEY and E2E_TESTING in .env
|
||||
run: |
|
||||
cd packages/database && pnpm db:up &
|
||||
for attempt in {1..20}; do
|
||||
if nc -zv localhost 5432; then
|
||||
echo "Ready"
|
||||
break
|
||||
fi
|
||||
echo "Waiting..."
|
||||
sleep 5
|
||||
done
|
||||
pnpm db:migrate:dev
|
||||
- name: Serve packages for lazy loading
|
||||
RANDOM_KEY=$(openssl rand -hex 32)
|
||||
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
|
||||
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
|
||||
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
|
||||
sed -i "s/ENTERPRISE_LICENSE_KEY=.*/ENTERPRISE_LICENSE_KEY=${RANDOM_KEY}/" .env
|
||||
echo "E2E_TESTING=1" >> .env
|
||||
shell: bash
|
||||
|
||||
- name: Build App
|
||||
run: |
|
||||
cd packages/surveys && pnpm serve &
|
||||
pnpm build --filter=@formbricks/web...
|
||||
|
||||
- name: Apply Prisma Migrations
|
||||
run: |
|
||||
pnpm prisma migrate deploy
|
||||
|
||||
- name: Run App
|
||||
run: |
|
||||
NODE_ENV=test pnpm start --filter=web &
|
||||
for attempt in {1..20}; do
|
||||
NODE_ENV=test pnpm start --filter=@formbricks/web &
|
||||
sleep 10 # Optional: gives some buffer for the app to start
|
||||
for attempt in {1..10}; do
|
||||
if [ $(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/health) -eq 200 ]; then
|
||||
echo "Ready"
|
||||
echo "Application is ready."
|
||||
break
|
||||
fi
|
||||
echo "Waiting..."
|
||||
if [ $attempt -eq 10 ]; then
|
||||
echo "Application failed to start in time."
|
||||
exit 1
|
||||
fi
|
||||
echo "Still waiting for the application to be ready..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Test Serve endpoints
|
||||
run: |
|
||||
curl -s http://localhost:3003
|
||||
|
||||
- name: Cache Playwright
|
||||
uses: actions/cache@v3
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install Playwright
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Run E2E Tests
|
||||
|
||||
131
.github/workflows/kamal-deploy.yml
vendored
@@ -1,131 +0,0 @@
|
||||
name: Kamal Deploy
|
||||
concurrency:
|
||||
group: deploy-to-kamal
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
|
||||
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
|
||||
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
|
||||
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
||||
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
|
||||
SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }}
|
||||
MAIL_FROM: ${{ secrets.MAIL_FROM }}
|
||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
||||
SMTP_PORT: ${{ secrets.SMTP_PORT }}
|
||||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
||||
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
|
||||
TERMS_URL: ${{ vars.TERMS_URL }}
|
||||
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
|
||||
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
|
||||
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
|
||||
AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }}
|
||||
AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }}
|
||||
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
|
||||
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
|
||||
OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }}
|
||||
OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }}
|
||||
OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }}
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }}
|
||||
NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }}
|
||||
NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }}
|
||||
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
|
||||
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
|
||||
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
||||
GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }}
|
||||
GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }}
|
||||
GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }}
|
||||
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
|
||||
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
|
||||
DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }}
|
||||
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
|
||||
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
|
||||
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
NODE_ENV: production
|
||||
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
|
||||
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
|
||||
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
|
||||
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
|
||||
S3_REGION: ${{ vars.S3_REGION }}
|
||||
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
|
||||
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
|
||||
RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }}
|
||||
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
||||
DB_HOST: ${{ secrets.DB_HOST }}
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
DB_NAME: ${{ secrets.DB_NAME }}
|
||||
REDIS_URL: ${{ secrets.REDIS_URL }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.0
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
gem install kamal
|
||||
|
||||
- uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Create builder
|
||||
run: docker buildx create --use --name formbricks-gh-actions-builder
|
||||
if: steps.buildx.outputs.should_create_builder == 'true'
|
||||
|
||||
- name: Push env variables to Kamal
|
||||
run: |
|
||||
kamal() { command kamal "$@" -c kamal/deploy.yml; }
|
||||
kamal env push
|
||||
|
||||
- name: Run deploy command
|
||||
run: |
|
||||
kamal() { command kamal "$@" -c kamal/deploy.yml; }
|
||||
set +e
|
||||
DEPLOY_OUTPUT=$(kamal deploy 2>&1)
|
||||
DEPLOY_EXIT_CODE=$?
|
||||
echo "$DEPLOY_OUTPUT"
|
||||
if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then
|
||||
echo "Deployment reported healthy container. Considering as success."
|
||||
kamal lock release
|
||||
exit 0
|
||||
else
|
||||
exit $DEPLOY_EXIT_CODE
|
||||
fi
|
||||
shell: bash
|
||||
128
.github/workflows/kamal-setup.yml
vendored
@@ -1,128 +0,0 @@
|
||||
name: Kamal Setup
|
||||
concurrency:
|
||||
group: setup-kamal
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Only to be triggered when accessories are updated
|
||||
|
||||
jobs:
|
||||
Setup:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
|
||||
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
|
||||
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
|
||||
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
||||
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
|
||||
SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }}
|
||||
MAIL_FROM: ${{ secrets.MAIL_FROM }}
|
||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
||||
SMTP_PORT: ${{ secrets.SMTP_PORT }}
|
||||
SMTP_USER: ${{ secrets.SMTP_USER }}
|
||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
||||
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
|
||||
TERMS_URL: ${{ vars.TERMS_URL }}
|
||||
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
|
||||
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
|
||||
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
|
||||
AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }}
|
||||
AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }}
|
||||
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
|
||||
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
|
||||
OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }}
|
||||
OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }}
|
||||
OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }}
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }}
|
||||
NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }}
|
||||
NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }}
|
||||
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
|
||||
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
|
||||
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
||||
GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }}
|
||||
GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }}
|
||||
GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }}
|
||||
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
|
||||
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
|
||||
DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }}
|
||||
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
|
||||
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
|
||||
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
NODE_ENV: production
|
||||
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
|
||||
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
|
||||
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
|
||||
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
|
||||
S3_REGION: ${{ vars.S3_REGION }}
|
||||
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
|
||||
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
|
||||
RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }}
|
||||
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
||||
DB_HOST: ${{ secrets.DB_HOST }}
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
DB_NAME: ${{ secrets.DB_NAME }}
|
||||
REDIS_URL: ${{ secrets.REDIS_URL }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.0
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
gem install kamal
|
||||
|
||||
- uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Create builder
|
||||
run: docker buildx create --use --name formbricks-gh-actions-builder
|
||||
if: steps.buildx.outputs.should_create_builder == 'true'
|
||||
|
||||
- name: Push env variables to Kamal
|
||||
run: |
|
||||
kamal() { command kamal "$@" -c kamal/deploy.yml; }
|
||||
kamal env push
|
||||
|
||||
- name: Run setup command
|
||||
run: |
|
||||
kamal() { command kamal "$@" -c kamal/deploy.yml; }
|
||||
set +e
|
||||
DEPLOY_OUTPUT=$(kamal setup 2>&1)
|
||||
DEPLOY_EXIT_CODE=$?
|
||||
echo "$DEPLOY_OUTPUT"
|
||||
if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then
|
||||
echo "Deployment reported healthy container. Considering as success."
|
||||
kamal lock release
|
||||
exit 0
|
||||
else
|
||||
exit $DEPLOY_EXIT_CODE
|
||||
fi
|
||||
shell: bash
|
||||
14
.github/workflows/lint.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
- name: Setup Node.js 18.x
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
@@ -25,10 +25,12 @@ jobs:
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
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: Lint
|
||||
run: pnpm lint
|
||||
|
||||
7
.github/workflows/release-changesets.yml
vendored
@@ -1,9 +1,10 @@
|
||||
name: Release Changesets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
#push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
name: Docker for Data Migrations
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -12,7 +7,6 @@ on:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: formbricks/data-migrations
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public"
|
||||
@@ -23,8 +17,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# This is used to complete the identity challenge
|
||||
# with sigstore/fulcio when running outside of PRs.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
@@ -50,6 +42,7 @@ jobs:
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=${{ github.ref_name }}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
@@ -66,3 +59,4 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
91
.github/workflows/release-docker-github-experimental.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: Docker Release to Github Experimental
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}-experimental
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# This is used to complete the identity challenge
|
||||
# with sigstore/fulcio when running outside of PRs.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
|
||||
# Install the cosign tool except on PR
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3 # v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5 # v5.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: tw0fqmsx3c
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
context: .
|
||||
file: ./apps/web/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# Sign the resulting Docker image digest except on PRs.
|
||||
# This will only write to the public Rekor transparency log when the Docker
|
||||
# repository is public to avoid leaking data. If you would like to publish
|
||||
# transparency data even for private images, pass --force to cosign below.
|
||||
# https://github.com/sigstore/cosign
|
||||
- name: Sign the published Docker image
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
||||
TAGS: ${{ steps.meta.outputs.tags }}
|
||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
||||
# This step uses the identity token to provision an ephemeral certificate
|
||||
# against the sigstore community Fulcio instance.
|
||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
||||
14
.github/workflows/semantic-pull-requests.yml
vendored
@@ -20,6 +20,20 @@ jobs:
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
fix
|
||||
feat
|
||||
chore
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
revert
|
||||
ossgg
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
|
||||
10
.github/workflows/test.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
@@ -25,10 +25,12 @@ jobs:
|
||||
- name: create .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Random ENCRYPTION_KEY
|
||||
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
|
||||
run: |
|
||||
SECRET=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
|
||||
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: Test
|
||||
run: pnpm test
|
||||
|
||||
2
.gitignore
vendored
@@ -56,5 +56,5 @@ Zone.Identifier
|
||||
packages/lib/uploads
|
||||
|
||||
# Vite Timestamps
|
||||
vite.config.*.timestamp-*
|
||||
*vite.config.*.timestamp-*
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ tasks:
|
||||
gp sync-await init &&
|
||||
cp .env.example .env &&
|
||||
sed -i -r "s#^(WEBAPP_URL=).*#\1 $(gp url 3000)#" .env &&
|
||||
sed -i -r "s#^(NEXTAUTH_URL=).*#\1 $(gp url 3000)#" .env &&
|
||||
RANDOM_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
sed -i 's/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY='"$RANDOM_ENCRYPTION_KEY"'/' .env
|
||||
turbo --filter "@formbricks/web" go
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint-staged
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A sample post-deploy hook
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Rebooted Traefik on $KAMAL_HOSTS"
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A sample pre-build hook
|
||||
#
|
||||
# Checks:
|
||||
# 1. We have a clean checkout
|
||||
# 2. A remote is configured
|
||||
# 3. The branch has been pushed to the remote
|
||||
# 4. The version we are deploying matches the remote
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "Git checkout is not clean, aborting..." >&2
|
||||
git status --porcelain >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
first_remote=$(git remote)
|
||||
|
||||
if [ -z "$first_remote" ]; then
|
||||
echo "No git remote set, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_branch=$(git branch --show-current)
|
||||
|
||||
if [ -z "$current_branch" ]; then
|
||||
echo "Not on a git branch, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
||||
|
||||
if [ -z "$remote_head" ]; then
|
||||
echo "Branch not pushed to remote, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
||||
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-connect check
|
||||
#
|
||||
# Warms DNS before connecting to hosts in parallel
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
hosts = ENV["KAMAL_HOSTS"].split(",")
|
||||
results = nil
|
||||
max = 3
|
||||
|
||||
elapsed = Benchmark.realtime do
|
||||
results = hosts.map do |host|
|
||||
Thread.new do
|
||||
tries = 1
|
||||
|
||||
begin
|
||||
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
||||
rescue SocketError
|
||||
if tries < max
|
||||
puts "Retrying DNS warmup: #{host}"
|
||||
tries += 1
|
||||
sleep rand
|
||||
retry
|
||||
else
|
||||
puts "DNS warmup failed: #{host}"
|
||||
host
|
||||
end
|
||||
end
|
||||
|
||||
tries
|
||||
end
|
||||
end.map(&:value)
|
||||
end
|
||||
|
||||
retries = results.sum - hosts.size
|
||||
nopes = results.count { |r| r == max }
|
||||
|
||||
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-deploy hook
|
||||
#
|
||||
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
|
||||
#
|
||||
# Fails unless the combined status is "success"
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_COMMAND
|
||||
# KAMAL_SUBCOMMAND
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
# Only check the build status for production deployments
|
||||
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
|
||||
exit 0
|
||||
end
|
||||
|
||||
require "bundler/inline"
|
||||
|
||||
# true = install gems so this is fast on repeat invocations
|
||||
gemfile(true, quiet: true) do
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "octokit"
|
||||
gem "faraday-retry"
|
||||
end
|
||||
|
||||
MAX_ATTEMPTS = 72
|
||||
ATTEMPTS_GAP = 10
|
||||
|
||||
def exit_with_error(message)
|
||||
$stderr.puts message
|
||||
exit 1
|
||||
end
|
||||
|
||||
class GithubStatusChecks
|
||||
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
||||
|
||||
def initialize
|
||||
@remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
|
||||
@git_sha = `git rev-parse HEAD`.strip
|
||||
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
||||
refresh!
|
||||
end
|
||||
|
||||
def refresh!
|
||||
@combined_status = github_client.combined_status(remote_url, git_sha)
|
||||
end
|
||||
|
||||
def state
|
||||
combined_status[:state]
|
||||
end
|
||||
|
||||
def first_status_url
|
||||
first_status = combined_status[:statuses].find { |status| status[:state] == state }
|
||||
first_status && first_status[:target_url]
|
||||
end
|
||||
|
||||
def complete_count
|
||||
combined_status[:statuses].count { |status| status[:state] != "pending"}
|
||||
end
|
||||
|
||||
def total_count
|
||||
combined_status[:statuses].count
|
||||
end
|
||||
|
||||
def current_status
|
||||
if total_count > 0
|
||||
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
|
||||
else
|
||||
"Build not started..."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
puts "Checking build status..."
|
||||
attempts = 0
|
||||
checks = GithubStatusChecks.new
|
||||
|
||||
begin
|
||||
loop do
|
||||
case checks.state
|
||||
when "success"
|
||||
puts "Checks passed, see #{checks.first_status_url}"
|
||||
exit 0
|
||||
when "failure"
|
||||
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
||||
when "pending"
|
||||
attempts += 1
|
||||
end
|
||||
|
||||
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
||||
|
||||
puts checks.current_status
|
||||
sleep(ATTEMPTS_GAP)
|
||||
checks.refresh!
|
||||
end
|
||||
rescue Octokit::NotFound
|
||||
exit_with_error "Build status could not be found"
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Rebooting Traefik on $KAMAL_HOSTS..."
|
||||
@@ -1,4 +1,4 @@
|
||||
const baseConfig = require("./packages/prettier-config/prettier-preset");
|
||||
const baseConfig = require("./packages/config-prettier/prettier-preset");
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
|
||||
4
LICENSE
@@ -1,9 +1,9 @@
|
||||
Copyright (c) 2023 Formbricks GmbH
|
||||
Copyright (c) 2024 Formbricks GmbH
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/" directory of this repository, if that directory exists, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/errors/" 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 "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 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.
|
||||
|
||||
|
||||
10
README.md
@@ -144,6 +144,12 @@ Or you can also deploy Formbricks on [RepoCloud](https://repocloud.io) using the
|
||||
|
||||
[](https://repocloud.io/details/?app_id=254)
|
||||
|
||||
##### Zeabur
|
||||
|
||||
Or you can also deploy Formbricks on [Zeabur](https://zeabur.com) using the button below.
|
||||
|
||||
[](https://zeabur.com/templates/G4TUJL)
|
||||
|
||||
<a id="development"></a>
|
||||
|
||||
## 👨💻 Development
|
||||
@@ -160,7 +166,7 @@ Here is what you need to be able to run Formbricks:
|
||||
|
||||
### Local Setup
|
||||
|
||||
To get started locally, we've got a [guide to help you](https://formbricks.com/docs/contributing/setup).
|
||||
To get started locally, we've got a [guide to help you](https://formbricks.com/docs/developer-docs/contributing/get-started#local-machine-setup).
|
||||
|
||||
### Gitpod Setup
|
||||
|
||||
@@ -184,7 +190,7 @@ Here are a few options:
|
||||
|
||||
- Upvote issues with 👍 reaction so we know what the demand for a particular issue is to prioritize it within the roadmap.
|
||||
|
||||
Please check out [our contribution guide](https://formbricks.com/docs/contributing/introduction) and our [list of open issues](https://github.com/formbricks/formbricks/issues) for more information.
|
||||
Please check out [our contribution guide](https://formbricks.com/docs/developer-docs/contributing/get-started) and our [list of open issues](https://github.com/formbricks/formbricks/issues) for more information.
|
||||
|
||||
## All Thanks To Our Contributors
|
||||
|
||||
|
||||
2
apps/demo-react-native/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
|
||||
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq
|
||||
7
apps/demo-react-native/.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ["@formbricks/eslint-config/react.js"],
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
};
|
||||
35
apps/demo-react-native/.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
0
apps/demo-react-native/.npmrc
Normal file
34
apps/demo-react-native/app.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "react-native-demo",
|
||||
"slug": "react-native-demo",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"jsEngine": "hermes",
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"infoPlist": {
|
||||
"NSCameraUsageDescription": "Take pictures for certain activities.",
|
||||
"NSPhotoLibraryUsageDescription": "Select pictures for certain activities.",
|
||||
"NSMicrophoneUsageDescription": "Need microphone access for recording videos."
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
apps/demo-react-native/assets/adaptive-icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
apps/demo-react-native/assets/favicon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
apps/demo-react-native/assets/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/demo-react-native/assets/splash.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
6
apps/demo-react-native/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = function babel(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
};
|
||||
};
|
||||
7
apps/demo-react-native/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { LogBox } from "react-native";
|
||||
import App from "./src/app";
|
||||
|
||||
registerRootComponent(App);
|
||||
|
||||
LogBox.ignoreAllLogs();
|
||||
21
apps/demo-react-native/metro.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const path = require("node:path");
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
|
||||
// Find the workspace root, this can be replaced with `find-yarn-workspace-root`
|
||||
const workspaceRoot = path.resolve(__dirname, "../..");
|
||||
const projectRoot = __dirname;
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
// 1. Watch all files within the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// 2. Let Metro know where to resolve packages, and in what order
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
|
||||
config.resolver.disableHierarchicalLookup = true;
|
||||
|
||||
module.exports = config;
|
||||
28
apps/demo-react-native/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@formbricks/demo-react-native",
|
||||
"version": "1.0.0",
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"dev": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"clean": "rimraf .turbo node_modules .expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/react-native": "workspace:*",
|
||||
"expo": "^51.0.26",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"react": "^18.2.0",
|
||||
"react-native": "^0.74.4",
|
||||
"react-native-webview": "13.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@types/react": "~18.2.79",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
52
apps/demo-react-native/src/app.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
||||
import Formbricks, { track } from "@formbricks/react-native";
|
||||
|
||||
LogBox.ignoreAllLogs();
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
if (!process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID) {
|
||||
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
|
||||
}
|
||||
|
||||
if (!process.env.EXPO_PUBLIC_API_HOST) {
|
||||
throw new Error("EXPO_PUBLIC_API_HOST is required");
|
||||
}
|
||||
|
||||
const config = {
|
||||
environmentId: process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
apiHost: process.env.EXPO_PUBLIC_API_HOST,
|
||||
userId: "random-user-id",
|
||||
attributes: {
|
||||
language: "en",
|
||||
testAttr: "attr-test",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Formbricks React Native SDK Demo</Text>
|
||||
|
||||
<Button
|
||||
title="Trigger Code Action"
|
||||
onPress={() => {
|
||||
track("code").catch((error: unknown) => {
|
||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||
console.error("Error tracking event:", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<StatusBar style="auto" />
|
||||
<Formbricks initConfig={config} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
6
apps/demo-react-native/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ShieldCheckIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import { classNames } from "../lib/utils";
|
||||
|
||||
const navigation = [
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next": "14.2.3",
|
||||
"lucide-react": "^0.418.0",
|
||||
"next": "14.2.5",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*"
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@formbricks/config-typescript": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
|
||||
import "../styles/globals.css";
|
||||
import "@formbricks/ui/globals.css";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js/app";
|
||||
|
||||
import { SurveySwitch } from "../../components/SurveySwitch";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
@@ -59,12 +57,12 @@ const AppPage = ({}) => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="min-h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col items-center gap-2 sm:flex-row">
|
||||
<SurveySwitch value="app" formbricks={formbricks} />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
@@ -119,7 +117,7 @@ const AppPage = ({}) => {
|
||||
</div>
|
||||
|
||||
<div className="md:grid md:grid-cols-3">
|
||||
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-800">
|
||||
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Reset person / pull data from Formbricks app
|
||||
</h3>
|
||||
@@ -142,7 +140,7 @@ const AppPage = ({}) => {
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
<button className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
No-Code Action
|
||||
</button>
|
||||
</div>
|
||||
@@ -171,7 +169,7 @@ const AppPage = ({}) => {
|
||||
onClick={() => {
|
||||
formbricks.setAttribute("Plan", "Free");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Plan to 'Free'
|
||||
</button>
|
||||
</div>
|
||||
@@ -194,7 +192,7 @@ const AppPage = ({}) => {
|
||||
onClick={() => {
|
||||
formbricks.setAttribute("Plan", "Paid");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Plan to 'Paid'
|
||||
</button>
|
||||
</div>
|
||||
@@ -217,7 +215,7 @@ const AppPage = ({}) => {
|
||||
onClick={() => {
|
||||
formbricks.setEmail("test@web.com");
|
||||
}}
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600">
|
||||
Set Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
import { SurveySwitch } from "../../components/SurveySwitch";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
@@ -36,7 +34,7 @@ const AppPage = ({}) => {
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const defaultAttributes = {
|
||||
language: "de",
|
||||
language: "en",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
@@ -58,9 +56,9 @@ const AppPage = ({}) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col items-center justify-between md:flex-row">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="min-h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div className="flex flex-col items-center gap-2 sm:flex-row">
|
||||
<SurveySwitch value="website" formbricks={formbricks} />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
@@ -115,7 +113,7 @@ const AppPage = ({}) => {
|
||||
</div>
|
||||
|
||||
<div className="md:grid md:grid-cols-3">
|
||||
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-800">
|
||||
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Reset person / pull data from Formbricks app
|
||||
</h3>
|
||||
@@ -130,6 +128,7 @@ const AppPage = ({}) => {
|
||||
}}>
|
||||
Reset
|
||||
</button>
|
||||
|
||||
<p className="text-xs text-slate-700 dark:text-slate-300">
|
||||
If you made a change in Formbricks app and it does not seem to work, hit 'Reset' and
|
||||
try again.
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,26 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Example on overriding packages/js colors */
|
||||
.dark {
|
||||
--fb-brand-color: red;
|
||||
--fb-brand-text-color: white;
|
||||
--fb-border-color: green;
|
||||
--fb-border-color-highlight: var(--slate-500);
|
||||
--fb-focus-color: red;
|
||||
--fb-heading-color: yellow;
|
||||
--fb-subheading-color: green;
|
||||
--fb-info-text-color: orange;
|
||||
--fb-signature-text-color: blue;
|
||||
--fb-survey-background-color: black;
|
||||
--fb-accent-background-color: rgb(13, 13, 12);
|
||||
--fb-accent-background-color-selected: red;
|
||||
--fb-placeholder-color: white;
|
||||
--fb-shadow-color: yellow;
|
||||
--fb-rating-fill: var(--yellow-300);
|
||||
--fb-rating-hover: var(--yellow-500);
|
||||
--fb-back-btn-border: currentColor;
|
||||
--fb-submit-btn-border: transparent;
|
||||
--fb-rating-selected: black;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"extends": "@formbricks/config-typescript/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
extends: ["@formbricks/eslint-config/legacy-next.js"],
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 53 KiB |
@@ -4,25 +4,20 @@ import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Using Actions in Formbricks | Fine-tuning User Moments",
|
||||
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.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Actions & Targeting
|
||||
# Actions
|
||||
|
||||
Understanding user thoughts and feelings at critical moments in their journey is pivotal. To achieve this, Formbricks uses user-centric actions that trigger surveys at precisely the right time. Actions are essentially notifications sent from your application to Formbricks when predefined user activities occur, making it possible to gather insights during key interactions.
|
||||
|
||||
<Note>
|
||||
Ensure that you’ve **initialized Formbricks with a userId** to fully utilize this feature along with other
|
||||
app survey capabilities.
|
||||
</Note>
|
||||
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 of that survey, while also recording the event. This capability ensures that surveys are not only triggered at the right time but are also tailored to the user’s recent interactions within the app. You can set up these actions through a user-friendly No-Code interface within the Formbricks dashboard.
|
||||
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?**
|
||||
|
||||
@@ -30,8 +25,7 @@ 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 Segments**: Analyze action data to create detailed user segments, targeting specific groups with surveys that are pertinent to their behaviors or interactions within the app.
|
||||
- **User Targeting**: Precise targeting based on user actions and attributes ensures that surveys are shown only to users who meet certain criteria, enhancing the relevance and effectiveness of each survey.
|
||||
- **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**
|
||||
|
||||
@@ -57,12 +51,35 @@ To add a No-Code Action:
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Here are three types of No-Code actions you can set up:
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
### **1. Page URL Action**
|
||||
### **1. Click Action**
|
||||
|
||||
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
|
||||
|
||||
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
### **2. Page view Action**
|
||||
|
||||
This action is triggered when a user visits a page within your application.
|
||||
|
||||
### **3. Exit Intent Action**
|
||||
|
||||
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
|
||||
|
||||
### **4. 50% Scroll Action**
|
||||
|
||||
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
|
||||
|
||||
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
|
||||
|
||||
<Note>
|
||||
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
|
||||
|
||||
### **URL Match Conditions**
|
||||
|
||||
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
|
||||
- **contains**: Activates when the URL contains the specified substring.
|
||||
- **startsWith**: Fires when the URL starts with the specified string.
|
||||
@@ -70,15 +87,7 @@ This action is triggered when a user visits a specific page within your applicat
|
||||
- **notMatch**: Triggers when the URL does not match the specified condition.
|
||||
- **notContains**: Activates when the URL does not contain the specified substring.
|
||||
|
||||
### **2. innerText Action**
|
||||
|
||||
Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
### **3. CSS Selector Action**
|
||||
|
||||
This action verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
<Note>You can have an action use combination of the 3 types as you wish</Note>
|
||||
</Note>
|
||||
|
||||
## **Setting Up Code Actions**
|
||||
|
||||
@@ -112,5 +121,3 @@ return <button onClick={handleClick}>Click Me</button>;
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
This documentation frames actions around user interactions, emphasizing the connection between the user's activities and the survey experience. By leveraging user-centric actions, you can create highly targeted and timely surveys that resonate with users and yield valuable insights.
|
||||
|
||||
@@ -1,34 +1,18 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
import { ResponsiveVideo } from "@/components/ResponsiveVideo";
|
||||
|
||||
import GermansGpt from "./germans-gpt.webp";
|
||||
import Hni from "./hni.webp";
|
||||
import PowerUsers from "./power-users.webp";
|
||||
import RideHailing from "./ride-hailing.webp";
|
||||
import UpsellMiro from "./upsell-miro.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Advanced Targeting in Surveys | Formbricks",
|
||||
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, user events, metadata , literally anything! This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code.",
|
||||
"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.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Advanced Targeting
|
||||
|
||||
Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, user events, 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.
|
||||
Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, device type, and more instead of spraying and praying. This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code.
|
||||
|
||||
<ResponsiveVideo title="Formbricks Multi-language Surveys"
|
||||
src="https://www.youtube-nocookie.com/embed/0BQp6N4cXzU?si=KeBM7G7Ch1xtrsOm&controls=0" />
|
||||
# How to setup Advanced Targeting
|
||||
|
||||
|
||||
## How to setup Advanced Targeting
|
||||
|
||||
<Note>
|
||||
Advanced Targeting is available on the Pro plan! Don't worry, you just need to enter your credit card
|
||||
details to start the freemium plan.
|
||||
</Note>
|
||||
<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.
|
||||
|
||||
@@ -36,7 +20,7 @@ Advanced Targeting allows you to show surveys to the right group of people. You
|
||||
|
||||
3. Give your segment a title & a description to help you remember what this segment is about.
|
||||
|
||||
4. Now click on the **Add Filter** button to add a filter. You can filter based on actions, user attributes, other segments, devices, and more.
|
||||
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**.
|
||||
|
||||
@@ -45,50 +29,3 @@ Advanced Targeting allows you to show surveys to the right group of people. You
|
||||
7. Once you are happy with the segment, click on **Save Segment**.
|
||||
|
||||
8. Now, when you create a survey, you can select this segment to target your survey to.
|
||||
|
||||
## Examples:
|
||||
|
||||
1. Let's say you want to upsell to: Miro, Loom, Figma, Slack and Asana.
|
||||
|
||||
<MdxImage
|
||||
src={UpsellMiro}
|
||||
alt="Upselling Opportunity"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Post-experience surveying for a ride hailing app where users who have taken more than 1 ride are shown a survey.
|
||||
|
||||
<MdxImage
|
||||
src={RideHailing}
|
||||
alt="Ride Hailing Targeting"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. Target High Value users who have $100k+ in their bank account, own 20+ stocks, and have are an active user.
|
||||
|
||||
<MdxImage
|
||||
src={Hni}
|
||||
alt="Target Active High Net Worth Individuals"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. Target Germans on mobile phones who have regenerated chatGPT answers frequently in the last quarter and did so today.
|
||||
|
||||
<MdxImage
|
||||
src={GermansGpt}
|
||||
alt="Target Germans on Mobile Phones who have regenerated chatGPT answers frequently in the last quarter and did so today"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. Sneak Peak: How we at Formbricks automate inviting power users to chat with us
|
||||
|
||||
<MdxImage
|
||||
src={PowerUsers}
|
||||
alt="Automate inviting power users to chat with us at Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
@@ -31,12 +31,18 @@ const libraries = [
|
||||
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 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3 dark:border-white/5">
|
||||
<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}
|
||||
|
||||
@@ -69,18 +69,18 @@ Refer to our [Example HTML project](https://github.com/formbricks/examples/tree/
|
||||
|
||||
## ReactJS
|
||||
|
||||
Install the Formbricks SDK using one of the package managers ie `npm`,`pnpm`,`yarn`.
|
||||
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
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -142,13 +142,13 @@ Code snippets for the integration for both conventions are provided to further a
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -164,7 +164,6 @@ yarn add @formbricks/js
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js/app";
|
||||
|
||||
export default function FormbricksProvider() {
|
||||
@@ -218,7 +217,6 @@ Refer to our [Example NextJS App Directory project](https://github.com/formbrick
|
||||
// other import
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js/app";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
@@ -348,6 +346,66 @@ router.afterEach((to, from) => {
|
||||
|
||||
Refer to our [Example VueJs project](https://github.com/formbricks/examples/tree/main/vuejs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
## React Native
|
||||
|
||||
Install the Formbricks React Native SDK using one of the package managers, i.e., npm, pnpm, or yarn.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/react-native
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/react-native
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/react-native
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Now, update your App.js/App.tsx file to initialize Formbricks:
|
||||
<Col>
|
||||
<CodeGroup title="src/App.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import Formbricks from "@formbricks/react-native";
|
||||
|
||||
const config = {
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<>
|
||||
{/* Your app content */}
|
||||
<Formbricks initConfig={config} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
---
|
||||
|
||||
## Validate your setup
|
||||
|
||||
Once you have completed the steps above, you can validate your setup by checking the **Setup Checklist** in the Settings. Your widget status indicator should go from this:
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
@@ -19,7 +19,7 @@ export const metadata = {
|
||||
|
||||
# Quickstart
|
||||
|
||||
App surveys have 6-10x better conversion rates than emailed out surveys. This tutorial explains how to run an app survey in your web app in 10 to 15 minutes. Let’s go!
|
||||
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!
|
||||
|
||||
<Note>
|
||||
App Surveys are ideal for websites that **have a user authentication** system. If you are looking to run
|
||||
@@ -37,7 +37,7 @@ App surveys have 6-10x better conversion rates than emailed out surveys. This tu
|
||||
src={I1}
|
||||
alt="Choose website survey from survey type"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **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:
|
||||
|
||||
@@ -41,9 +41,7 @@ To run the Churn Survey in your app you want to proceed as follows:
|
||||
4. Prevent that churn!
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running? We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (takes 15mins max.)](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Churn Survey
|
||||
@@ -80,9 +78,9 @@ In this case, you don’t really need to pre-segment your audience. You likely w
|
||||
|
||||
### 4. Set up a trigger
|
||||
|
||||
To create the trigger for your Churn Survey, you have two options to choose from:
|
||||
To create the trigger for your Churn Survey, you have three options to choose from:
|
||||
|
||||
1. **Trigger by innerText:** You likely have a “Cancel Subscription” button in your app. You can setup a user Action with the according `innerText` to trigger the survey, like so:
|
||||
1. **Trigger by Inner Text:** You likely have a “Cancel Subscription” button in your app. You can setup a user Action with the according `Inner Text` to trigger the survey, like so:
|
||||
|
||||
<MdxImage
|
||||
src={TriggerInnerText}
|
||||
@@ -100,7 +98,7 @@ To create the trigger for your Churn Survey, you have two options to choose from
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Trigger by pageURL:** Lastly, you could also display your survey on a subpage “/subscription-cancelled” where you forward users once they cancelled the trial subscription. You can then create a user Action with the type `pageURL` with the following settings:
|
||||
1. **Trigger by page view filters:** Lastly, you could also display your survey on a subpage “/subscription-cancelled” where you forward users once they cancelled the trial subscription. You can then create a user Action with the type `Page View` and add select `Limit to specific pages` to add url filters, with the following settings:
|
||||
|
||||
<MdxImage
|
||||
src={TriggerPageUrl}
|
||||
@@ -111,7 +109,7 @@ To create the trigger for your Churn Survey, you have two options to choose from
|
||||
|
||||
Whenever a user visits this page, matches the filter conditions above and the recontact options (below) the survey will be displayed ✅
|
||||
|
||||
Here is our complete [Actions manual](/actions/why) covering [Code](/actions/code) and [No-Code](/actions/no-code) Actions.
|
||||
Here is our complete [Actions manual](/app-surveys/actions/) covering [No-Code](/app-surveys/actions#setting-up-no-code-actions) and [Code](/app-surveys/actions#setting-up-code-actions) Actions.
|
||||
|
||||
<Note>
|
||||
## Pre-churn flow coming soon We’re currently building full-screen survey pop-ups. You’ll be able to prevent
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 59 KiB |
@@ -129,10 +129,8 @@ Locate that file. We are using the [Tailwind Template “Syntax”](https://tail
|
||||
```tsx
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@formbricks/ui/Button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover";
|
||||
|
||||
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 = () => {
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 59 KiB |
@@ -38,9 +38,10 @@ To run the Feature Chaser survey in your app you want to proceed as follows:
|
||||
2. Setup a user action to display survey at the right point in time
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web wapp. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web
|
||||
wapp. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start
|
||||
Guide (takes 15mins max.)](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Feature Chaser
|
||||
@@ -73,11 +74,11 @@ Save, and move over to where the magic happens: The “Audience” tab.
|
||||
|
||||
Before setting the right trigger, you need to identify a user action in your app which signals, that they have just used the feature you want to understand better. In most cases, it is clicking a specific button in your product.
|
||||
|
||||
You can create [Code Actions](/actions/code) and [No Code Actions](/actions/no-code) to follow users through your app. In this example, we will create a No Code Action.
|
||||
You can create [Code Actions](/app-surveys/actions#setting-up-code-actions) and [No Code Actions](/app-surveys/actions#setting-up-no-code-actions) to follow users through your app. In this example, we will create a No Code Action.
|
||||
|
||||
There are two ways to track a button:
|
||||
|
||||
1. **Trigger by innerText:** You might have a button with a unique text at the end of your feature e.g. "Export Report". You can setup a user Action with the according `innerText` to trigger the survey, like so:
|
||||
1. **Trigger by Inner Text:** You might have a button with a unique text at the end of your feature e.g. "Export Report". You can setup a user Action with the according `Inner Text` to trigger the survey, like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionText}
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 57 KiB |
@@ -65,19 +65,18 @@ Change the questions and answer options according to your preference:
|
||||
|
||||
### 3. Create user action to trigger Feedback Box:
|
||||
|
||||
Go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool No-Code User Action Tracker:
|
||||
Go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool User Action Tracker:
|
||||
|
||||
<MdxImage src={AddAction} alt="Add action" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
We have two options to track the Feedback Button in your application: innerText and CSS-Selector:
|
||||
|
||||
1. **innerText:** This means that whenever a user clicks any HTML item in your app which has an `innerText` of `Feedback` the Feedback Box will be displayed.
|
||||
2. **CSS-Selector:** This means that when an element with a specific CSS-Selector like `#feedback-button` is clicked, your Feedback Box is triggered.
|
||||
1. **Inner Text:** This means that whenever a user clicks any HTML item in your app which has an `Inner Text` of `Feedback` the Feedback Box will be displayed.
|
||||
|
||||
<div className="grid max-w-full grid-cols-2 space-x-2 sm:max-w-3xl">
|
||||
<MdxImage src={ActionText} alt="Add HTML action" quality="100" className="rounded-lg" />
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
</div>
|
||||
<MdxImage src={ActionText} alt="Add HTML action" quality="100" className="rounded-lg" />
|
||||
|
||||
2. **CSS Selector:** This means that when an element with a specific CSS-Selector like `#feedback-button` is clicked, your Feedback Box is triggered.
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
|
||||
### 4. Select action in the “When to ask” card
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 64 KiB |
@@ -38,9 +38,8 @@ To display the Trial Conversion Survey in your app you want to proceed as follow
|
||||
3. Print that 💸
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(takes 15mins max.)](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (takes 15mins max.)](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Trial Conversion Survey
|
||||
@@ -84,7 +83,7 @@ Pre-segmentation isn't relevant for this survey because you likely want to solve
|
||||
|
||||
How you trigger your survey depends on your product. There are two options:
|
||||
|
||||
1. **Trigger by pageURL:** Let’s say you have a page under “/trial-cancelled” where you forward users once they cancelled the trial subscription. You can then create an user Action with the type `pageURL` with the following settings:
|
||||
1. **Trigger by Page view:** Let’s say you have a page under “/trial-cancelled” where you forward users once they cancelled the trial subscription. You can then create an user Action with the type `Page View` and add select `Limit to specific pages` to add url filters, with the following settings:
|
||||
|
||||
<MdxImage
|
||||
src={ActionPageurl}
|
||||
@@ -95,7 +94,7 @@ How you trigger your survey depends on your product. There are two options:
|
||||
|
||||
Whenever a user visits this page, the survey will be displayed ✅
|
||||
|
||||
2. **Trigger by Button Click:** In a different case, you have a “Cancel Trial button in your app. You can setup a user Action with the according `innerText` like so:
|
||||
2. **Trigger by Button Click:** In a different case, you have a “Cancel Trial" button in your app. You can setup a user Action with the according `Inner Text` like so:
|
||||
|
||||
<MdxImage
|
||||
src={ActionText}
|
||||
@@ -104,7 +103,7 @@ Whenever a user visits this page, the survey will be displayed ✅
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Please have a look at our complete [Actions manual](/actions/why) if you have questions.
|
||||
Please have a look at our complete [Actions manual](/app-surveys/actions/) if you have questions.
|
||||
|
||||
### 5. Select Action in the “When to ask” card
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 34 KiB |
@@ -43,9 +43,8 @@ To display an Interview Prompt in your app you want to proceed as follows:
|
||||
3. That’s it! 🎉
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
(15mins).](/app-surveys/quickstart)
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide (15mins).](/app-surveys/quickstart)
|
||||
</Note>
|
||||
|
||||
### 1. Create new Interview Prompt
|
||||
@@ -86,9 +85,7 @@ Save, and move over to the “Audience” tab.
|
||||
### 3. Pre-segment your audience (coming soon)
|
||||
|
||||
<Note>
|
||||
## Filter by attribute coming soon.
|
||||
We're working on pre-segmenting users by attributes. We will update this
|
||||
manual in the next few days.
|
||||
## Filter by attribute coming soon. We're working on pre-segmenting users by attributes. We will update this manual in the next few days.
|
||||
</Note>
|
||||
|
||||
Once you clicked over to the “Audience” tab you can change the settings. In the **Who To Send** card, select “Filter audience by attribute”. This allows you to only show the prompt to a specific segment of your user base.
|
||||
@@ -99,13 +96,13 @@ Great, now only the “Power User” segment will see our Interview Prompt. But
|
||||
|
||||
### 4. Set up a trigger for the Interview Prompt:
|
||||
|
||||
To create the trigger to show your Interview Prompt, go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool No-Code User Action Tracker:
|
||||
To create the trigger to show your Interview Prompt, go to the “Audience” tab, find the “When to send” card and choose “Add Action”. We will now use our super cool User Action Tracker:
|
||||
|
||||
<MdxImage src={AddAction} alt="Add action" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
Generally, we have two types of user actions: Page views and clicks. The Interview Prompt, you’ll likely want to display it on a page visit since you already filter who sees the prompt by attributes.
|
||||
|
||||
1. **pageURL:** Whenever a user visits a page the survey will be displayed, as long as the other conditions match. Other conditions are pre-segmentation, if this user has seen a survey in the past 2 weeks, etc.
|
||||
1. **Page view:** Whenever a user visits a page the survey will be displayed, as long as the other conditions match. Other conditions are pre-segmentation, if this user has seen a survey in the past 2 weeks, etc.
|
||||
|
||||
<MdxImage
|
||||
src={ActionPageurl}
|
||||
@@ -114,7 +111,7 @@ Generally, we have two types of user actions: Page views and clicks. The Intervi
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **innerText & CSS-Selector:** When a user clicks an element (like a button) with a specific text content or CSS selector, the prompt will be displayed as long as the other conditions also match.
|
||||
2. **Click(Inner Text & CSS Selector):** When a user clicks an element (like a button) with a specific text content or CSS selector, the prompt will be displayed as long as the other conditions also match.
|
||||
|
||||
<div className="grid max-w-full grid-cols-2 space-x-2 sm:max-w-3xl">
|
||||
<MdxImage src={ActionCSS} alt="Add CSS action" quality="100" className="rounded-lg" />
|
||||
@@ -146,8 +143,7 @@ Scroll down to “Recontact Options”. Here you have to choose the correct sett
|
||||
<MdxImage src={Publish} alt="Publish survey" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
You need to have the Formbricks Widget installed to display the Feedback Box in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
## Formbricks Widget running? You need to have the Formbricks Widget installed to display the Feedback Box in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 55 KiB |
@@ -37,8 +37,8 @@ To display the Product-Market Fit survey in your app you want to proceed as foll
|
||||
3. Setup the user action to display survey at good point in time
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running?
|
||||
We assume that you have already installed the Formbricks Widget in your web app. It’s required to display messages and surveys in your app. If not, please follow the [Quick Start Guide
|
||||
## 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>
|
||||
|
||||
@@ -70,28 +70,15 @@ _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 (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>
|
||||
### 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).
|
||||
|
||||
**Filter by actions (coming soon)**: Later, you can also segment users based on events tracked with Formbricks. However, this makes it impossible to use historic usage data (pre Formbricks usage). Here we will have a few options to achieve that:
|
||||
|
||||
- Check the time passed since sign-up (e.g. signed up 4 weeks ago)
|
||||
- User has performed a specific action a certain number of times or (e.g. created 5 reports)
|
||||
- User has performed a combination of actions (e.g. created a report **and** invited a organization member)
|
||||
|
||||
This way you make sure that you separate potentially misleading opinions from valuable insights.
|
||||
|
||||
### 4. Set up a trigger for the Product-Market Fit survey:
|
||||
|
||||
You need a trigger to display the survey but in this case, the filtering does all the work. It’s up to you to decide to display the survey after the user viewed a specific subpage (pageURL) or after clicking an element. Have a look at the [Actions manual](/actions/why) if you are not sure how to set them up:
|
||||
You need a trigger to display the survey but in this case, the filtering does all the work. It’s up to you to decide to display the survey after the user viewed a specific subpage (pageURL) or after clicking an element. Have a look at the [Actions manual](/app-surveys/actions/) if you are not sure how to set them up:
|
||||
|
||||
<Col>
|
||||
|
||||
|
||||
@@ -79,28 +79,6 @@ Promise<{ id: string }, NetworkError | Error>
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
- Update Display
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Update Display">
|
||||
|
||||
```javascript {{ title: 'Update Display Method Call'}}
|
||||
await api.client.display.update(
|
||||
displayId: "<your-display-id>",
|
||||
{
|
||||
userId: "<your-user-id>", // optional
|
||||
responseId: "<your-response-id>", // optional
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Update Display Method Return Type' }}
|
||||
Promise<{ }, NetworkError | Error]>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Responses
|
||||
|
||||
- Create Response
|
||||
@@ -173,29 +151,6 @@ Promise<{ }, NetworkError | Error]>
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Action
|
||||
|
||||
- Create Action:
|
||||
|
||||
<Note> An environment cannot have 2 actions with the same name. </Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Create Action">
|
||||
|
||||
```javascript {{ title: 'Create Action Method Call'}}
|
||||
await api.client.action.create({
|
||||
name: "<your-action-name>", // required
|
||||
userId: "<your-user-id>", // required
|
||||
});
|
||||
```
|
||||
|
||||
```javascript {{ title: 'Create Action Method Return Type' }}
|
||||
Promise<{ }, NetworkError | Error]>
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## Attribute
|
||||
|
||||
- Update Attribute
|
||||
|
||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 56 KiB |
@@ -7,16 +7,16 @@ import I3 from "./images/3-survey-logs-in-app-survey-popup.webp";
|
||||
export const metadata = {
|
||||
title: "Formbricks App Survey SDK",
|
||||
description:
|
||||
"An overview of all available methods & how to integrate Formbricks App Surveys for frontend developers in web applications. Learn the key methods, configuration settings, and best practices.",
|
||||
"Integrate Formbricks App Surveys into your web apps with the Formbricks JS SDK for App Surveys. Learn how to initialize Formbricks, set attributes, track actions, and troubleshoot common issues.",
|
||||
};
|
||||
|
||||
#### Developer Docs
|
||||
|
||||
# SDK: App Survey
|
||||
# SDK: Run Surveys Inside Your Web Apps
|
||||
|
||||
### Overview
|
||||
|
||||
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. It’s available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. It’s available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
|
||||
### Install
|
||||
|
||||
@@ -26,9 +26,11 @@ The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Survey
|
||||
```js {{ title: 'npm' }}
|
||||
npm install @formbricks/js
|
||||
```
|
||||
|
||||
```js {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
```
|
||||
|
||||
```js {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
```
|
||||
@@ -51,9 +53,10 @@ import formbricks from "@formbricks/js/app";
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>", // required
|
||||
apiHost: "<your-api-host>", // required
|
||||
userId: "<user-id>" // required
|
||||
userId: "<user-id>", // required
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -67,15 +70,16 @@ Formbricks JS is a client SDK meant to be run client-side in their browser so ma
|
||||
|
||||
```js
|
||||
if (window !== undefined) {
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>",
|
||||
apiHost: "<your-api-host>",
|
||||
userId: "<user-id>"
|
||||
});
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>",
|
||||
apiHost: "<your-api-host>",
|
||||
userId: "<user-id>",
|
||||
});
|
||||
} else {
|
||||
console.error("Window object not accessible to init Formbricks");
|
||||
console.error("Window object not accessible to init Formbricks");
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -91,6 +95,7 @@ You can set custom attributes for the identified user. This can be helpful for s
|
||||
```js
|
||||
formbricks.setAttribute("Plan", "Paid");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -104,10 +109,10 @@ Track user actions to trigger surveys based on user interactions, such as button
|
||||
```js
|
||||
formbricks.track("Clicked on Claim");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
|
||||
### Logout
|
||||
|
||||
To log out and deinitialize Formbricks, use the formbricks.logout() function. This action clears the current initialization configuration and erases stored frontend information, such as the surveys a user has viewed or completed. It's an important step when a user logs out of your application or when you want to reset Formbricks.
|
||||
@@ -118,6 +123,7 @@ To log out and deinitialize Formbricks, use the formbricks.logout() function. Th
|
||||
```js
|
||||
formbricks.logout();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -133,6 +139,7 @@ Reset the current instance and fetch the latest surveys and state again:
|
||||
```js
|
||||
formbricks.reset();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -140,7 +147,11 @@ formbricks.reset();
|
||||
|
||||
Listen for page changes and dynamically show surveys configured via no-code actions in the Formbricks app:
|
||||
|
||||
<Note> This is only needed when your framework has a custom routing system and you want to trigger surveys on route changes. For example: NextJs</Note>
|
||||
<Note>
|
||||
{" "}
|
||||
This is only needed when your framework has a custom routing system and you want to trigger surveys on route
|
||||
changes. For example: NextJs
|
||||
</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup>
|
||||
@@ -148,6 +159,7 @@ Listen for page changes and dynamically show surveys configured via no-code acti
|
||||
```js
|
||||
formbricks.registerRouteChange();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
@@ -167,7 +179,7 @@ In case you don’t see your survey right away, here's what you can do. Go throu
|
||||
|
||||
### Formbricks Cloud and your app are not connected properly.
|
||||
|
||||
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the Setup Checklist in the Settings. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
|
||||
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the App connection in the Configuration. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,66 @@
|
||||
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!
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui/Accordion";
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@formbricks/ui/components/Accordion";
|
||||
import { FaqJsonLdComponent } from "./FAQPageJsonLd";
|
||||
|
||||
const FAQ_DATA = [
|
||||
170
apps/docs/app/developer-docs/contributing/get-started/page.mdx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
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
|
||||
|
||||
# Get started
|
||||
|
||||
We are so happy that you are interested in contributing to Formbricks 🤗 There are many ways to contribute to Formbricks like writing issues, fixing bugs, building new features or updating the docs.
|
||||
|
||||
- **Issues**: Spotted a bug? Has deployment gone wrong? Do you have user feedback? [Raise an issue](https://github.com/formbricks/formbricks/issues/new/choose) for the fastest response.
|
||||
- **Feature requests**: Raise an issue for these and tag it as an Enhancement. We love every idea. Please [open a feature request](https://github.com/formbricks/formbricks/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFEATURE%5D) clearly describing the problem you want to solve.
|
||||
- **How we Code at Formbricks**: [View this Notion document](https://formbricks.notion.site/How-we-code-at-Formbricks-8bcaa0304a20445db4871831149c0cf5?pvs=4) and understand the coding practises we follow so that you can adhere to them for uniformity.
|
||||
- **Creating a PR**: Please fork the repository, make your changes and create a new pull request if you want to make an update.
|
||||
- **E2E Tests**: [Understand how we write E2E tests](https://formbricks.notion.site/Formbricks-End-to-End-Tests-06dc830d71604deaa8da24714540f7ab?pvs=4) and make sure to consider writing a test whenever you ship a feature.
|
||||
- **New Question Types**:[Follow this guide](https://formbricks.notion.site/Guidelines-for-Implementing-a-New-Question-Type-9ac0d1c362714addb24b9abeb326d1c1?pvs=4) to keep everything in mind when you want to add a new question type.
|
||||
- **How to create a service**: [Read this document to understand how we use services](https://formbricks.notion.site/How-to-create-a-service-8e0c035704bb40cb9ea5e5beeeeabd67?pvs=4). This is particulalry important when you need to write a new one.
|
||||
|
||||
## Talk to us first
|
||||
|
||||
We highly recommend connecting with us on [Discord server](https://formbricks.com/discord) before you ship a contribution. This will increase the likelihood of your PR being merged. And it will decrease the likelihood of you wasting your time :)
|
||||
|
||||
## Contributor License Agreement (CLA)
|
||||
|
||||
To be able to keep working on Formbricks over the coming years, we need to collect a CLA from all relevant contributors.
|
||||
|
||||
Once you open a PR, you will get a message from the CLA bot to fill out the form. Please note that we can only get your contribution merged when we have a CLA signed by you.
|
||||
|
||||
## Setup Dev Environment
|
||||
|
||||
We currently officially support the below methods to set up your development environment for Formbricks:
|
||||
|
||||
- [Gitpod](/docs/developer-docs/contributing/gitpod)
|
||||
- [GitHub Codespaces](/docs/developer-docs/contributing/codespaces)
|
||||
- [Local Machine Setup](#local-machine-setup)
|
||||
|
||||
Both Gitpod and GitHub Codespaces have a **generous free tier** to explore and develop. For junior developers we suggest using either of these, because you can dive into coding within minutes, not hours.
|
||||
|
||||
## Local Machine Setup
|
||||
|
||||
<Note>
|
||||
The below only works for **Mac**, **Linux** & **WSL2** on Windows (not on pure Windows)!
|
||||
|
||||
This method is recommended **only for advanced users** & we won't be able to provide official support for this.
|
||||
|
||||
</Note>
|
||||
|
||||
To get the project running locally on your machine you need to have the following development tools installed:
|
||||
|
||||
- Node.JS (we recommend v20)
|
||||
- [pnpm](https://pnpm.io/)
|
||||
- [Docker](https://www.docker.com/) (to run PostgreSQL / MailHog)
|
||||
|
||||
1. Clone the project & move into the directory:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Git clone Formbricks monorepo">
|
||||
|
||||
```bash
|
||||
git clone https://github.com/formbricks/formbricks && cd formbricks
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
2. Install Node.JS packages via pnpm. Don't have pnpm? Get it [here](https://pnpm.io/installation)
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install dependencies via pnpm">
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
3. Create a `.env` file based on `.env.example`. It's already preset to work with the local development setup but you can also change values if needed.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Define environment variables">
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
4. Generate & set some secret values mandatory for the `ENCRYPTION_KEY`, `NEXTAUTH_SECRET` and `CRON_SECRET` in the .env file. You can use the following command to generate the random string of required length:
|
||||
|
||||
- For Linux
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="For Linux">
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
- For Mac
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="For Mac">
|
||||
|
||||
```bash
|
||||
sed -i '' '/^ENCRYPTION_KEY=/s|.*|ENCRYPTION_KEY='$(openssl rand -hex 32)'|' .env
|
||||
sed -i '' '/^NEXTAUTH_SECRET=/s|.*|NEXTAUTH_SECRET='$(openssl rand -hex 32)'|' .env
|
||||
sed -i '' '/^CRON_SECRET=/s|.*|CRON_SECRET='$(openssl rand -hex 32)'|' .env
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
5. Make sure you have [`Docker`](https://docs.docker.com/compose/) & [`docker-compose`](https://docs.docker.com/compose/) installed and running on your machine. Then run the following command to start the Formbricks dev setup:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Start Formbricks Dev Setup">
|
||||
|
||||
```bash
|
||||
pnpm go
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
This starts the Formbricks main app (plus all its dependencies) as well as the following services using Docker:
|
||||
|
||||
- A `postgres` container for hosting your database,
|
||||
- A `mailhog` container that acts as a mock SMTP server and shows received mails in a web UI (forwarded to your host's `localhost:8025`)
|
||||
- Demo App at [http://localhost:3002](http://localhost:3002)
|
||||
- Landing Page at [http://localhost:3001](http://localhost:3001)
|
||||
|
||||
<Note>
|
||||
**WSL2 users**: If you encounter connection issues with Prisma, ensure your WSL2 instance's PostgreSQL
|
||||
service is stopped before running `pnpm go`. Use the command `sudo systemctl stop postgresql` to stop the
|
||||
service.
|
||||
</Note>
|
||||
|
||||
**You can now access the Formbricks app on [http://localhost:3000](http://localhost:3000)**. You will be automatically redirected to the login. To use your local installation of formbricks, create a new account.
|
||||
|
||||
{" "}
|
||||
|
||||
<Note>
|
||||
A fresh setup does not have a default account. Please create a new account and proceed accordingly.
|
||||
</Note>
|
||||
|
||||
For viewing the emails sent by the system, you can access mailhog at [http://localhost:8025](http://localhost:8025)
|
||||
|
||||
### Build
|
||||
|
||||
To build all apps and packages and check for build errors, run the following command:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Build Formbricks stack">
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |