Compare commits
485 Commits
fix-docs-d
...
testing/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed383e7862 | ||
|
|
3db7c7946b | ||
|
|
2f63659739 | ||
|
|
b3757ae7c1 | ||
|
|
942bf818a5 | ||
|
|
74b03a54e1 | ||
|
|
5282637772 | ||
|
|
9165daffe9 | ||
|
|
de05d2abdf | ||
|
|
a907179c7d | ||
|
|
bc7d3e5da4 | ||
|
|
7042d73e99 | ||
|
|
fe7ca5a923 | ||
|
|
960edfd3f0 | ||
|
|
2b0df8280d | ||
|
|
13ce552a39 | ||
|
|
4d6665ab3e | ||
|
|
483bdc0eff | ||
|
|
8238b502fe | ||
|
|
01ceaa13ec | ||
|
|
35ff935a27 | ||
|
|
901cd42f56 | ||
|
|
63e1ac11cf | ||
|
|
744f3410ae | ||
|
|
0e73d81999 | ||
|
|
ba46782da4 | ||
|
|
25774f6f08 | ||
|
|
9a98772210 | ||
|
|
59a29dd3d6 | ||
|
|
e4fceb2e5e | ||
|
|
350c895d8c | ||
|
|
0b553447e0 | ||
|
|
6b64367d99 | ||
|
|
fe9746ba67 | ||
|
|
e4009d5951 | ||
|
|
b1ed61c247 | ||
|
|
10255aa102 | ||
|
|
774c6f19a5 | ||
|
|
ebf35ea582 | ||
|
|
f13efc954e | ||
|
|
9ee052a229 | ||
|
|
152fbede90 | ||
|
|
c9b8ffa9ef | ||
|
|
4ed1747ee2 | ||
|
|
88c492afd8 | ||
|
|
c7e0b02595 | ||
|
|
15aa9b2731 | ||
|
|
6f7359abf6 | ||
|
|
04e16d44a1 | ||
|
|
29131f93c2 | ||
|
|
1e2fe7b066 | ||
|
|
426a0a3847 | ||
|
|
80ef504bef | ||
|
|
6ab83e25d3 | ||
|
|
0bf5e0fd4c | ||
|
|
688dc25990 | ||
|
|
887c5c0eef | ||
|
|
c53b58c64f | ||
|
|
61d18edb5d | ||
|
|
af4ae38564 | ||
|
|
07b5dfe28a | ||
|
|
6b829744a1 | ||
|
|
f55cad0121 | ||
|
|
494299cd89 | ||
|
|
7b0d4926e8 | ||
|
|
334166b4b1 | ||
|
|
1685e77a35 | ||
|
|
927f97e9ad | ||
|
|
5cc071e5a8 | ||
|
|
76c437b16a | ||
|
|
0532f2744b | ||
|
|
43ea26a33a | ||
|
|
ec54e40a8b | ||
|
|
0c2d425a45 | ||
|
|
7aa827d1e1 | ||
|
|
d4961f1840 | ||
|
|
4b508f02e3 | ||
|
|
eec7e1b62a | ||
|
|
39e87eb8d3 | ||
|
|
6772ea6be4 | ||
|
|
9bacc88063 | ||
|
|
005c777c9c | ||
|
|
560dce3bbf | ||
|
|
ffd45a6f20 | ||
|
|
1ddd82c084 | ||
|
|
780115ffb8 | ||
|
|
647539c617 | ||
|
|
a5a3161f7c | ||
|
|
0535581f6d | ||
|
|
97b04f9e43 | ||
|
|
c7c4ba6e49 | ||
|
|
b9a7edf1f5 | ||
|
|
86bf2accc9 | ||
|
|
6ec0861c49 | ||
|
|
954c435404 | ||
|
|
538c1bd809 | ||
|
|
8542320a8e | ||
|
|
e0767881f2 | ||
|
|
7d3c8d35e1 | ||
|
|
7d0cbad326 | ||
|
|
0ce7703ab8 | ||
|
|
4362fdf35a | ||
|
|
4bcca2daf4 | ||
|
|
6abcf91a07 | ||
|
|
a1d83ac7b9 | ||
|
|
492729baf3 | ||
|
|
4003d21826 | ||
|
|
9a7887d9fd | ||
|
|
b10d398728 | ||
|
|
198df84b89 | ||
|
|
211fc22b2a | ||
|
|
7dd8bb95bd | ||
|
|
4a0a5c9591 | ||
|
|
8f51afe198 | ||
|
|
40b6642ef0 | ||
|
|
0076cbaf54 | ||
|
|
14e0d57091 | ||
|
|
df40c0ef11 | ||
|
|
7e14b86a63 | ||
|
|
a1ba3af439 | ||
|
|
3e79ec9a61 | ||
|
|
42cd7d3b77 | ||
|
|
6f4c65c178 | ||
|
|
863424ffd7 | ||
|
|
0a8c5e384d | ||
|
|
c35cfbd170 | ||
|
|
b20ce46a7b | ||
|
|
22425726d1 | ||
|
|
94e8c1da68 | ||
|
|
55c305c569 | ||
|
|
ad028947e0 | ||
|
|
58636a9d51 | ||
|
|
328e3d0b9a | ||
|
|
f1a2ecaa3a | ||
|
|
d56f05fb19 | ||
|
|
c32ced20f1 | ||
|
|
212e0753c8 | ||
|
|
387590986a | ||
|
|
4a5c0b1409 | ||
|
|
711dc83f5c | ||
|
|
6314eeda0a | ||
|
|
275731e381 | ||
|
|
cf783ea480 | ||
|
|
899fbef948 | ||
|
|
4a6d7952a7 | ||
|
|
71c3ac0e4e | ||
|
|
d876c495be | ||
|
|
5443226e27 | ||
|
|
89ffe99dcc | ||
|
|
1ba885e5dc | ||
|
|
ede306b88e | ||
|
|
0acc49c57d | ||
|
|
2bbeb040c2 | ||
|
|
245972234e | ||
|
|
5689c36b12 | ||
|
|
f743709908 | ||
|
|
f4a367d2de | ||
|
|
afe042ecfc | ||
|
|
79603293a0 | ||
|
|
29e0cf96d4 | ||
|
|
c108cd4780 | ||
|
|
d84146fd88 | ||
|
|
30e6316e16 | ||
|
|
6835e585b0 | ||
|
|
49d4f43652 | ||
|
|
70dd9c7724 | ||
|
|
f386e47efa | ||
|
|
4eea6a11c8 | ||
|
|
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 | ||
|
|
c65c1af023 | ||
|
|
f10bd9c0d8 | ||
|
|
a6ac78294b | ||
|
|
04c9ead19d | ||
|
|
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 | ||
|
|
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 | ||
|
|
a5f6ecb992 | ||
|
|
a211e64f0e | ||
|
|
9d33aa034a | ||
|
|
a91c9db4e0 | ||
|
|
291f628415 | ||
|
|
d53ceaaaac | ||
|
|
aa981fd891 | ||
|
|
b50bda8488 | ||
|
|
0d36e11bf4 | ||
|
|
102cdb4589 | ||
|
|
5b78487b94 | ||
|
|
f917d2171e | ||
|
|
295754480e | ||
|
|
db03ce70d2 | ||
|
|
a44198539d | ||
|
|
7dbac97883 | ||
|
|
df6cf5a1c5 | ||
|
|
ffa774db6a | ||
|
|
6d6a47a5ac | ||
|
|
50e373a98a | ||
|
|
b56b2adb54 | ||
|
|
af09e315c5 | ||
|
|
1848e062f1 | ||
|
|
c8f2f94361 | ||
|
|
be52763be4 | ||
|
|
5dd5816c34 | ||
|
|
16f5ce40d9 | ||
|
|
223937adcc | ||
|
|
4544cba858 | ||
|
|
1284adf91d | ||
|
|
4b13d19ed9 | ||
|
|
12a606a443 | ||
|
|
052f86b19f | ||
|
|
27b37a5f27 | ||
|
|
c37d3ae0f9 | ||
|
|
e69bb3501d | ||
|
|
eda9c00548 | ||
|
|
6a7e0d3ecb | ||
|
|
48859facf4 | ||
|
|
732b8b599f | ||
|
|
00ad4c3895 | ||
|
|
4858bdd838 | ||
|
|
eee78a79d9 | ||
|
|
aa890affc9 | ||
|
|
10aed2d9d8 | ||
|
|
4d78b904b8 | ||
|
|
473cc7334b | ||
|
|
afdcac2465 | ||
|
|
e539ac82f3 | ||
|
|
c2fbb20787 | ||
|
|
084307bdb2 | ||
|
|
fd78cec5ac | ||
|
|
0b87d35877 | ||
|
|
9849a96f32 | ||
|
|
b5287f7f01 | ||
|
|
11888f3e38 | ||
|
|
d2b27b046b | ||
|
|
8f4f26c143 | ||
|
|
514d0a9e5a | ||
|
|
bc99f15094 | ||
|
|
ba0b68cbfb | ||
|
|
fb33005051 | ||
|
|
5da9091aee | ||
|
|
a91a5e7014 | ||
|
|
87a623a796 | ||
|
|
ed75089ee0 | ||
|
|
a04e031f9e | ||
|
|
3c91dd52a0 | ||
|
|
c282f630c4 | ||
|
|
49bf9ec8a9 | ||
|
|
2b74054f4b | ||
|
|
64793132c7 |
@@ -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
|
||||
|
||||
51
.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,12 +84,13 @@ EMAIL_VERIFICATION_DISABLED=1
|
||||
PASSWORD_RESET_DISABLED=1
|
||||
|
||||
# Signup. Disable the ability for new users to create an account.
|
||||
# Note: This variable is only available to the SaaS setup of Formbricks Cloud. Signup is disable by default for self-hosting.
|
||||
# SIGNUP_DISABLED=1
|
||||
|
||||
# Email login. Disable the ability for users to login with email.
|
||||
# EMAIL_AUTH_DISABLED=1
|
||||
|
||||
# Team Invite. Disable the ability for invited users to create an account.
|
||||
# Organization Invite. Disable the ability for invited users to create an account.
|
||||
# INVITE_DISABLED=1
|
||||
|
||||
##########
|
||||
@@ -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=
|
||||
|
||||
@@ -154,14 +153,11 @@ SLACK_CLIENT_SECRET=
|
||||
# Enterprise License Key
|
||||
ENTERPRISE_LICENSE_KEY=
|
||||
|
||||
# Automatically assign new users to a specific team and role within that team
|
||||
# Insert an existing team id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn)
|
||||
# Automatically assign new users to a specific organization and role within that organization
|
||||
# Insert an existing organization id or generate a valid CUID for a new one at https://www.getuniqueid.com/cuid (e.g. cjld2cjxh0000qzrmn831i7rn)
|
||||
# (Role Management is an Enterprise feature)
|
||||
# DEFAULT_TEAM_ID=
|
||||
# DEFAULT_TEAM_ROLE=admin
|
||||
|
||||
# set to 1 to skip onboarding for new users
|
||||
# ONBOARDING_DISABLED=1
|
||||
# DEFAULT_ORGANIZATION_ID=
|
||||
# DEFAULT_ORGANIZATION_ROLE=admin
|
||||
|
||||
# Send new users to customer.io
|
||||
# CUSTOMER_IO_API_KEY=
|
||||
@@ -177,7 +173,10 @@ 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
|
||||
|
||||
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: "[oss.gg hackathon]"
|
||||
labels: 🕹️ oss.gg, player submission
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: contribution-name
|
||||
attributes:
|
||||
label: What side quest or challenge are you solving?
|
||||
description: Add the name of the side quest or challenge.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: points
|
||||
attributes:
|
||||
label: Points
|
||||
description: How many points are assigned to this contribution?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What's the task your performed?
|
||||
validations:
|
||||
- type: textarea
|
||||
id: proof
|
||||
attributes:
|
||||
label: Provide proof that you've completed the task
|
||||
description: Screenshots, loom recordings, links to the content you shared or interacted with.
|
||||
validations:
|
||||
required: true
|
||||
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
|
||||
|
||||
26
.github/workflows/build-formbricks-com.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Build formbricks-com
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Formbricks-com
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||
|
||||
- name: Build Formbricks-com
|
||||
run: pnpm build --filter=formbricks-com...
|
||||
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:
|
||||
|
||||
85
.github/workflows/e2e.yml
vendored
@@ -2,67 +2,84 @@ 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
|
||||
echo "NEXT_PUBLIC_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_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }}
|
||||
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
|
||||
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
|
||||
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
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_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }}
|
||||
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
|
||||
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
|
||||
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
|
||||
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
|
||||
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
|
||||
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:
|
||||
@@ -33,9 +25,7 @@ jobs:
|
||||
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
|
||||
with:
|
||||
cosign-release: "v2.1.1"
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -52,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
|
||||
@@ -68,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}
|
||||
6
.github/workflows/release-docker-github.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Docker
|
||||
name: Docker Release to Github
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
@@ -41,9 +41,7 @@ jobs:
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
- name: Install cosign
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
|
||||
with:
|
||||
cosign-release: "v2.1.1"
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
|
||||
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,
|
||||
|
||||
14
README.md
@@ -82,7 +82,7 @@ Formbricks is both a free and open source survey platform - and a privacy-first
|
||||
|
||||
- 🔗 Create shareable **link surveys**.
|
||||
|
||||
- 👨👩👦 Invite your team members to **collaborate** on your surveys.
|
||||
- 👨👩👦 Invite your organization members to **collaborate** on your surveys.
|
||||
|
||||
- 🔌 Integrate Formbricks with **Slack, Notion, Zapier, n8n and more**.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -224,7 +230,7 @@ Additional to the AGPL licensed Formbricks core, this repository contains code l
|
||||
|
||||
### White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
If you have other licensing requirements such as White-Labeling please [send us an email](mailto:hola@formbricks.com).
|
||||
We currently do not offer Formbricks white-labeled. Any other needs? [Send us an email](mailto:hola@formbricks.com).
|
||||
|
||||
### Why charge for Enterprise Features?
|
||||
|
||||
|
||||
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"],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Sidebar from "./Sidebar";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
|
||||
export default function LayoutApp({ children }: { children: React.ReactNode }) {
|
||||
export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="min-h-full">
|
||||
{/* Static sidebar for desktop */}
|
||||
@@ -10,4 +10,4 @@ export default function LayoutApp({ children }: { children: React.ReactNode }) {
|
||||
<div className="flex flex-1 flex-col lg:pl-64">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ShieldCheckIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import { classNames } from "../lib/utils";
|
||||
|
||||
const navigation = [
|
||||
@@ -26,7 +25,7 @@ const secondaryNavigation = [
|
||||
{ name: "Privacy", href: "#", icon: ShieldCheckIcon },
|
||||
];
|
||||
|
||||
export default function Sidebar({}) {
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
|
||||
<nav
|
||||
@@ -63,4 +62,4 @@ export default function Sidebar({}) {
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select";
|
||||
|
||||
interface SurveySwitchProps {
|
||||
value: "website" | "app";
|
||||
formbricks: any;
|
||||
@@ -7,23 +5,18 @@ interface SurveySwitchProps {
|
||||
|
||||
export const SurveySwitch = ({ value, formbricks }: SurveySwitchProps) => {
|
||||
return (
|
||||
<Select
|
||||
<select
|
||||
value={value}
|
||||
onValueChange={(v) => {
|
||||
onChange={(v) => {
|
||||
formbricks.logout();
|
||||
window.location.href = `/${v}`;
|
||||
window.location.href = `/${v.target.value}`;
|
||||
}}>
|
||||
<SelectTrigger className="w-[180px] px-4">
|
||||
<SelectValue placeholder="Theme" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="website" className="h-10 px-4 hover:bg-slate-100">
|
||||
Website Surveys
|
||||
</SelectItem>
|
||||
<SelectItem value="app" className="hover:bg-slate-10 h-10 px-4">
|
||||
App Surveys
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<option value="website" className="h-10 px-4 hover:bg-slate-100">
|
||||
Website Surveys
|
||||
</option>
|
||||
<option value="app" className="hover:bg-slate-10 h-10 px-4">
|
||||
App Surveys
|
||||
</option>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function classNames(...classes: any) {
|
||||
export const classNames = (...classes: any) => {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@formbricks/ui"],
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
@@ -25,4 +24,4 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
export default nextConfig;
|
||||
@@ -13,13 +13,13 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "^0.378.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,9 +1,8 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
|
||||
import "../styles/globals.css";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -18,4 +17,6 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
const Document = () => {
|
||||
return (
|
||||
<Html lang="en" className="h-full bg-slate-50">
|
||||
<Head />
|
||||
@@ -10,4 +10,6 @@ export default function Document() {
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Document;
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
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";
|
||||
|
||||
declare const window: any;
|
||||
|
||||
export default function AppPage({}) {
|
||||
const AppPage = ({}) => {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -36,7 +34,11 @@ export default function AppPage({}) {
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const userId = "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING";
|
||||
const userInitAttributes = { language: "de", "Init Attribute 1": "eight", "Init Attribute 2": "two" };
|
||||
const userInitAttributes = {
|
||||
language: "de",
|
||||
"Init Attribute 1": "eight",
|
||||
"Init Attribute 2": "two",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
@@ -55,12 +57,12 @@ export default function AppPage({}) {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div className="flex 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">
|
||||
@@ -138,7 +140,7 @@ export default function 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>
|
||||
@@ -167,7 +169,7 @@ export default function 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>
|
||||
@@ -190,7 +192,7 @@ export default function 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>
|
||||
@@ -213,7 +215,7 @@ export default function 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>
|
||||
@@ -234,4 +236,6 @@ export default function AppPage({}) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AppPage;
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
import { SurveySwitch } from "../../components/SurveySwitch";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
declare const window: any;
|
||||
|
||||
export default function AppPage({}) {
|
||||
const AppPage = ({}) => {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -36,7 +34,7 @@ export default function AppPage({}) {
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const defaultAttributes = {
|
||||
language: "de",
|
||||
language: "en",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
@@ -60,7 +58,7 @@ export default function 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="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">
|
||||
@@ -130,6 +128,7 @@ export default function 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.
|
||||
@@ -139,4 +138,6 @@ export default function AppPage({}) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AppPage;
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -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: 56 KiB |
BIN
apps/docs/app/app-surveys/actions/images/I1.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 62 KiB |
BIN
apps/docs/app/app-surveys/actions/images/I2.webp
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -1,12 +1,12 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import I1 from "./images/I1.png";
|
||||
import I2 from "./images/I2.png";
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Using Actions in Formbricks | Fine-tuning User Moments",
|
||||
description:
|
||||
"Dive deep into how actions in Formbricks help products and teams 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.",
|
||||
"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
|
||||
@@ -22,7 +22,7 @@ Understanding user thoughts and feelings at critical moments in their journey is
|
||||
|
||||
## **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 either programmatically in your code or 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 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.
|
||||
|
||||
## **Why Are Actions Useful?**
|
||||
|
||||
@@ -33,13 +33,6 @@ Actions are invaluable for enhancing survey relevance and effectiveness:
|
||||
- **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.
|
||||
|
||||
<Note>
|
||||
You now have the option to create survey-specific action classes directly within the survey editor. These
|
||||
actions are exclusive to the individual survey and won't appear in the global action list, helping to keep
|
||||
it uncluttered. For actions that are needed across multiple surveys, we recommend creating them from the
|
||||
Actions tab.
|
||||
</Note>
|
||||
|
||||
## **Setting Up No-Code Actions**
|
||||
|
||||
Formbricks offers an intuitive No-Code interface that allows you to configure actions without needing to write any code.
|
||||
@@ -64,12 +57,35 @@ To add a No-Code Action:
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Here are three types of No-Code actions you can set up:
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
### **1. Page URL Action**
|
||||
### **1. Click Action**
|
||||
|
||||
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
|
||||
|
||||
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
### **2. Page view Action**
|
||||
|
||||
This action is triggered when a user visits a page within your application.
|
||||
|
||||
### **3. Exit Intent Action**
|
||||
|
||||
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
|
||||
|
||||
### **4. 50% Scroll Action**
|
||||
|
||||
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
|
||||
|
||||
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
|
||||
|
||||
<Note>
|
||||
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
|
||||
|
||||
### **URL Match Conditions**
|
||||
|
||||
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
|
||||
- **contains**: Activates when the URL contains the specified substring.
|
||||
- **startsWith**: Fires when the URL starts with the specified string.
|
||||
@@ -77,15 +93,7 @@ This action is triggered when a user visits a specific page within your applicat
|
||||
- **notMatch**: Triggers when the URL does not match the specified condition.
|
||||
- **notContains**: Activates when the URL does not contain the specified substring.
|
||||
|
||||
### **2. innerText Action**
|
||||
|
||||
Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
### **3. CSS Selector Action**
|
||||
|
||||
This action verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
<Note>You can have an action use combination of the 3 types as you wish</Note>
|
||||
</Note>
|
||||
|
||||
## **Setting Up Code Actions**
|
||||
|
||||
|
||||
@@ -8,26 +8,31 @@ 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 In-app Surveys | Formbricks",
|
||||
description:
|
||||
"Advanced Targeting allows you to show surveys to just the right group of people. You can target surveys based on user attributes, user events, 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, user events, and metadata. 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.
|
||||
<Note>
|
||||
Targeting based on actions is deprecated in Advanced Targeting and will be removed soon. We recommend using
|
||||
filters on user attributes to target the survey only to specific groups of users.
|
||||
</Note>
|
||||
|
||||
<ResponsiveVideo title="Formbricks Multi-language Surveys"
|
||||
src="https://www.youtube-nocookie.com/embed/0BQp6N4cXzU?si=KeBM7G7Ch1xtrsOm&controls=0" />
|
||||
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
|
||||
|
||||
<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.
|
||||
Advanced Targeting is available on the Pro plan!
|
||||
</Note>
|
||||
|
||||
1. On the Formbricks dashboard, click on **People** tab from the top navigation bar.
|
||||
@@ -66,25 +71,7 @@ Advanced Targeting allows you to show surveys to the right group of people. You
|
||||
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
|
||||
3. Sneak Peak: How we at Formbricks automate inviting power users to chat with us
|
||||
|
||||
<MdxImage
|
||||
src={PowerUsers}
|
||||
|
||||
@@ -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 function Libraries() {
|
||||
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}
|
||||
@@ -57,4 +63,4 @@ export function Libraries() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
@@ -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: 207 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I1.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 75 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I2.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 208 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I3.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 106 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I4.webp
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 130 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I5.webp
Normal file
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 52 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I6.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 78 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I7.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 75 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I8.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
@@ -1,14 +1,13 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import I1 from "./images/I1.png";
|
||||
import I2 from "./images/I2.png";
|
||||
import I3 from "./images/I3.png";
|
||||
import I4 from "./images/I4.png";
|
||||
import I5 from "./images/I5.png";
|
||||
import I6 from "./images/I6.png";
|
||||
import I7 from "./images/I7.png";
|
||||
import I8 from "./images/I8.png";
|
||||
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
import I3 from "./images/I3.webp";
|
||||
import I4 from "./images/I4.webp";
|
||||
import I5 from "./images/I5.webp";
|
||||
import I6 from "./images/I6.webp";
|
||||
import I7 from "./images/I7.webp";
|
||||
import I8 from "./images/I8.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Quickstart Guide: App Surveys Made Easier & Faster",
|
||||
@@ -17,25 +16,28 @@ export const metadata = {
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# 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 surveys on your public facing website, head over to the [Website Surveys Quickstart Guide](/website-surveys/quickstart).
|
||||
App Surveys are ideal for websites that **have a user authentication** system. If you are looking to run
|
||||
surveys on your public facing website, head over to the [Website Surveys Quickstart
|
||||
Guide](/website-surveys/quickstart).
|
||||
</Note>
|
||||
|
||||
1. **Create a free Formbricks Cloud account**: While you can [self-host](/self-hosting/deployment) Formbricks, but the quickest and easiest way to get started is with the free Cloud plan. Just [sign up here](https://app.formbricks.com/auth/signup) and you'll be guided to our onboarding like below:
|
||||
|
||||
<Note>
|
||||
Website & App Surveys have the same integration process. The difference will come when we setup our survey.
|
||||
Website & App Surveys have the same integration process. The difference will come when we setup our survey.
|
||||
</Note>
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="Choose website survey from survey type"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
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:
|
||||
@@ -70,41 +72,41 @@ Onboarding is complete! Now let’s create our first survey as you should see te
|
||||
Pick the Survey Type as **App Survey**.
|
||||
|
||||
<MdxImage
|
||||
src={I5}
|
||||
alt="Survey settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
src={I5}
|
||||
alt="Survey settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. **Set Trigger for the Survey**: Scroll down to Survey Trigger and inside **When**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
|
||||
4. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
|
||||
|
||||
<MdxImage
|
||||
src={I6}
|
||||
alt="Survey trigger settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
src={I6}
|
||||
alt="Survey trigger settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once to each user to prevent survey fatigue:
|
||||
|
||||
<Note>
|
||||
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
|
||||
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
|
||||
</Note>
|
||||
|
||||
<MdxImage
|
||||
src={I7}
|
||||
alt="Recontact options for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
src={I7}
|
||||
alt="Recontact options for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. **Publish your survey**: Now hit **Publish** and you’ll be forwarded to the Summary Page. This is where you’ll find the responses to this survey.
|
||||
|
||||
<MdxImage
|
||||
src={I8}
|
||||
alt="Survey published successfully"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
src={I8}
|
||||
alt="Survey published successfully"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
@@ -80,6 +80,11 @@ formbricks.init({
|
||||
|
||||
You can use the setAttribute function to set any custom attribute for the user (e.g. name, plan, etc.):
|
||||
|
||||
<Note>
|
||||
Please note that the number of different attribute classes (e.g., "Plan," "First Name," etc.) is currently
|
||||
limited to 150 attributes per environment.
|
||||
</Note>
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Setting Custom Attributes">
|
||||
|
||||
@@ -115,4 +120,4 @@ formbricks.logout();
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Col>
|
||||
|
||||
@@ -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
|
||||
@@ -152,8 +150,7 @@ These settings make sure the survey is always displayed, when a user wants to Ca
|
||||
|
||||
<Note>
|
||||
## Formbricks Widget running? You need to have the Formbricks Widget installed to display the Churn Survey
|
||||
in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart)
|
||||
to install the widget.
|
||||
in your app. Please follow [this tutorial (Step 4 onwards)](/app-surveys/quickstart) to install the widget.
|
||||
</Note>
|
||||
|
||||
###
|
||||
|
||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 59 KiB |
@@ -5,9 +5,9 @@ import ChangeId from "./change-id.webp";
|
||||
import CopyIds from "./copy-ids.webp";
|
||||
import DocsNavi from "./docs-navi.webp";
|
||||
import DocsTemplate from "./docs-template.webp";
|
||||
import SelectNonevent from "./select-nonevent.webp";
|
||||
import SelectAction from "./select-action.webp";
|
||||
import SurveyTrigger from "./survey-trigger.webp";
|
||||
import SwitchToDev from "./switch-to-dev.webp";
|
||||
import WhenToAsk from "./when-to-ask.webp";
|
||||
|
||||
export const metadata = {
|
||||
title:
|
||||
@@ -73,10 +73,10 @@ To get this running, you'll need a bit of time. Here are the steps we're going t
|
||||
choices accordingly. They have to be identical to the frontend we're building in the next step.
|
||||
</Note>
|
||||
|
||||
6. Click on “Continue to Settings or select the audience tab manually. Scroll down to “When to ask” and create a new Action:
|
||||
6. Click on “Continue to Settings or select the audience tab manually. Scroll down to “Survey Trigger” and create a new Action:
|
||||
|
||||
<MdxImage
|
||||
src={WhenToAsk}
|
||||
src={SurveyTrigger}
|
||||
alt="set up when to ask card"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
@@ -89,7 +89,7 @@ To get this running, you'll need a bit of time. Here are the steps we're going t
|
||||
8. Select the Non-Event in the dropdown. Now you see that the “Publish survey” button is active. Publish your survey 🤝
|
||||
|
||||
<MdxImage
|
||||
src={SelectNonevent}
|
||||
src={SelectAction}
|
||||
alt="select nonevent"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
@@ -135,7 +135,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover"
|
||||
|
||||
import { handleFeedbackSubmit, updateFeedback } from "../../lib/handleFeedbackSubmit";
|
||||
|
||||
export default function DocsFeedback() {
|
||||
export const DocsFeedback = () => {
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [sharedFeedback, setSharedFeedback] = useState(false);
|
||||
@@ -199,7 +199,7 @@ export default function DocsFeedback() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
BIN
apps/docs/app/best-practices/docs-feedback/select-action.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
BIN
apps/docs/app/best-practices/docs-feedback/survey-trigger.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |