Compare commits
61 Commits
v2.7.0
...
feat/icon-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65a5d5b8e7 | ||
|
|
4214e94778 | ||
|
|
7139ec5276 | ||
|
|
b83b54eee1 | ||
|
|
eb2621f72a | ||
|
|
44980d21a9 | ||
|
|
e1d2e1357b | ||
|
|
f80c7d03e2 | ||
|
|
4ca6ee358b | ||
|
|
9dad06222d | ||
|
|
37ef6be4c3 | ||
|
|
f0a4fad878 | ||
|
|
06ae035e11 | ||
|
|
91b5177bdb | ||
|
|
30bd427985 | ||
|
|
a92762ff47 | ||
|
|
0a5c98aba0 | ||
|
|
6f041bf693 | ||
|
|
23c9dc304a | ||
|
|
97377fe8bd | ||
|
|
36cf16ce90 | ||
|
|
ab3ef63097 | ||
|
|
7f8549124f | ||
|
|
44bbb59c8f | ||
|
|
43b1cb904d | ||
|
|
762a3ca626 | ||
|
|
91f0d00ba2 | ||
|
|
41f42f4427 | ||
|
|
98181bfe6c | ||
|
|
a8ab4aaf2e | ||
|
|
78dca7a2bf | ||
|
|
844ea40c3a | ||
|
|
7a6dedf452 | ||
|
|
b641b37308 | ||
|
|
8c1f8bfb42 | ||
|
|
1f1563401d | ||
|
|
9fd585ee07 | ||
|
|
609dcabf77 | ||
|
|
80d338c998 | ||
|
|
306784c31b | ||
|
|
bcd68e0f19 | ||
|
|
5764148753 | ||
|
|
e7edfe3ba1 | ||
|
|
da6f54eede | ||
|
|
ade5c3d80e | ||
|
|
cc2600cfba | ||
|
|
9b191ef3e4 | ||
|
|
c450c35baf | ||
|
|
b35b82f4ee | ||
|
|
ea52624ab2 | ||
|
|
0484bccfd1 | ||
|
|
d094f63faa | ||
|
|
dc6bc61442 | ||
|
|
5918c42cf9 | ||
|
|
c34a08561e | ||
|
|
7213c726b4 | ||
|
|
f650ac4e76 | ||
|
|
2ff1be2c4a | ||
|
|
61ac306ef3 | ||
|
|
022569e404 | ||
|
|
ebf22df7b6 |
@@ -1,16 +0,0 @@
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT=20
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
# RUN su node -c "npm install -g <your-package-list-here>"
|
||||
|
||||
RUN su node -c "npm install -g pnpm"
|
||||
@@ -1,28 +1,6 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node-postgres
|
||||
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
|
||||
{
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": ["dbaeumer.vscode-eslint"]
|
||||
}
|
||||
},
|
||||
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or with the host.
|
||||
"forwardPorts": [3000, 5432, 8025],
|
||||
|
||||
"name": "Node.js & PostgreSQL",
|
||||
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace"
|
||||
"features": {},
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"postAttachCommand": "pnpm go",
|
||||
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev"
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# Update 'VARIANT' to pick an LTS version of Node.js: 20, 18, 16, 14.
|
||||
# Append -bullseye or -buster to pin to an OS version.
|
||||
# Use -bullseye variants on local arm64/Apple Silicon.
|
||||
VARIANT: "20"
|
||||
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
|
||||
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
|
||||
network_mode: service:db
|
||||
# Uncomment the next line to use a non-root user for all processes.
|
||||
# user: node
|
||||
|
||||
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg17
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: formbricks
|
||||
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
network_mode: service:app
|
||||
logging:
|
||||
driver:
|
||||
"none" # disable saving logs
|
||||
# ports:
|
||||
# - 8025:8025 # web ui
|
||||
# 1025:1025 # smtp server
|
||||
|
||||
volumes:
|
||||
postgres-data: null
|
||||
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Bug report
|
||||
description: "Found a bug? Please fill out the sections below. \U0001F44D"
|
||||
labels:
|
||||
- bug
|
||||
type: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue-summary
|
||||
@@ -10,6 +9,13 @@ body:
|
||||
description: A summary of the issue. This needs to be a clear detailed-rich summary.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: issue-expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other-information
|
||||
attributes:
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Feature request
|
||||
description: "Suggest an idea for this project \U0001F680"
|
||||
labels:
|
||||
- enhancement
|
||||
type: feature
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Task (internal)
|
||||
description: "Template for creating a task. Used by the Formbricks Team only \U0001f4e5"
|
||||
type: task
|
||||
body:
|
||||
- type: textarea
|
||||
id: task-summary
|
||||
attributes:
|
||||
label: Task description
|
||||
description: A clear detailed-rich description of the task.
|
||||
validations:
|
||||
required: true
|
||||
6
.gitpod.Dockerfile
vendored
@@ -1,6 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
# Install custom tools, runtime, etc.
|
||||
RUN brew install yq
|
||||
|
||||
RUN pnpm install turbo --global
|
||||
74
.gitpod.yml
@@ -1,74 +0,0 @@
|
||||
tasks:
|
||||
- name: demo
|
||||
init: |
|
||||
gp sync-await init-install &&
|
||||
bash .gitpod/setup-demo.bash
|
||||
command: |
|
||||
cd apps/demo &&
|
||||
cp .env.example .env &&
|
||||
sed -i -r "s#^(NEXT_PUBLIC_FORMBRICKS_API_HOST=).*#\1 $(gp url 3000)#" .env &&
|
||||
gp sync-await init &&
|
||||
turbo --filter "@formbricks/demo" go
|
||||
|
||||
- name: Init Formbricks
|
||||
init: |
|
||||
cp .env.example .env &&
|
||||
bash .gitpod/init.bash &&
|
||||
turbo --filter "@formbricks/js" build &&
|
||||
gp sync-done init-install
|
||||
command: |
|
||||
gp sync-done init &&
|
||||
gp tasks list &&
|
||||
gp ports await 3002 && gp ports await 3000 && gp open apps/demo/.env && gp preview $(gp url 3002) --external
|
||||
|
||||
- name: web
|
||||
init: |
|
||||
gp sync-await init-install &&
|
||||
bash .gitpod/setup-web.bash &&
|
||||
turbo --filter "@formbricks/database" db:down
|
||||
command: |
|
||||
gp sync-await init &&
|
||||
cp .env.example .env &&
|
||||
sed -i -r "s#^(WEBAPP_URL=).*#\1 $(gp url 3000)#" .env &&
|
||||
RANDOM_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
sed -i 's/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY='"$RANDOM_ENCRYPTION_KEY"'/' .env
|
||||
turbo --filter "@formbricks/web" go
|
||||
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
visibility: public
|
||||
onOpen: open-browser
|
||||
- port: 3001
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 3002
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 5432
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 1025
|
||||
visibility: public
|
||||
onOpen: ignore
|
||||
- port: 8025
|
||||
visibility: public
|
||||
onOpen: open-browser
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
master: true
|
||||
pullRequests: true
|
||||
addComment: true
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- "ban.spellright"
|
||||
- "bradlc.vscode-tailwindcss"
|
||||
- "DavidAnson.vscode-markdownlint"
|
||||
- "dbaeumer.vscode-eslint"
|
||||
- "esbenp.prettier-vscode"
|
||||
- "Prisma.prisma"
|
||||
- "yzhang.markdown-all-in-one"
|
||||
@@ -18,7 +18,7 @@ Ready to dive into the code and make a real impact? Here's your path:
|
||||
|
||||
1. **Read our Best Practices**: [It takes 5 minutes](https://formbricks.com/docs/developer-docs/contributing/get-started) but will help you save hours 🤓
|
||||
|
||||
1. **Fork the Repository:** Fork our repository or use [Gitpod](https://formbricks.com/docs/developer-docs/contributing/gitpod) or use [Codespaces](https://formbricks.com/docs/developer-docs/contributing/codespaces)
|
||||
1. **Fork the Repository:** Fork our repository or use [Gitpod](https://gitpod.io) or use [Github Codespaces](https://github.com/features/codespaces) to get started instantly.
|
||||
|
||||
1. **Tweak and Transform:** Work your coding magic and apply your changes.
|
||||
|
||||
|
||||
2
LICENSE
@@ -2,7 +2,7 @@ Copyright (c) 2024 Formbricks GmbH
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/", "apps/web/modules/ee" & "apps/web/app/(ee)" directories of this repository, if these directories exist, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All content that resides under the "apps/web/modules/ee" directory of this repository, if these directories exist, is licensed under the license defined in "apps/web/modules/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/react-native/" and "packages/api/" directories of this repository, if that directories exist, is licensed under the "MIT" license as defined in the "LICENSE" files of these packages.
|
||||
- All third party components incorporated into the Formbricks Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||
|
||||
@@ -228,7 +228,7 @@ The Formbricks core application is licensed under the [AGPLv3 Open Source Licens
|
||||
|
||||
### The Enterprise Edition
|
||||
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/packages/ee) and [license](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE) for the enterprise functionality can be found in the `/packages/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/apps/web/modules/ee) and [license](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE) for the enterprise functionality can be found in the `/apps/web/modules/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
|
||||
### White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"@formbricks/react-native": "workspace:*",
|
||||
"expo": "51.0.26",
|
||||
"expo-status-bar": "1.12.1",
|
||||
"react": "18.3.1",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-native": "0.74.4",
|
||||
"react-native-webview": "13.8.6"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import type { JSX } from "react";
|
||||
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
||||
import Formbricks, { track } from "@formbricks/react-native";
|
||||
|
||||
|
||||
3
apps/demo/globals.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -12,11 +12,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "0.452.0",
|
||||
"next": "14.2.16",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
"next": "15.0.3",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import "@formbricks/ui/globals.css";
|
||||
import "../globals.css";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
return (
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -127,10 +127,10 @@ Locate that file. We are using the [Tailwind Template “Syntax”](https://tail
|
||||
<CodeGroup title="Entire Widget">
|
||||
|
||||
```tsx
|
||||
import { Button } from "@/modules/ui/components/Button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/modules/ui/popover";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/components/Popover";
|
||||
import { handleFeedbackSubmit, updateFeedback } from "../../lib/handleFeedbackSubmit";
|
||||
|
||||
export const DocsFeedback = () => {
|
||||
|
||||
@@ -10,6 +10,11 @@ export const metadata = {
|
||||
|
||||
# SDK: Formbricks API
|
||||
|
||||
<Note>
|
||||
The API SDK is currently in beta and APIs are subject to change. We will do our best to notify you of any
|
||||
changes.
|
||||
</Note>
|
||||
|
||||
### Overview
|
||||
|
||||
The Formbricks Client API Wrapper is a lightweight package designed to simplify the integration of Formbricks API endpoints into your JavaScript (JS) or TypeScript (TS) projects. With this wrapper, you can easily interact with Formbricks API endpoints without the need for complex setup or manual HTTP requests.
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@formbricks/ui/components/Accordion";
|
||||
import { FaqJsonLdComponent } from "./FAQPageJsonLd";
|
||||
|
||||
const FAQ_DATA = [
|
||||
{
|
||||
question: "What is an environment ID?",
|
||||
answer: () => (
|
||||
<>
|
||||
The environment ID is a unique identifier associated with each Environment in Formbricks,
|
||||
distinguishing between different setups like production, development, etc.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: "How can I implement authentication for the Formbricks API?",
|
||||
answer: () => (
|
||||
<>
|
||||
Formbricks provides 2 types of API keys for each environment ie development and production. You can
|
||||
generate, view, and manage these keys in the Settings section on the Admin dashboard. Include the API
|
||||
key in your requests to authenticate and gain access to Formbricks functionalities.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: "Can I run the deployment shell script on any server?",
|
||||
answer: () => (
|
||||
<>
|
||||
You can run it on any machine you own as long as its running a <b> Linux Ubuntu </b> distribution. And
|
||||
to forward the requests, make sure you have an <b>A record</b> setup for your domain pointing to the
|
||||
server.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: "Can I self-host Formbricks?",
|
||||
answer: () => (
|
||||
<>
|
||||
Absolutely! We provide an option for users to host Formbricks on their own server, ensuring even more
|
||||
control over data and compliance. And the best part? Self-hosting is available for free, always. For
|
||||
documentation on self hosting, click{" "}
|
||||
<a href="/self-hosting/deployment" className="text-brand-dark dark:text-brand-light">
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: "How can I change Button texts in my survey?",
|
||||
answer: () => (
|
||||
<>
|
||||
For the question that you want to change the button text, click on the <b>Show Advanced Settings</b>{" "}
|
||||
toggle and change the button label in the <b>Button Text</b> field.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const faqJsonLdData = FAQ_DATA.map((faq) => ({
|
||||
questionName: faq.question,
|
||||
acceptedAnswerText: faq.answer(),
|
||||
}));
|
||||
|
||||
export const FAQ = () => {
|
||||
return (
|
||||
<>
|
||||
<FaqJsonLdComponent data={faqJsonLdData} />
|
||||
<Accordion type="single" collapsible>
|
||||
{FAQ_DATA.map((faq, index) => (
|
||||
<AccordionItem key={`item-${index}`} value={`item-${index + 1}`} className="not-prose mb-0 mt-0">
|
||||
<AccordionTrigger>{faq.question}</AccordionTrigger>
|
||||
<AccordionContent>{faq.answer()}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { FAQPageJsonLd } from "next-seo";
|
||||
|
||||
export const FaqJsonLdComponent = ({ data }) => {
|
||||
const faqEntities = data.map(({ question, answer }) => ({
|
||||
questionName: question,
|
||||
acceptedAnswerText: answer,
|
||||
}));
|
||||
|
||||
return <FAQPageJsonLd mainEntity={faqEntities} />;
|
||||
};
|
||||
@@ -14,11 +14,7 @@ We are so happy that you are interested in contributing to Formbricks 🤗 There
|
||||
|
||||
- **Issues**: Spotted a bug? Has deployment gone wrong? Do you have user feedback? [Raise an issue](https://github.com/formbricks/formbricks/issues/new/choose) for the fastest response.
|
||||
- **Feature requests**: Raise an issue for these and tag it as an Enhancement. We love every idea. Please [open a feature request](https://github.com/formbricks/formbricks/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFEATURE%5D) clearly describing the problem you want to solve.
|
||||
- **How we Code at Formbricks**: [View this Notion document](https://formbricks.notion.site/How-we-code-at-Formbricks-8bcaa0304a20445db4871831149c0cf5?pvs=4) and understand the coding practises we follow so that you can adhere to them for uniformity.
|
||||
- **Creating a PR**: Please fork the repository, make your changes and create a new pull request if you want to make an update.
|
||||
- **E2E Tests**: [Understand how we write E2E tests](https://formbricks.notion.site/Formbricks-End-to-End-Tests-06dc830d71604deaa8da24714540f7ab?pvs=4) and make sure to consider writing a test whenever you ship a feature.
|
||||
- **New Question Types**:[Follow this guide](https://formbricks.notion.site/Guidelines-for-Implementing-a-New-Question-Type-9ac0d1c362714addb24b9abeb326d1c1?pvs=4) to keep everything in mind when you want to add a new question type.
|
||||
- **How to create a service**: [Read this document to understand how we use services](https://formbricks.notion.site/How-to-create-a-service-8e0c035704bb40cb9ea5e5beeeeabd67?pvs=4). This is particulalry important when you need to write a new one.
|
||||
- **Creating a PR**: Please fork the repository, make your changes and create a new pull request if you want to make an update. Please talk to us first before starting development of more complex features. Small fixes are always welcome!
|
||||
|
||||
## Talk to us first
|
||||
|
||||
@@ -34,8 +30,8 @@ Once you open a PR, you will get a message from the CLA bot to fill out the form
|
||||
|
||||
We currently officially support the below methods to set up your development environment for Formbricks:
|
||||
|
||||
- [Gitpod](/developer-docs/contributing/gitpod)
|
||||
- [GitHub Codespaces](/developer-docs/contributing/codespaces)
|
||||
- [Gitpod](https://gitpod.io)
|
||||
- [GitHub Codespaces](https://github.com/features/codespaces)
|
||||
- [Local Machine Setup](#local-machine-setup)
|
||||
|
||||
Both Gitpod and GitHub Codespaces have a **generous free tier** to explore and develop. For junior developers we suggest using either of these, because you can dive into coding within minutes, not hours.
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 130 KiB |
@@ -1,172 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import GitpodAuth from "./images/auth.webp";
|
||||
import GitpodNewWorkspace from "./images/new-workspace.webp";
|
||||
import GitpodPorts from "./images/ports.webp";
|
||||
import GitpodPreparing from "./images/preparing.webp";
|
||||
import GitpodRunning from "./images/running.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Open Source Contribution Guide: How to Enhance yourself and Contribute to Formbricks",
|
||||
description:
|
||||
"Join the Formbricks community and learn how to effectively contribute. From raising issues and feature requests to creating PRs, discover the best practices and communicate with our responsive team on Discord",
|
||||
};
|
||||
|
||||
#### Contributing
|
||||
|
||||
# Gitpod Guide
|
||||
|
||||
**Building custom image for the workspace:**
|
||||
- This includes : Installing `yq` and `turbo` globally before the workspace starts. This is accomplished within the `.gitpod.Dockerfile` along with starting upon a base custom image building on [workspace-full](https://hub.docker.com/r/workspace-full/dockerfile).
|
||||
|
||||
**Initialization of Formbricks:**
|
||||
- During the prebuilds phase, we initialize Formbricks by performing the following tasks:
|
||||
1. Setting up environment variables.
|
||||
2. Installing monorepo dependencies.
|
||||
3. Installing Docker images by extracting them from the `packages/database/docker-compose.yml` file.
|
||||
4. Building the @formbricks/js component.
|
||||
- When the workspace starts:
|
||||
1. Wait for the web and demo apps to launch on Gitpod. This automatically opens the `apps/demo/.env` file. Utilize dynamic localhost URLs (e.g., `localhost:3000` for signup and `localhost:8025` for email confirmation) to configure `NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID`. After creating your account and finding the `ID` in the URL at `localhost:3000`, replace `YOUR_ENVIRONMENT_ID` in the `.env` file located in `app/demo`.
|
||||
|
||||
**Web Component Initialization:**
|
||||
- We initialize the @formbricks/web component during prebuilds. This involves:
|
||||
1. Installing build dependencies for the `@formbricks/web#go` task from turbo.json in prebuilds to save time.
|
||||
2. Starting PostgreSQL and Mailhog containers for running migrations in prebuilds.
|
||||
3. To prevent the "Init" task from running indefinitely due to prebuild rules, a cleanup `docker compose down` step i.e. `db:down` is added to `turbo.json`. This step is designed to halt the execution of containers that are currently running.
|
||||
- When the workspace starts:
|
||||
1. Initializing environment variables.
|
||||
2. Replacing `NEXT_PUBLIC_WEBAPP_URL` to take in Gitpod URL's ports when running on VSCode browser.
|
||||
3. Starting the `@formbricks/web` dev environment.
|
||||
|
||||
**Demo Component Initialization:**
|
||||
- Similar to the web component, the demo component is also initialized during prebuilds. This includes:
|
||||
1. Installing build dependencies for the `formbricks/demo#go` task from turbo.json in prebuilds to save time.
|
||||
2. Caching hits and replaying builds from the `@formbricks/js` component.
|
||||
- When the workspace starts:
|
||||
1. Initializing environment variables.
|
||||
2. Replaces `NEXT_PUBLIC_FORMBRICKS_API_HOST` to take in Gitpod URL's ports when running on VSCode browser.
|
||||
3. Starting the `@formbricks/demo` dev environment.
|
||||
|
||||
**Github Prebuilds Configuration:**
|
||||
- This configures Github Prebuilds for the master branch, pull requests, and adding comments. This helps automate the prebuild process for the specified branches and actions.
|
||||
|
||||
**VSCode Extensions:**
|
||||
- This includes a list of VSCode extensions that are added to the configuration when using Gitpod. These extensions can enhance the development experience within Gitpod.
|
||||
|
||||
### 1. Browser Redirection
|
||||
|
||||
After clicking the one-click setup button, Gitpod will open a new tab or window. Please ensure that your browser allows redirection to successfully access the services:
|
||||
|
||||
### 2. Authorizing in Gitpod
|
||||
|
||||
<MdxImage
|
||||
src={GitpodAuth}
|
||||
alt="Gitpod Auth Page"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
- This is the Gitpod Authentication Page. It appears when you click the "Open in GitPod" button and Gitpod
|
||||
needs to authenticate your access to the workspace. Click on 'Continue With Github' to authorize your GitPod
|
||||
session.
|
||||
|
||||
### 3. Creating a New Workspace
|
||||
|
||||
<MdxImage
|
||||
src={GitpodNewWorkspace}
|
||||
alt="Gitpod New workspace Page"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
- After authentication, Gitpod asks to create a new workspace for you. This page displays the configurations
|
||||
of your workspace. - You can use either choose either VS Code Browser or VS Code Desktop editor with the
|
||||
'Standard Class' for your workspace class. - If you opt for the VS Code Desktop, follow the following steps 1.
|
||||
Gitpod will prompt you to grant access to the VSCode app. Once approved, install the GitPod extension from the
|
||||
VSCode Marketplace and follow the prompts to authorize the integration. 2. Change the `WEBAPP_URL` to
|
||||
`https://localhost:3000`
|
||||
|
||||
### 4. Gitpod preparing the created Workspace
|
||||
|
||||
<MdxImage
|
||||
src={GitpodPreparing}
|
||||
alt="Gitpod Preparing workspace Page"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
- Gitpod is preparing your workspace with all the necessary dependencies and configurations. You will see this
|
||||
page while Gitpod sets up your development environment.
|
||||
|
||||
### 5. Gitpod running the Workspace
|
||||
|
||||
<MdxImage
|
||||
src={GitpodRunning}
|
||||
alt="Gitpod Running Workspace Page"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
- Once the workspace is fully prepared, voila, it enters the running state. You can start working on your
|
||||
project in this environment.
|
||||
|
||||
### Ports and Services
|
||||
|
||||
Here are the ports and corresponding URLs for the services within your Gitpod environment:
|
||||
|
||||
- **Port 3000**:
|
||||
|
||||
- **Service**: Demo App
|
||||
- **Description**: This port hosts the demo application of your project. You can access and interact with your application's demo by navigating to this port.
|
||||
|
||||
- **Port 3001**:
|
||||
|
||||
- **Service**: Formbricks website
|
||||
- **Description**: This port hosts the [Formbricks](https://formbricks.com) website, which contains documents, pricing, blogs, best practices, and concierge service.
|
||||
|
||||
- **Port 3002**:
|
||||
|
||||
- **Service**: Formbricks In-product Survey Demo App
|
||||
- **Description**: This app helps you test your app & website surveys. You can create and test user actions, create and update user attributes, etc.
|
||||
|
||||
- **Port 5432**:
|
||||
|
||||
- **Service**: PostgreSQL Database Server
|
||||
- **Description**: The PostgreSQL DB is hosted on this port.
|
||||
|
||||
- **Port 1025**:
|
||||
|
||||
- **Service**: SMTP server
|
||||
- **Description**: SMTP Server for sending and receiving email messages. This server is responsible for handling email communication.
|
||||
|
||||
- **Port 8025**:
|
||||
- **Service**: Mailhog
|
||||
|
||||
### Accessing port URLs
|
||||
|
||||
1. **Direct URL Composition**:
|
||||
|
||||
- You can access the dedicated port URL by pre-pending the port number to the workspace URL.
|
||||
- For example, if you want to access port 3000, you can use the URL format: `3000-yourworkspace.ws-eu45.gitpod.io`.
|
||||
|
||||
2. **Using [gp CLI](https://www.gitpod.io/docs/references-cli)**:
|
||||
|
||||
- Gitpod provides a convenient command, `gp url`, to quickly retrieve the URL for a specific port.
|
||||
- Simply use the command followed by the desired port number. For example, to get the URL for port 3000, run: `gp url 3000`.
|
||||
|
||||
3. **Listing All Open Port URLs**:
|
||||
|
||||
- If you prefer to see a list of all open port URLs at once, you can use the `gp ports list` command.
|
||||
- Running this command will display a list of ports along with their corresponding URLs.
|
||||
|
||||
4. **Viewing All Ports in Panel**:
|
||||
|
||||
- Gitpod also offers a user-friendly 'Ports' tab in the Gitpod panel.
|
||||
- Click on the 'Ports' tab to view a list of all open ports and their respective URLs.
|
||||
|
||||
{" "}
|
||||
|
||||
<MdxImage
|
||||
src={GitpodPorts}
|
||||
alt="Gitpod Ports tab"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
These URLs and port numbers represent various services and endpoints within your Gitpod environment. You can access and interact with these services by the Port URL for the respective service.
|
||||
@@ -26,15 +26,20 @@ export const metadata = {
|
||||
|
||||
# n8n Setup
|
||||
|
||||
n8n allows you to build flexible workflows focused on deep data integration. And with sharable templates and a user-friendly UI, the less technical people on your team can collaborate on them too. Unlike other tools, complexity is not a limitation. So you can build whatever you want — without stressing over budget. Hook up Formbricks with n8n and you can send your data to 350+ other apps. Here is how to do it.
|
||||
|
||||
<Note>
|
||||
Nail down your survey? Any changes in the survey cause additional work in the n8n node. It makes
|
||||
sense to first settle on the survey you want to run and then get to setting up n8n.
|
||||
The Formbricks n8n node is currently only available in the n8n self-hosted version as a community node. To
|
||||
install it go to "Settings" -> "Community Nodes" and install @formbricks/n8n-nodes-formbricks.
|
||||
</Note>
|
||||
|
||||
n8n allows you to build flexible workflows focused on deep data integration. And with sharable templates and a user-friendly UI, the less technical people on your team can collaborate on them too. Unlike other tools, complexity is not a limitation. So you can build whatever you want — without stressing over budget. Hook up Formbricks with n8n and you can send your data to 350+ other apps. Here is how to do it.
|
||||
|
||||
## Step 1: Setup your survey incl. `questionId` for every question
|
||||
|
||||
<Note>
|
||||
Nailed down your survey? Any changes in the survey cause additional work in the n8n node. It makes sense to
|
||||
first settle on the survey you want to run and then get to setting up n8n.
|
||||
</Note>
|
||||
|
||||
When setting up the node your life will be easier when you change the `questionId`s of your survey questions. You can only do so **before** you publish your survey.
|
||||
|
||||
<MdxImage
|
||||
|
||||
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 40 KiB |
@@ -8,7 +8,10 @@ import LinkWithQuestions from "./images/link-with-questions.webp";
|
||||
import ListLinkedSurveys from "./images/list-linked-surveys.webp";
|
||||
import SlackAuth from "./images/slack-auth.webp";
|
||||
import SlackConnected from "./images/slack-connected.webp";
|
||||
|
||||
import AddSlackBot1 from "./images/add-slack-bot-1.webp";
|
||||
import AddSlackBot2 from "./images/add-slack-bot-2.webp";
|
||||
import AddSlackBot3 from "./images/add-slack-bot-3.webp";
|
||||
import AddSlackBot4 from "./images/add-slack-bot-4.webp";
|
||||
export const metadata = {
|
||||
title: "Slack",
|
||||
description:
|
||||
@@ -69,7 +72,34 @@ The slack integration allows you to automatically send responses to a Slack chan
|
||||
channel in the Slack workspace you integrated.
|
||||
</Note>
|
||||
|
||||
5. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
|
||||
5. In order to make your channel available in channel dropdown, you need to add formbricks integration bot to the channel you want to link. You can do this by going to channel settings -> Integrations -> Add apps -> Search for "Formbricks" -> Select the bot -> Add.
|
||||
|
||||
<MdxImage
|
||||
src={AddSlackBot1}
|
||||
alt="Click on three dot at top right of the channel"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
<MdxImage
|
||||
src={AddSlackBot2}
|
||||
alt="Select Edit Settings"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
<MdxImage
|
||||
src={AddSlackBot3}
|
||||
alt="Navigate to Integrations"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
<MdxImage
|
||||
src={AddSlackBot4}
|
||||
alt="Add Formbricks Bot"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
|
||||
|
||||
<MdxImage
|
||||
src={LinkSurveyWithChannel}
|
||||
@@ -78,7 +108,7 @@ The slack integration allows you to automatically send responses to a Slack chan
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
|
||||
7. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
|
||||
|
||||
<MdxImage
|
||||
src={LinkWithQuestions}
|
||||
@@ -87,7 +117,7 @@ The slack integration allows you to automatically send responses to a Slack chan
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
7. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
|
||||
8. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
|
||||
|
||||
<MdxImage
|
||||
src={ListLinkedSurveys}
|
||||
@@ -124,6 +154,7 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
|
||||
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
|
||||
|
||||
- `channels:read`
|
||||
- `groups:read`
|
||||
- `chat:write`
|
||||
- `chat:write.public`
|
||||
- `chat:write.customize`
|
||||
|
||||
@@ -10,6 +10,11 @@ export const metadata = {
|
||||
|
||||
# React Native: In App Surveys
|
||||
|
||||
<Note>
|
||||
The React Native SDK is currently in beta and APIs are subject to change. We will do our best to notify you
|
||||
of any changes.
|
||||
</Note>
|
||||
|
||||
### Overview
|
||||
|
||||
The Formbricks React Native SDK can be used for seamlessly integrating App Surveys into your React Native Apps. Here, w'll explore how to leverage the SDK for in app surveys. The SDK is [available on npm.](https://www.npmjs.com/package/@formbricks/react-native)
|
||||
|
||||
@@ -13,6 +13,11 @@ export const metadata = {
|
||||
|
||||
# API Overview
|
||||
|
||||
<Note>
|
||||
The Formbricks API is currently in beta and is subject to change. We will do our best to notify you of any
|
||||
changes.
|
||||
</Note>
|
||||
|
||||
Formbricks offers two types of APIs: the **Public Client API** and the **Management API**. Each API serves a different purpose, has different authentication requirements, and provides access to different data and settings.
|
||||
|
||||
View our [API Documentation](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh) in more than 30 frameworks and languages.
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
@@ -1,131 +1,75 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import StepEight from "./images/StepEight.webp";
|
||||
import StepFive from "./images/StepFive.webp";
|
||||
import StepFour from "./images/StepFour.webp";
|
||||
import SurveyEmbed from "@/components/SurveyEmbed";
|
||||
import StepOne from "./images/StepOne.webp";
|
||||
import StepSeven from "./images/StepSeven.webp";
|
||||
import StepSix from "./images/StepSix.webp";
|
||||
import StepThree from "./images/StepThree.webp";
|
||||
import StepTwo from "./images/StepTwo.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Recall Functionality for Formbricks Surveys",
|
||||
title: "Recall Data in Formbricks Surveys",
|
||||
description:
|
||||
"Enhance your surveys with the Recall Functionality to create more engaging and personalized experiences. This feature allows users to dynamically include responses from previous answers in subsequent questions or descriptions, adapting the survey content based on individual responses.",
|
||||
};
|
||||
"Personalize your surveys by dynamically inserting data from URL parameters or previous answers into questions and descriptions. The Recall Data feature helps create engaging, adaptive survey experiences tailored to each respondent."};
|
||||
|
||||
# Recall Functionality
|
||||
# Recall Data
|
||||
|
||||
Enhance your surveys with the Recall Functionality to create more engaging and personalized experiences. This feature allows users to dynamically include responses from previous answers in subsequent questions or descriptions, adapting the survey content based on individual responses.
|
||||
Personalize your surveys by dynamically inserting data from URL parameters or previous answers into questions and descriptions. The Recall Data feature helps create engaging, adaptive survey experiences tailored to each respondent.
|
||||
|
||||
### **How to Insert Recall References**
|
||||
## Recall Sources
|
||||
You can recall data from the following sources:
|
||||
|
||||
- The response of a previous question
|
||||
- The URL using a [Hidden Field](/docs/global/hidden-fields)
|
||||
|
||||
## Recalling from a previous question
|
||||
|
||||
<Note>
|
||||
The recall functionality is disabled on the first question of the survey since there’s no preceding question
|
||||
to recall
|
||||
to recall data from.
|
||||
</Note>
|
||||
|
||||
**Pre-requisite:** Ensure the answer you wish to recall precedes the question in which it will be recalled. Here’s an example of setting up the first question:
|
||||
### **Pre-requisite**
|
||||
|
||||
<MdxImage
|
||||
src={StepOne}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
### **Steps to Initiate Recall**
|
||||
|
||||
1. **Initiate Recall**: In the survey editor, type **`@`** in the question or description field where you want to insert a recall. This triggers a dropdown menu listing all preceding questions.
|
||||
|
||||
<MdxImage
|
||||
src={StepTwo}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
2. **Select a Question**: Navigate through the dropdown to choose your question. The first question is automatically focused, making it easy to select by pressing **`ENTER`**. Once selected, the question becomes a linked placeholder within the text field.
|
||||
|
||||
### **Configuring Fallback Options**
|
||||
|
||||
To ensure the survey remains coherent when a response is missing (or the question is optional), you can set a fallback option.
|
||||
Ensure the answer you wish to recall precedes the question in which it will be recalled. Here’s an example of setting up the first question:
|
||||
|
||||
<MdxImage
|
||||
src={StepThree}
|
||||
alt="Choose a link survey template"
|
||||
alt="Survey setup example with link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
1. **Access Fallback Settings**: Click the linked placeholder to open the configuration pop-up for fallback settings.
|
||||
2. **Set Fallback Text**: Input the fallback text that should appear if the recalled question lacks a response. This text ensures smooth survey progression.
|
||||
### **Step 1: Recall Data**
|
||||
|
||||
### **Example of Recall Usage**
|
||||
|
||||
For example, you might structure a survey about favorite fruits as follows:
|
||||
|
||||
- **Question 1**: "What is your favorite fruit?"
|
||||
- **Question 2**: "Why do you like `@What is your favorite fruit?` so much?"
|
||||
|
||||
If "Question 1" is unanswered, you can set a fallback like "the fruit" to make "Question 2" read: "Why do you like the fruit so much?"
|
||||
|
||||
### **User Experience**
|
||||
|
||||
If a respondent answers “Mango” to the first question, the recall functionality automatically adjusts the following question to "Why is Mango your favorite?”
|
||||
Type **`@`** in the question or description field where you want to insert a recall. This triggers a dropdown menu listing all preceding questions. Select the question you want to recall data from.
|
||||
|
||||
<MdxImage
|
||||
src={StepFour}
|
||||
alt="Choose a link survey template"
|
||||
src={StepTwo}
|
||||
alt="Dropdown menu for recalling data in survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
As you can see, the questions that use this recall automatically use the response value:
|
||||
### **Step 2: Set a Fallback**
|
||||
|
||||
To ensure the survey remains coherent when a response is missing (or the question is optional), you should set a fallback option.
|
||||
|
||||
<MdxImage
|
||||
src={StepFive}
|
||||
alt="Choose a link survey template"
|
||||
src={StepOne}
|
||||
alt="Setting fallback option in survey question"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
<MdxImage
|
||||
src={StepSix}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
## Recalling from the URL
|
||||
1. Create a hidden field, [here is how →](/docs/global/hidden-fields)
|
||||
2. Use the `@` symbol in a question or description to recall the value of the hidden field
|
||||
3. Set a fallback in case the hidden field is not being filled by a URL parameter
|
||||
4. Use [Data Prefilling](/docs/link-surveys/data-prefilling) to set the hidden field value when the survey is accessed
|
||||
|
||||
### **Visual Indicators in UI**
|
||||
|
||||
When setting up your questions, the UI will visually indicate where recalls are used:
|
||||
|
||||
- **Recall Indicators**: Linked questions will be highlighted with a light grey background in the survey editor, making it clear where dynamic content is being utilized.
|
||||
- **Fallback Indicators**: Any fallbacks set will also be displayed in a subtle yet distinct manner, ensuring you are aware of all conditional text paths in your survey.
|
||||
## Live Demo
|
||||
|
||||
### Response Summary Page
|
||||
|
||||
On the Formbricks dashboard, summary responses and individual response cards reflect recalled information, ensuring data coherence and enhancing analysis.
|
||||
|
||||
- Summary Tab
|
||||
|
||||
<MdxImage
|
||||
src={StepSeven}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
- Response Card:
|
||||
|
||||
<MdxImage
|
||||
src={StepEight}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
<SurveyEmbed surveyUrl="https://app.formbricks.com/s/cm393eiiq0001kxphzc6lbbku" />
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
|
||||
BIN
apps/docs/app/global/variables/images/created-variables.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/docs/app/global/variables/images/input-variables.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/docs/app/global/variables/images/logic-with-variables.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
apps/docs/app/global/variables/images/variables-card.webp
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
apps/docs/app/global/variables/images/variables-usage.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
89
apps/docs/app/global/variables/page.mdx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import CreatedVariables from "./images/created-variables.webp";
|
||||
import InputVariables from "./images/input-variables.webp";
|
||||
import LogicWithVariables from "./images/logic-with-variables.webp";
|
||||
import VariablesCard from "./images/variables-card.webp";
|
||||
import VariablesUsage from "./images/variables-usage.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Variables",
|
||||
description: "Add variabeles to your surveys to transform your survey into a quiz",
|
||||
};
|
||||
|
||||
# Variables
|
||||
|
||||
Variables are a powerful feature in Formbricks that allows you to keep track of data variables when user fills a form. This feature is especially useful when you want to use your survey as a quiz.
|
||||
|
||||
## Types of Variables
|
||||
|
||||
There are two types of variables you can add to your survey:
|
||||
|
||||
1. **Text**: You can add text variables to your survey to capture text data.
|
||||
2. **Number**: You can add number variables to your survey to capture number data.
|
||||
|
||||
## How to Add Variables
|
||||
|
||||
1. Edit the survey you want to add variables to & switch to the Questions tab and scroll down to the bottom of the page. You will see a section called **Variables**.
|
||||
|
||||
<MdxImage
|
||||
src={VariablesCard}
|
||||
alt="Variables card"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Now click on it to add a new variable ID. You can add as many variables as you want. You can also choose the type of variable you want to add along with the default value.
|
||||
|
||||
<MdxImage
|
||||
src={InputVariables}
|
||||
alt="add variables"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
<MdxImage
|
||||
src={CreatedVariables}
|
||||
alt="created variables"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Use case:
|
||||
|
||||
- **Quiz**: You can use variables to create a quiz. For example, you can add a variable `score` and update it every time a user answers a question. You can also use the variable for recall to show the final score to the user.
|
||||
|
||||
- **Personalisation**: You can use variables to store user data and personalise the survey experience. For example, you can add a variable `full_name` and update it every time a user fills in their first name and last name. You can use the variable to personalise the survey experience by addressing the user with their full name.
|
||||
|
||||
## How is it different from Hidden Fields?
|
||||
|
||||
Variables are different from hidden fields in the following ways:
|
||||
|
||||
1. **Setting**: Hidden fields can be set through query parameters or `formbricks.init`, but the variables can only be set either during creation or dynamically by using logic actions.
|
||||
2. **Updating**: Hidden fields cannot be set again, but the value of variables can be updated while the user fills the survey.
|
||||
3. **Type**: Hidden fields can only store text data, but variables can store both text and number data.
|
||||
|
||||
## How to use Variables
|
||||
|
||||
1. Once you have added the variables to your survey, you'll be able to access them in the logic editor. You can use the variables to create logic actions and conditions.
|
||||
|
||||
<MdxImage
|
||||
src={VariablesUsage}
|
||||
alt="Variables usage"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. You can create logic based on the variables and questions in your survey and can update the variables based on the user's response.
|
||||
|
||||
<MdxImage
|
||||
src={LogicWithVariables}
|
||||
alt="Logic with variables"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
<Note>
|
||||
To know more about how to use logic in Formbricks, check out the [Conditional
|
||||
Logic](/global/conditional-logic).
|
||||
</Note>
|
||||
@@ -5,6 +5,7 @@ import "@/styles/tailwind.css";
|
||||
import glob from "fast-glob";
|
||||
import { type Metadata } from "next";
|
||||
import { Jost } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -27,6 +28,18 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<html lang="en" className="h-full" suppressHydrationWarning>
|
||||
<head>
|
||||
{process.env.NEXT_PUBLIC_LAYER_API_KEY && (
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
src="https://storage.googleapis.com/generic-assets/buildwithlayer-widget-4.js"
|
||||
primary-color="#00C4B8"
|
||||
api-key={process.env.NEXT_PUBLIC_LAYER_API_KEY}
|
||||
walkthrough-enabled="false"
|
||||
design-style="copilot"
|
||||
/>
|
||||
)}
|
||||
</head>
|
||||
<body className={`flex min-h-full bg-white antialiased dark:bg-zinc-900 ${jost.className}`}>
|
||||
<Providers>
|
||||
<div className="w-full">
|
||||
|
||||
54
apps/docs/app/self-hosting/cluster-setup/page.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
export const metadata = {
|
||||
title: "Cluster Setup for Formbricks",
|
||||
description: "Learn how to set up Formbricks in a multi-instance cluster configuration for high availability and scalability.",
|
||||
};
|
||||
|
||||
#### Self-Hosting
|
||||
|
||||
# Cluster Setup
|
||||
|
||||
<Note>
|
||||
Running Formbricks in a multi-instance cluster configuration is an Enterprise Edition feature and requires an enterprise license key.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
Running Formbricks as a cluster of multiple instances offers several key advantages:
|
||||
|
||||
- **High Availability**: Ensure your surveys remain accessible even if some instances fail
|
||||
- **Load Distribution**: Handle higher traffic by distributing requests across multiple instances
|
||||
- **Scalability**: Easily scale horizontally by adding more instances as your needs grow
|
||||
- **Zero-Downtime Updates**: Rolling updates without service interruption
|
||||
|
||||
## Requirements
|
||||
|
||||
To run Formbricks in a cluster setup, you'll need:
|
||||
|
||||
- Enterprise Edition license key
|
||||
- Shared PostgreSQL database
|
||||
- Shared Redis cache for session management and caching
|
||||
- Load balancer to distribute traffic
|
||||
|
||||
## Redis Configuration
|
||||
|
||||
<Note>
|
||||
Redis integration is an Enterprise Edition feature and requires an enterprise license key.
|
||||
</Note>
|
||||
|
||||
Configure Redis by adding the following environment variables to your instances:
|
||||
|
||||
```sh {{ title: 'env file' }}
|
||||
REDIS_URL=redis://your-redis-host:6379
|
||||
REDIS_HTTP_URL=http://your-redis-host:8000
|
||||
```
|
||||
|
||||
## Coming Soon
|
||||
|
||||
We're working on comprehensive Kubernetes deployment instructions to make cluster setup even easier. This will include:
|
||||
|
||||
- Kubernetes manifests
|
||||
- Helm charts
|
||||
- Detailed scaling configurations
|
||||
- Production-ready deployment examples
|
||||
|
||||
Stay tuned for updates.
|
||||
@@ -302,6 +302,7 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
|
||||
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
|
||||
|
||||
- `channels:read`
|
||||
- `groups:read`
|
||||
- `chat:write`
|
||||
- `chat:write.public`
|
||||
- `chat:write.customize`
|
||||
|
||||
@@ -19,7 +19,7 @@ The Formbricks Core source code is licensed under AGPLv3 and available on GitHub
|
||||
|
||||
## Enterprise Edition License
|
||||
|
||||
Additional to the AGPLv3 licensed Formbricks core, the Formbricks repository contains code licensed under our **[Enterprise License](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE)**. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it. For the pricing, please refer to [Formbricks Pricing](https://formbricks.com/pricing).
|
||||
Additional to the AGPLv3 licensed Formbricks core, the Formbricks repository contains code licensed under our **[Enterprise License](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE)**. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it. For the pricing, please refer to [Formbricks Pricing](https://formbricks.com/pricing).
|
||||
|
||||
### When do I need an Enterprise License?
|
||||
|
||||
@@ -64,7 +64,7 @@ The Formbricks core application is licensed under the **[AGPLv3 Open Source Lice
|
||||
|
||||
### The Enterprise Edition
|
||||
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise License. The **[code](https://github.com/formbricks/formbricks/tree/main/packages/ee)** and **[license](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE)** for the enterprise functionality can be found in the `/packages/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it.
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The **[code](https://github.com/formbricks/formbricks/tree/main/apps/web/modules/ee)** and **[license](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE)** for the enterprise functionality can be found in the `/apps/web/modules/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **[Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise)** to unlock it.
|
||||
|
||||
## White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ export const navigation: Array<NavGroup> = [
|
||||
title: "Add Image/Video to Question",
|
||||
href: "/global/add-image-or-video-question",
|
||||
},
|
||||
{ title: "Variables", href: "/global/variables" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -85,6 +86,7 @@ export const navigation: Array<NavGroup> = [
|
||||
title: "Add Image/Video to Question",
|
||||
href: "/global/add-image-or-video-question",
|
||||
},
|
||||
{ title: "Variables", href: "/global/variables" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -141,6 +143,7 @@ export const navigation: Array<NavGroup> = [
|
||||
{ title: "Configuration", href: "/self-hosting/configuration" },
|
||||
{ title: "Integrations", href: "/self-hosting/integrations" },
|
||||
{ title: "License", href: "/self-hosting/license" },
|
||||
{ title: "Cluster Setup", href: "/self-hosting/cluster-setup" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -156,8 +159,6 @@ export const navigation: Array<NavGroup> = [
|
||||
title: "Contributing",
|
||||
children: [
|
||||
{ title: "Get started", href: "/developer-docs/contributing/get-started" },
|
||||
{ title: "Codespaces", href: "/developer-docs/contributing/codespaces" },
|
||||
{ title: "Gitpod", href: "/developer-docs/contributing/gitpod" },
|
||||
{ title: "Troubleshooting", href: "/developer-docs/contributing/troubleshooting" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ const withMDX = nextMDX({
|
||||
const nextConfig = {
|
||||
basePath: "/docs",
|
||||
pageExtensions: ["js", "jsx", "ts", "tsx", "mdx"],
|
||||
transpilePackages: ["@formbricks/ui", "@formbricks/lib"],
|
||||
transpilePackages: ["@formbricks/lib"],
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
"@docsearch/react": "3.6.2",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "2.1.9",
|
||||
"@headlessui/tailwindcss": "0.2.1",
|
||||
"@mapbox/rehype-prism": "0.9.0",
|
||||
"@mdx-js/loader": "3.0.1",
|
||||
"@mdx-js/react": "3.0.1",
|
||||
"@next/mdx": "14.2.15",
|
||||
"@next/mdx": "15.0.3",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@sindresorhus/slugify": "2.2.1",
|
||||
"@tailwindcss/typography": "0.5.15",
|
||||
@@ -39,7 +38,7 @@
|
||||
"lucide-react": "0.452.0",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"mdx-annotations": "0.1.4",
|
||||
"next": "14.2.16",
|
||||
"next": "15.0.3",
|
||||
"next-plausible": "3.12.2",
|
||||
"next-seo": "6.6.0",
|
||||
"next-sitemap": "4.2.3",
|
||||
@@ -47,8 +46,8 @@
|
||||
"node-fetch": "3.3.2",
|
||||
"prism-react-renderer": "2.4.0",
|
||||
"prismjs": "1.29.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-highlight-words": "0.20.0",
|
||||
"react-markdown": "9.0.1",
|
||||
"react-responsive-embed": "2.1.0",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import headlessuiPlugin from "@headlessui/tailwindcss";
|
||||
import forms from "@tailwindcss/forms";
|
||||
import typographyPlugin from "@tailwindcss/typography";
|
||||
import { type Config } from "tailwindcss";
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
@@ -82,5 +81,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [typographyPlugin, headlessuiPlugin, forms],
|
||||
plugins: [typographyPlugin, headlessuiPlugin],
|
||||
} satisfies Config;
|
||||
|
||||
@@ -10,11 +10,7 @@ const getAbsolutePath = (value: string) => {
|
||||
};
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: [
|
||||
"../src/**/*.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
|
||||
"../../../packages/ui/**/stories.@(js|jsx|mjs|ts|tsx)",
|
||||
],
|
||||
stories: ["../src/**/*.mdx", "../../web/modules/ui/**/stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-onboarding"),
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
import "../../../packages/ui/globals.css";
|
||||
import "@formbricks/web/modules/ui/globals.css";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
"clean": "rimraf .turbo node_modules dist storybook-static"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"eslint-plugin-react-refresh": "0.4.12",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "2.0.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
import base from "../../packages/ui/tailwind.config";
|
||||
import base from "../web/tailwind.config";
|
||||
|
||||
export default {
|
||||
...base,
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", "../../packages/ui/**/*.{js,ts,jsx,tsx}"],
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", "../web/modules/ui/**/*.{js,ts,jsx,tsx}"],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
@@ -7,4 +8,9 @@ export default defineConfig({
|
||||
define: {
|
||||
"process.env": {},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "../web"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -7,7 +8,6 @@ import { useEffect } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions";
|
||||
|
||||
interface ConnectWithFormbricksProps {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { inviteOrganizationMemberAction } from "@/app/(app)/(onboarding)/organizations/actions";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -8,9 +11,6 @@ import { FormProvider, useForm } from "react-hook-form";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { z } from "zod";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
|
||||
interface InviteOrganizationMemberProps {
|
||||
organization: TOrganization;
|
||||
@@ -19,7 +19,11 @@ interface InviteOrganizationMemberProps {
|
||||
|
||||
const ZInviteOrganizationMemberDetails = z.object({
|
||||
email: z.string().email(),
|
||||
inviteMessage: z.string().trim().min(1),
|
||||
inviteMessage: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine((value) => !/https?:\/\/|<script/i.test(value), "Invite message cannot contain URLs or scripts"),
|
||||
});
|
||||
type TInviteOrganizationMemberDetails = z.infer<typeof ZInviteOrganizationMemberDetails>;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { CodeBlock } from "@/modules/ui/components/code-block";
|
||||
import { Html5Icon, NpmIcon } from "@/modules/ui/components/icons";
|
||||
import { TabBar } from "@/modules/ui/components/tab-bar";
|
||||
import { useTranslations } from "next-intl";
|
||||
import "prismjs/themes/prism.css";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { CodeBlock } from "@formbricks/ui/components/CodeBlock";
|
||||
import { TabBar } from "@formbricks/ui/components/TabBar";
|
||||
import { Html5Icon, NpmIcon } from "@formbricks/ui/components/icons";
|
||||
|
||||
const tabs = [
|
||||
{ id: "html", label: "HTML", icon: <Html5Icon /> },
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { InviteOrganizationMember } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -6,16 +8,15 @@ import { notFound, redirect } from "next/navigation";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface InvitePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: InvitePageProps) => {
|
||||
const Page = async (props: InvitePageProps) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ConnectPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ConnectPageProps) => {
|
||||
const Page = async (props: ConnectPageProps) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
|
||||
const OnboardingLayout = async ({ children, params }) => {
|
||||
const OnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { XMTemplateList } from "@/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/components/XMTemplateList";
|
||||
import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -7,16 +9,15 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getProductByEnvironmentId, getUserProducts } from "@formbricks/lib/product/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface XMTemplatePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: XMTemplatePageProps) => {
|
||||
const Page = async (props: XMTemplatePageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
const t = await getTranslations();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
export const getTeamsByOrganizationId = reactCache(
|
||||
(organizationId: string): Promise<TOrganizationTeam[] | null> =>
|
||||
async (organizationId: string): Promise<TOrganizationTeam[] | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([organizationId, ZId]);
|
||||
|
||||
@@ -3,19 +3,7 @@
|
||||
import { formbricksLogout } from "@/app/lib/formbricks";
|
||||
import FBLogo from "@/images/formbricks-wordmark.svg";
|
||||
import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal";
|
||||
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
import { AiOutlineDiscord } from "react-icons/ai";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
import { ProfileAvatar } from "@formbricks/ui/components/Avatars";
|
||||
import { ProfileAvatar } from "@/modules/ui/components/avatars";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -28,7 +16,19 @@ import {
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
import { AiOutlineDiscord } from "react-icons/ai";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
|
||||
interface LandingSidebarProps {
|
||||
isMultiOrgEnabled: boolean;
|
||||
|
||||
@@ -5,7 +5,11 @@ import { getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getUserProducts } from "@formbricks/lib/product/service";
|
||||
|
||||
const LandingLayout = async ({ children, params }) => {
|
||||
const LandingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organizationId]/landing/components/landing-sidebar";
|
||||
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
|
||||
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -7,9 +8,12 @@ import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
|
||||
|
||||
const ProductOnboardingLayout = async ({ children, params }) => {
|
||||
const ProductOnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -4,7 +4,11 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
|
||||
const OnboardingLayout = async ({ children, params }) => {
|
||||
const OnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { GlobeIcon, GlobeLockIcon, LinkIcon, XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getUserProducts } from "@formbricks/lib/product/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ChannelPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ChannelPageProps) => {
|
||||
const Page = async (props: ChannelPageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getUserProducts } from "@formbricks/lib/product/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ModePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ModePageProps) => {
|
||||
const Page = async (props: ModePageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -4,6 +4,20 @@ import { createProductAction } from "@/app/(app)/environments/[environmentId]/ac
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { TOrganizationTeam } from "@/modules/ee/teams/product-teams/types/teams";
|
||||
import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { ColorPicker } from "@/modules/ui/components/color-picker";
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormError,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormProvider,
|
||||
} from "@/modules/ui/components/form";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { MultiSelect } from "@/modules/ui/components/multi-select";
|
||||
import { SurveyInline } from "@/modules/ui/components/survey";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
@@ -20,20 +34,6 @@ import {
|
||||
TProductUpdateInput,
|
||||
ZProductUpdateInput,
|
||||
} from "@formbricks/types/product";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormError,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormProvider,
|
||||
} from "@formbricks/ui/components/Form";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { MultiSelect } from "@formbricks/ui/components/MultiSelect";
|
||||
import { SurveyInline } from "@formbricks/ui/components/Survey";
|
||||
|
||||
interface ProductSettingsProps {
|
||||
organizationId: string;
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboarding";
|
||||
import { getCustomHeadline } from "@/app/(app)/(onboarding)/lib/utils";
|
||||
import { ProductSettings } from "@/app/(app)/(onboarding)/organizations/[organizationId]/products/new/settings/components/ProductSettings";
|
||||
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Header } from "@/modules/ui/components/header";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { DEFAULT_BRAND_COLOR, DEFAULT_LOCALE } from "@formbricks/lib/constants";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { getUserProducts } from "@formbricks/lib/product/service";
|
||||
import { getUserLocale } from "@formbricks/lib/user/service";
|
||||
import { TProductConfigChannel, TProductConfigIndustry, TProductMode } from "@formbricks/types/product";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ProductSettingsPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
searchParams: {
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
channel?: TProductConfigChannel;
|
||||
industry?: TProductConfigIndustry;
|
||||
mode?: TProductMode;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params, searchParams }: ProductSettingsPageProps) => {
|
||||
const Page = async (props: ProductSettingsPageProps) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OptionCard } from "@/modules/ui/components/option-card";
|
||||
import { LucideProps } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ForwardRefExoticComponent, RefAttributes } from "react";
|
||||
import { OptionCard } from "@formbricks/ui/components/OptionCard";
|
||||
|
||||
interface OnboardingOptionsContainerProps {
|
||||
options: {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { FormbricksClient } from "@/app/(app)/components/FormbricksClient";
|
||||
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
|
||||
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
|
||||
import { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
|
||||
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -10,10 +12,12 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { DevEnvironmentBanner } from "@formbricks/ui/components/DevEnvironmentBanner";
|
||||
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
|
||||
|
||||
const SurveyEditorEnvironmentLayout = async ({ children, params }) => {
|
||||
const SurveyEditorEnvironmentLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
getProductIdFromSurveyId,
|
||||
} from "@/lib/utils/helper";
|
||||
import { getSegment, getSurvey } from "@/lib/utils/services";
|
||||
import { getSurveyFollowUpsPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { checkMultiLanguagePermission } from "@/modules/ee/multi-language-surveys/lib/actions";
|
||||
import { z } from "zod";
|
||||
import { createActionClass } from "@formbricks/lib/actionClass/service";
|
||||
import { UNSPLASH_ACCESS_KEY, UNSPLASH_ALLOWED_DOMAINS } from "@formbricks/lib/constants";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { getProduct } from "@formbricks/lib/product/service";
|
||||
import {
|
||||
cloneSegment,
|
||||
@@ -26,15 +29,37 @@ import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { loadNewSegmentInSurvey, updateSurvey } from "@formbricks/lib/survey/service";
|
||||
import { ZActionClassInput } from "@formbricks/types/action-classes";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { OperationNotAllowedError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZBaseFilters, ZSegmentFilters, ZSegmentUpdateInput } from "@formbricks/types/segment";
|
||||
import { ZSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
/**
|
||||
* Checks if survey follow-ups are enabled for the given organization.
|
||||
*
|
||||
* @param { string } organizationId The ID of the organization to check.
|
||||
* @returns { Promise<void> } A promise that resolves if the permission is granted.
|
||||
* @throws { ResourceNotFoundError } If the organization is not found.
|
||||
* @throws { OperationNotAllowedError } If survey follow-ups are not enabled for the organization.
|
||||
*/
|
||||
const checkSurveyFollowUpsPermission = async (organizationId: string): Promise<void> => {
|
||||
const organization = await getOrganization(organizationId);
|
||||
if (!organization) {
|
||||
throw new ResourceNotFoundError("Organization", organizationId);
|
||||
}
|
||||
|
||||
const isSurveyFollowUpsEnabled = await getSurveyFollowUpsPermission(organization);
|
||||
if (!isSurveyFollowUpsEnabled) {
|
||||
throw new OperationNotAllowedError("Survey follow ups are not enabled for this organization");
|
||||
}
|
||||
};
|
||||
|
||||
export const updateSurveyAction = authenticatedActionClient
|
||||
.schema(ZSurvey)
|
||||
.action(async ({ ctx, parsedInput }) => {
|
||||
const organizationId = await getOrganizationIdFromSurveyId(parsedInput.id);
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId: await getOrganizationIdFromSurveyId(parsedInput.id),
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
@@ -48,6 +73,14 @@ export const updateSurveyAction = authenticatedActionClient
|
||||
],
|
||||
});
|
||||
|
||||
if (parsedInput.followUps?.length) {
|
||||
await checkSurveyFollowUpsPermission(organizationId);
|
||||
}
|
||||
|
||||
if (parsedInput.languages?.length) {
|
||||
await checkMultiLanguagePermission(organizationId);
|
||||
}
|
||||
|
||||
return await updateSurvey(parsedInput);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { ModalWithTabs } from "@/modules/ui/components/modal-with-tabs";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { ModalWithTabs } from "@formbricks/ui/components/ModalWithTabs";
|
||||
import { CreateNewActionTab } from "./CreateNewActionTab";
|
||||
import { SavedActionsTab } from "./SavedActionsTab";
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
import { type JSX, useEffect } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { QuestionToggleTable } from "@formbricks/ui/components/QuestionToggleTable";
|
||||
|
||||
interface AddressQuestionFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { Slider } from "@/modules/ui/components/slider";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
@@ -8,9 +11,6 @@ import { UseFormReturn } from "react-hook-form";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TProductStyling } from "@formbricks/types/product";
|
||||
import { TSurveyStyling } from "@formbricks/types/surveys/types";
|
||||
import { Badge } from "@formbricks/ui/components/Badge";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
|
||||
import { Slider } from "@formbricks/ui/components/Slider";
|
||||
import { SurveyBgSelectorTab } from "./SurveyBgSelectorTab";
|
||||
|
||||
interface BackgroundStylingCardProps {
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { type JSX, useState } from "react";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
|
||||
|
||||
const options = [
|
||||
{
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { type JSX, useEffect, useState } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
|
||||
interface CalQuestionFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { CardArrangementTabs } from "@/modules/ui/components/card-arrangement-tabs";
|
||||
import { ColorPicker } from "@/modules/ui/components/color-picker";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { Slider } from "@/modules/ui/components/slider";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
@@ -10,12 +16,6 @@ import { cn } from "@formbricks/lib/cn";
|
||||
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { TProduct, TProductStyling } from "@formbricks/types/product";
|
||||
import { TSurveyStyling, TSurveyType } from "@formbricks/types/surveys/types";
|
||||
import { Badge } from "@formbricks/ui/components/Badge";
|
||||
import { CardArrangementTabs } from "@formbricks/ui/components/CardArrangementTabs";
|
||||
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
|
||||
import { Slider } from "@formbricks/ui/components/Slider";
|
||||
import { Switch } from "@formbricks/ui/components/Switch";
|
||||
|
||||
type CardStylingSettingsProps = {
|
||||
open: boolean;
|
||||
@@ -41,8 +41,8 @@ export const CardStylingSettings = ({
|
||||
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
|
||||
const isLogoVisible = !!product.logo?.url;
|
||||
|
||||
const linkCardArrangement = form.watch("cardArrangement.linkSurveys") ?? "simple";
|
||||
const appCardArrangement = form.watch("cardArrangement.appSurveys") ?? "simple";
|
||||
const linkCardArrangement = form.watch("cardArrangement.linkSurveys") ?? "straight";
|
||||
const appCardArrangement = form.watch("cardArrangement.appSurveys") ?? "straight";
|
||||
const roundness = form.watch("roundness") ?? 8;
|
||||
|
||||
const [parent] = useAutoAnimate();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ColorPicker } from "@/modules/ui/components/color-picker";
|
||||
import { useState } from "react";
|
||||
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
|
||||
|
||||
interface ColorSurveyBgProps {
|
||||
handleBgChange: (bg: string, bgType: string) => void;
|
||||
|
||||
@@ -3,6 +3,14 @@ import {
|
||||
getDefaultOperatorForQuestion,
|
||||
replaceEndingCardHeadlineRecall,
|
||||
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
@@ -20,14 +28,6 @@ import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
|
||||
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
|
||||
interface ConditionalLogicProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -87,10 +87,12 @@ export function ConditionalLogic({
|
||||
|
||||
const handleRemoveLogic = (logicItemIdx: number) => {
|
||||
const logicCopy = structuredClone(question.logic ?? []);
|
||||
const isLast = logicCopy.length === 1;
|
||||
logicCopy.splice(logicItemIdx, 1);
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
logic: logicCopy,
|
||||
logicFallback: isLast ? undefined : question.logicFallback,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { type JSX, useState } from "react";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
|
||||
interface ConsentQuestionFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
import { type JSX, useEffect } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { QuestionToggleTable } from "@formbricks/ui/components/QuestionToggleTable";
|
||||
|
||||
interface ContactInfoQuestionFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { isValidCssSelector } from "@/app/lib/actionClass/actionClass";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { CodeActionForm } from "@/modules/ui/components/code-action-form";
|
||||
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { NoCodeActionForm } from "@/modules/ui/components/no-code-action-form";
|
||||
import { TabToggle } from "@/modules/ui/components/tab-toggle";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useMemo } from "react";
|
||||
@@ -12,13 +19,6 @@ import {
|
||||
ZActionClassInput,
|
||||
} from "@formbricks/types/action-classes";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { TabToggle } from "@formbricks/ui/components/TabToggle";
|
||||
import { CodeActionForm } from "@formbricks/ui/components/organisms/CodeActionForm";
|
||||
import { NoCodeActionForm } from "@formbricks/ui/components/organisms/NoCodeActionForm";
|
||||
import { createActionClassAction } from "../actions";
|
||||
|
||||
interface CreateNewActionTabProps {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
|
||||
|
||||
interface IDateQuestionFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
import { EditorCardMenu } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditorCardMenu";
|
||||
import { EndScreenForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EndScreenForm";
|
||||
import { RedirectUrlForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RedirectUrlForm";
|
||||
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
import {
|
||||
findEndingCardUsedInLogic,
|
||||
formatTextWithSlashes,
|
||||
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
|
||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { GripIcon, Handshake, Undo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { recallToHeadline } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
@@ -21,8 +29,6 @@ import {
|
||||
TSurveyRedirectUrlCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
|
||||
import { TooltipRenderer } from "@formbricks/ui/components/Tooltip";
|
||||
|
||||
interface EditEndingCardProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -61,6 +67,8 @@ export const EditEndingCard = ({
|
||||
? plan === "free" && endingCard.type !== "redirectToUrl"
|
||||
: false;
|
||||
|
||||
const [openDeleteConfirmationModal, setOpenDeleteConfirmationModal] = useState(false);
|
||||
|
||||
const endingCardTypes = [
|
||||
{ value: "endScreen", label: t("environments.surveys.edit.ending_card") },
|
||||
{
|
||||
@@ -73,6 +81,7 @@ export const EditEndingCard = ({
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: endingCard.id,
|
||||
});
|
||||
|
||||
let open = activeQuestionId === endingCard.id;
|
||||
|
||||
const setOpen = (e) => {
|
||||
@@ -93,6 +102,29 @@ export const EditEndingCard = ({
|
||||
};
|
||||
|
||||
const deleteEndingCard = () => {
|
||||
const isEndingCardUsedInFollowUps = localSurvey.followUps.some((followUp) => {
|
||||
if (followUp.trigger.type === "endings") {
|
||||
if (followUp.trigger.properties?.endingIds?.includes(endingCard.id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// checking if this ending card is used in logic
|
||||
const quesIdx = findEndingCardUsedInLogic(localSurvey, endingCard.id);
|
||||
|
||||
if (quesIdx !== -1) {
|
||||
toast.error(t("environments.surveys.edit.ending_card_used_in_logic", { questionIndex: quesIdx + 1 }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEndingCardUsedInFollowUps) {
|
||||
setOpenDeleteConfirmationModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setLocalSurvey((prevSurvey) => {
|
||||
const updatedEndings = prevSurvey.endings.filter((_, index) => index !== endingCardIndex);
|
||||
return { ...prevSurvey, endings: updatedEndings };
|
||||
@@ -253,6 +285,37 @@ export const EditEndingCard = ({
|
||||
)}
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
|
||||
<ConfirmationModal
|
||||
buttonText={t("common.delete")}
|
||||
onConfirm={() => {
|
||||
setLocalSurvey((prevSurvey) => {
|
||||
const updatedEndings = prevSurvey.endings.filter((_, index) => index !== endingCardIndex);
|
||||
const surveyFollowUps = prevSurvey.followUps.map((f) => {
|
||||
if (f.trigger.properties?.endingIds?.includes(endingCard.id)) {
|
||||
return {
|
||||
...f,
|
||||
trigger: {
|
||||
...f.trigger,
|
||||
properties: {
|
||||
...f.trigger.properties,
|
||||
endingIds: f.trigger.properties.endingIds.filter((id) => id !== endingCard.id),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return f;
|
||||
});
|
||||
|
||||
return { ...prevSurvey, endings: updatedEndings, followUps: surveyFollowUps };
|
||||
});
|
||||
}}
|
||||
open={openDeleteConfirmationModal}
|
||||
setOpen={setOpenDeleteConfirmationModal}
|
||||
text={t("environments.surveys.edit.follow_ups_ending_card_delete_modal_text")}
|
||||
title={t("environments.surveys.edit.follow_ups_ending_card_delete_modal_title")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { FileInput } from "@/modules/ui/components/file-input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { Hand } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -11,9 +14,6 @@ import { cn } from "@formbricks/lib/cn";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyQuestionId, TSurveyWelcomeCard } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { FileInput } from "@formbricks/ui/components/FileInput";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { Switch } from "@formbricks/ui/components/Switch";
|
||||
|
||||
interface EditWelcomeCardProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -19,16 +29,6 @@ import {
|
||||
TSurveyQuestionTypeEnum,
|
||||
TSurveyRedirectUrlCard,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { ConfirmationModal } from "@formbricks/ui/components/ConfirmationModal";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@formbricks/ui/components/DropdownMenu";
|
||||
|
||||
interface EditorCardMenuProps {
|
||||
survey: TSurvey;
|
||||
@@ -142,6 +142,30 @@ export const EditorCardMenu = ({
|
||||
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
<ArrowUpIcon
|
||||
className={cn(
|
||||
"h-4 cursor-pointer text-slate-500",
|
||||
cardIdx === 0 ? "cursor-not-allowed opacity-50" : "hover:text-slate-600"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (cardIdx !== 0) {
|
||||
e.stopPropagation();
|
||||
moveCard(cardIdx, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ArrowDownIcon
|
||||
className={cn(
|
||||
"h-4 cursor-pointer text-slate-500",
|
||||
lastCard ? "cursor-not-allowed opacity-50" : "hover:text-slate-600"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (!lastCard) {
|
||||
e.stopPropagation();
|
||||
moveCard(cardIdx, false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CopyIcon
|
||||
className="h-4 cursor-pointer text-slate-500 hover:text-slate-600"
|
||||
onClick={(e) => {
|
||||
@@ -160,7 +184,6 @@ export const EditorCardMenu = ({
|
||||
deleteCard(cardIdx);
|
||||
}}
|
||||
/>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<EllipsisIcon className="h-4 w-4 text-slate-500 hover:text-slate-600" />
|
||||
@@ -262,7 +285,6 @@ export const EditorCardMenu = ({
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<ConfirmationModal
|
||||
open={logicWarningModal}
|
||||
setOpen={setLogicWarningModal}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyEndScreenCard } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { Switch } from "@formbricks/ui/components/Switch";
|
||||
|
||||
interface EndScreenFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { useGetBillingInfo } from "@/modules/utils/hooks/useGetBillingInfo";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon, XCircleIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import { type JSX, useMemo, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { createI18nString } from "@formbricks/lib/i18n/utils";
|
||||
@@ -15,9 +18,6 @@ import { TAllowedFileExtension, ZAllowedFileExtension } from "@formbricks/types/
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSurvey, TSurveyFileUploadQuestion } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
|
||||
interface FileUploadFormProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { ColorPicker } from "@/modules/ui/components/color-picker";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { CheckIcon, SparklesIcon } from "lucide-react";
|
||||
@@ -11,9 +14,6 @@ import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { mixColor } from "@formbricks/lib/utils/colors";
|
||||
import { TProductStyling } from "@formbricks/types/product";
|
||||
import { TSurveyStyling } from "@formbricks/types/surveys/types";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
|
||||
|
||||
type FormStylingSettingsProps = {
|
||||
open: boolean;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { findHiddenFieldUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { Tag } from "@/modules/ui/components/tag";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { EyeOff } from "lucide-react";
|
||||
@@ -11,11 +16,6 @@ import { cn } from "@formbricks/lib/cn";
|
||||
import { extractRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
import { TSurvey, TSurveyHiddenFields, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { validateId } from "@formbricks/types/surveys/validation";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { Switch } from "@formbricks/ui/components/Switch";
|
||||
import { Tag } from "@formbricks/ui/components/Tag";
|
||||
|
||||
interface HiddenFieldsCardProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -84,6 +84,17 @@ export const HiddenFieldsCard = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const isHiddenFieldUsedInFollowUp = localSurvey.followUps
|
||||
.filter((f) => !f.deleted)
|
||||
.some((followUp) => {
|
||||
return followUp.action.properties.to === fieldId;
|
||||
});
|
||||
|
||||
if (isHiddenFieldUsedInFollowUp) {
|
||||
toast.error(t("environments.surveys.edit.follow_ups_hidden_field_error"));
|
||||
return;
|
||||
}
|
||||
|
||||
updateSurvey(
|
||||
{
|
||||
enabled: true,
|
||||
|
||||