Compare commits

...

20 Commits

Author SHA1 Message Date
Piyush Gupta
65e30f3ab0 fix: improve error handling for Slack integration and update error types 2024-11-11 14:44:27 +05:30
Piyush Gupta
7f1e3502e2 Merge branch 'main' of https://github.com/formbricks/formbricks into mertergolu-fix 2024-11-11 14:07:34 +05:30
Matti Nannt
c34a08561e chore: remove docker-compose for dev environment (#4271) 2024-11-11 07:59:54 +01:00
Matti Nannt
7213c726b4 docs: add beta notes to features currently under development (#4270) 2024-11-11 07:59:05 +01:00
Piyush Gupta
f650ac4e76 fix: hostname regex (#4255) 2024-11-11 05:51:39 +00:00
Johannes
2ff1be2c4a fix: add missing translation (#4266) 2024-11-10 18:12:31 -08:00
Dhruwang Jariwala
61ac306ef3 fix: unresponsive table header and preview of inactive survey issue (#4258)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Johannes <johannes@formbricks.com>
2024-11-09 02:31:29 +00:00
Johannes
022569e404 docs: add recall from URL (#4260) 2024-11-08 19:25:03 +00:00
Matti Nannt
ebf22df7b6 chore: remove old gitpod docs (#4259) 2024-11-08 15:47:40 +01:00
Dhruwang Jariwala
be3bbdf2e2 Merge branch 'main' into main 2024-11-08 16:32:38 +05:30
Dhruwang
b1c2f2c4cd fix reconnect 2024-11-08 16:31:50 +05:30
Dhruwang
7616133a25 tweaks 2024-11-08 15:18:33 +05:30
Dhruwang
60139afd81 merged main 2024-11-08 15:14:05 +05:30
Dhruwang
19e5865d05 fix: backward compatibility 2024-10-17 12:08:53 +05:30
Dhruwang
6c5c27f571 Merge branch 'main' of https://github.com/merteroglu/formbricks into merteroglu/main 2024-10-16 17:48:30 +05:30
Dhruwang Jariwala
c12fb1a9f8 Merge branch 'main' into main 2024-10-16 17:46:00 +05:30
Dhruwang
526439def3 Merge branch 'main' of https://github.com/merteroglu/formbricks into merteroglu/main 2024-10-04 11:45:19 +05:30
Dhruwang
4e01ac211f Merge branch 'main' of https://github.com/Dhruwang/formbricks into merteroglu/main 2024-10-04 11:44:48 +05:30
Dhruwang Jariwala
f2f3ff6d46 Merge branch 'main' into main 2024-10-04 11:44:41 +05:30
Mert Eroğlu
b332cf12ca implement new slack ep 2024-09-29 23:21:30 +03:00
48 changed files with 221 additions and 542 deletions

6
.gitpod.Dockerfile vendored
View File

@@ -1,6 +0,0 @@
FROM gitpod/workspace-full
# Install custom tools, runtime, etc.
RUN brew install yq
RUN pnpm install turbo --global

View File

@@ -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"

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -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`

View File

@@ -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)

View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -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 theres 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. Heres 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. Heres 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**

View File

@@ -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`

View File

@@ -156,8 +156,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" },
],
},

View File

@@ -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) => {

View File

@@ -337,7 +337,7 @@ export const SurveyMenuBar = ({
/>
</div>
{responseCount > 0 && (
<div className="ju flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<div className="flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger>

View File

@@ -192,9 +192,9 @@ export const PersonTable = ({
/>
<div className="w-full overflow-x-auto rounded-xl border border-slate-200">
<Table className="w-full" style={{ tableLayout: "fixed" }}>
<TableHeader>
<TableHeader className="pointer-events-auto">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
<TableRow key={headerGroup.id}>
<SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}>
{headerGroup.headers.map((header) => (
<DataTableHeader
@@ -204,7 +204,7 @@ export const PersonTable = ({
/>
))}
</SortableContext>
</tr>
</TableRow>
))}
</TableHeader>

View File

@@ -7,12 +7,12 @@ import { z } from "zod";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { ZId } from "@formbricks/types/common";
const ZRefreshChannelsAction = z.object({
const ZGetSlackChannelsAction = z.object({
environmentId: ZId,
});
export const refreshChannelsAction = authenticatedActionClient
.schema(ZRefreshChannelsAction)
export const getSlackChannelsAction = authenticatedActionClient
.schema(ZGetSlackChannelsAction)
.action(async ({ ctx, parsedInput }) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,

View File

@@ -23,6 +23,8 @@ interface ManageIntegrationProps {
React.SetStateAction<(TIntegrationSlackConfigData & { index: number }) | null>
>;
refreshChannels: () => void;
showReconnectButton: boolean;
handleSlackAuthorization: () => void;
locale: TUserLocale;
}
@@ -33,6 +35,8 @@ export const ManageIntegration = ({
setIsConnected,
setSelectedIntegration,
refreshChannels,
showReconnectButton,
handleSlackAuthorization,
locale,
}: ManageIntegrationProps) => {
const t = useTranslations();
@@ -70,7 +74,19 @@ export const ManageIntegration = ({
return (
<div className="mt-6 flex w-full flex-col items-center justify-center p-6">
<div className="flex w-full justify-end">
{showReconnectButton && (
<div className="mb-4 flex w-full items-center justify-between space-x-4">
<p className="text-amber-700">
{t.rich("environments.integrations.slack.slack_reconnect_button_description", {
b: (chunks) => <b>{chunks}</b>,
})}
</p>
<Button onClick={handleSlackAuthorization} variant="secondary">
{t("environments.integrations.slack.slack_reconnect_button")}
</Button>
</div>
)}
<div className="flex w-full justify-end space-x-4">
<div className="mr-6 flex items-center">
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
<span className="text-slate-500">

View File

@@ -1,11 +1,11 @@
"use client";
import { refreshChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { getSlackChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { AddChannelMappingModal } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal";
import { ManageIntegration } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/ManageIntegration";
import { authorize } from "@/app/(app)/environments/[environmentId]/integrations/slack/lib/slack";
import slackLogo from "@/images/slacklogo.png";
import { useState } from "react";
import { useEffect, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
@@ -18,7 +18,6 @@ interface SlackWrapperProps {
isEnabled: boolean;
environment: TEnvironment;
surveys: TSurvey[];
channelsArray: TIntegrationItem[];
slackIntegration?: TIntegrationSlack;
webAppUrl: string;
attributeClasses: TAttributeClass[];
@@ -29,27 +28,38 @@ export const SlackWrapper = ({
isEnabled,
environment,
surveys,
channelsArray,
slackIntegration,
webAppUrl,
attributeClasses,
locale,
}: SlackWrapperProps) => {
const [isConnected, setIsConnected] = useState(slackIntegration ? slackIntegration.config?.key : false);
const [slackChannels, setSlackChannels] = useState(channelsArray);
const [slackChannels, setSlackChannels] = useState<TIntegrationItem[]>([]);
const [isModalOpen, setModalOpen] = useState<boolean>(false);
const [showReconnectButton, setShowReconnectButton] = useState<boolean>(false);
const [selectedIntegration, setSelectedIntegration] = useState<
(TIntegrationSlackConfigData & { index: number }) | null
>(null);
const refreshChannels = async () => {
const refreshChannelsResponse = await refreshChannelsAction({ environmentId: environment.id });
const getSlackChannels = async () => {
const getSlackChannelsResponse = await getSlackChannelsAction({ environmentId: environment.id });
if (refreshChannelsResponse?.data) {
setSlackChannels(refreshChannelsResponse.data);
if (
getSlackChannelsResponse?.serverError &&
getSlackChannelsResponse.serverError.includes("missing_scope")
) {
setShowReconnectButton(true);
}
if (getSlackChannelsResponse?.data) {
setSlackChannels(getSlackChannelsResponse.data);
}
};
useEffect(() => {
getSlackChannels();
}, []);
const handleSlackAuthorization = async () => {
authorize(environment.id, webAppUrl).then((url: string) => {
if (url) {
@@ -76,7 +86,9 @@ export const SlackWrapper = ({
setOpenAddIntegrationModal={setModalOpen}
setIsConnected={setIsConnected}
setSelectedIntegration={setSelectedIntegration}
refreshChannels={refreshChannels}
refreshChannels={getSlackChannels}
showReconnectButton={showReconnectButton}
handleSlackAuthorization={handleSlackAuthorization}
locale={locale}
/>
</>

View File

@@ -12,10 +12,8 @@ import { getIntegrationByType } from "@formbricks/lib/integration/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
@@ -45,10 +43,6 @@ const Page = async ({ params }) => {
throw new Error(t("common.product_not_found"));
}
let channelsArray: TIntegrationItem[] = [];
if (slackIntegration && slackIntegration.config.key) {
channelsArray = await getSlackChannels(params.environmentId);
}
const locale = await findMatchingLocale();
const currentUserMembership = await getMembershipByUserIdOrganizationId(
@@ -75,7 +69,6 @@ const Page = async ({ params }) => {
<SlackWrapper
isEnabled={isEnabled}
environment={environment}
channelsArray={channelsArray}
surveys={surveys}
slackIntegration={slackIntegration as TIntegrationSlack}
webAppUrl={WEBAPP_URL}

View File

@@ -196,9 +196,9 @@ export const ResponseTable = ({
<div className="w-fit max-w-full overflow-hidden overflow-x-auto rounded-xl border border-slate-200">
<div className="w-full overflow-x-auto">
<Table className="w-full" style={{ tableLayout: "fixed" }} id="response-table">
<TableHeader>
<TableHeader className="pointer-events-auto">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
<TableRow key={headerGroup.id}>
<SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}>
{headerGroup.headers.map((header) => (
<DataTableHeader
@@ -208,7 +208,7 @@ export const ResponseTable = ({
/>
))}
</SortableContext>
</tr>
</TableRow>
))}
</TableHeader>

View File

@@ -3,7 +3,15 @@
import { ShareEmbedSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareEmbedSurvey";
import { SuccessMessage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage";
import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown";
import { BellRing, Code2Icon, Eye, LinkIcon, MoreVertical, SquarePenIcon, UsersRound } from "lucide-react";
import {
BellRing,
Code2Icon,
CopyIcon,
EyeIcon,
MoreVertical,
SquarePenIcon,
UsersRound,
} from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@ -135,12 +143,8 @@ export const SurveyAnalysisCTA = ({
)}
{survey.type === "link" && (
<Button
variant="secondary"
size="sm"
onClick={() => window.open(getPreviewUrl(), "_blank")}
EndIcon={Eye}>
{t("common.preview")}
<Button variant="secondary" size="sm" onClick={handleCopyLink} EndIcon={CopyIcon}>
{t("common.copy_link")}
</Button>
)}
@@ -168,9 +172,11 @@ export const SurveyAnalysisCTA = ({
<DropdownMenuGroup>
{survey.type === "link" && (
<DropdownMenuItem>
<button onClick={handleCopyLink} className="flex w-full items-center">
<LinkIcon className="mr-2 h-4 w-4" />
{t("common.copy_link")}
<button
onClick={() => window.open(getPreviewUrl(), "_blank")}
className="flex w-full items-center">
<EyeIcon className="mr-2 h-4 w-4" />
{t("common.preview")}
</button>
</DropdownMenuItem>
)}

View File

@@ -1,8 +1,12 @@
import { responses } from "@/app/lib/api/response";
import { NextRequest } from "next/server";
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { createOrUpdateIntegration } from "@formbricks/lib/integration/service";
import { TIntegrationSlackConfig, TIntegrationSlackCredential } from "@formbricks/types/integration/slack";
import { createOrUpdateIntegration, getIntegrationByType } from "@formbricks/lib/integration/service";
import {
TIntegrationSlackConfig,
TIntegrationSlackConfigData,
TIntegrationSlackCredential,
} from "@formbricks/types/integration/slack";
export const GET = async (req: NextRequest) => {
const url = req.url;
@@ -58,18 +62,20 @@ export const GET = async (req: NextRequest) => {
team: data.team,
};
const slackIntegration = await getIntegrationByType(environmentId, "slack");
const slackConfiguration: TIntegrationSlackConfig = {
data: [],
data: (slackIntegration?.config.data as TIntegrationSlackConfigData[]) ?? [],
key: slackCredentials,
};
const slackIntegration = {
const integration = {
type: "slack" as "slack",
environment: environmentId,
config: slackConfiguration,
};
const result = await createOrUpdateIntegration(environmentId, slackIntegration);
const result = await createOrUpdateIntegration(environmentId, integration);
if (result) {
return Response.redirect(`${WEBAPP_URL}/environments/${environmentId}/integrations/slack`);

View File

@@ -45,6 +45,7 @@ interface LinkSurveyProps {
PRIVACY_URL?: string;
IS_FORMBRICKS_CLOUD: boolean;
locale: string;
isPreview: boolean;
}
export const LinkSurvey = ({
@@ -64,11 +65,11 @@ export const LinkSurvey = ({
PRIVACY_URL,
IS_FORMBRICKS_CLOUD,
locale,
isPreview,
}: LinkSurveyProps) => {
const t = useTranslations();
const responseId = singleUseResponse?.id;
const searchParams = useSearchParams();
const isPreview = searchParams?.get("preview") === "true";
const skipPrefilled = searchParams?.get("skipPrefilled") === "true";
const sourceParam = searchParams?.get("source");
const suId = searchParams?.get("suId");

View File

@@ -29,6 +29,7 @@ interface PinScreenProps {
attributeClasses: TAttributeClass[];
isEmbed: boolean;
locale: string;
isPreview: boolean;
}
export const PinScreen = (props: PinScreenProps) => {
@@ -48,6 +49,7 @@ export const PinScreen = (props: PinScreenProps) => {
attributeClasses,
isEmbed,
locale,
isPreview,
} = props;
const [localPinEntry, setLocalPinEntry] = useState<string>("");
@@ -135,6 +137,7 @@ export const PinScreen = (props: PinScreenProps) => {
PRIVACY_URL={PRIVACY_URL}
IS_FORMBRICKS_CLOUD={IS_FORMBRICKS_CLOUD}
locale={locale}
isPreview={isPreview}
/>
);
};

View File

@@ -28,6 +28,7 @@ interface LinkSurveyPageProps {
verify?: string;
lang?: string;
embed?: string;
preview?: string;
};
}
@@ -45,6 +46,7 @@ const Page = async ({ params, searchParams }: LinkSurveyPageProps) => {
if (!validId.success) {
notFound();
}
const isPreview = searchParams.preview === "true";
const survey = await getSurvey(params.surveyId);
const locale = findMatchingLocale();
const suId = searchParams.suId;
@@ -62,7 +64,7 @@ const Page = async ({ params, searchParams }: LinkSurveyPageProps) => {
}
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
if (survey && survey.status !== "inProgress") {
if (survey && survey.status !== "inProgress" && !isPreview) {
return (
<SurveyInactive
status={survey.status}
@@ -174,6 +176,7 @@ const Page = async ({ params, searchParams }: LinkSurveyPageProps) => {
attributeClasses={attributeClasses}
isEmbed={isEmbed}
locale={locale}
isPreview={isPreview}
/>
);
}
@@ -196,6 +199,7 @@ const Page = async ({ params, searchParams }: LinkSurveyPageProps) => {
PRIVACY_URL={PRIVACY_URL}
IS_FORMBRICKS_CLOUD={IS_FORMBRICKS_CLOUD}
locale={locale}
isPreview={isPreview}
/>
) : null;
};

View File

@@ -22,7 +22,6 @@ export const actionClient = createSafeActionClient({
}
console.error("SERVER ERROR: ", e);
return DEFAULT_SERVER_ERROR_MESSAGE;
},
});

View File

@@ -342,7 +342,7 @@ export function AdvancedTargetingCard({
}}
size="sm"
variant={isSegmentUsedInOtherSurveys ? "minimal" : "secondary"}>
{t("common.edit_segment")}
{t("environments.segments.edit_segment")}
<PencilIcon className="ml-2 h-3 w-3" />
</Button>
)}

View File

@@ -1,128 +0,0 @@
# This should be the same as below if you are running via docker compose up
x-webapp-url: &webapp_url http://localhost:3000
x-nextauth-url: &nextauth_url http://localhost:3000
# PostgreSQL DB for Formbricks to connect to
x-database-url: &database_url postgresql://postgres:postgres@postgres:5432/formbricks?schema=public
x-redis-url: &redis_url
# NextJS Auth
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
# You can use: `openssl rand -hex 32` to generate one
x-nextauth-secret: &nextauth_secret
# Encryption key
# You can use: `openssl rand -hex 32` to generate one
x-cron-secret: &cron_secret
# Set the below to use it instead of API Key for the API & use as an auth for cronjobs
# You can use: $(openssl rand -hex 32) to generate a secure one
x-encryption-key: &encryption_key
x-mail-from: &mail_from
x-smtp-host: &smtp_host
x-smtp-port: &smtp_port
x-smtp-secure-enabled: &smtp_secure_enabled # Enable SMTP_SECURE_ENABLED for TLS (port 465)
x-smtp-user: &smtp_user
x-smtp-password: &smtp_password
x-smtp-reject-unauthorized-tls: &smtp_reject_unauthorized_tls 1 # If set to 0, the server will accept connections without requiring authorization from the list of supplied CAs.
x-short-url-base:
&short_url_base # Set the below value if you have and want to share a shorter base URL than the x-survey-base-url
x-email-verification-disabled: &email_verification_disabled 1
# Password Reset. If you enable Password Reset functionality you have to setup SMTP-Settings, too.
x-password-reset-disabled: &password_reset_disabled 1
# Signup. Disable the ability for new users to create an account.
x-signup-disabled: &signup_disabled 1
# Email login. Disable the ability for users to login with email.
x-auth-disabled: &email_auth_disabled 0
# Organization Invite. Disable the ability for invited users to create an account.
x-invite-disabled: &invite_disabled 0
# Set the below values to display privacy policy, imprint and terms of service links in the footer of signup & public pages.
x-privacy-url: &privacy_url
x-terms-url: &terms_url
x-imprint-url: &imprint_url
x-github-id: &github_id
x-github-secret: &github_secret
x-google-client-id: &google_client_id
x-google-client-secret: &google_client_secret
x-sentry-ignore-api-resolution-error: &sentry_ignore_api_resolution_error # Disable Sentry warning
x-next-public-sentry-dsn: &next_public_sentry_dsn # Enable Sentry Error Tracking
services:
postgres:
restart: always
image: pgvector/pgvector:pg17
volumes:
- postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
formbricks:
restart: always
build:
context: .
dockerfile: ./apps/web/Dockerfile
depends_on:
- postgres
ports:
- 3000:3000
environment:
WEBAPP_URL: *webapp_url
DATABASE_URL: *database_url
NEXTAUTH_SECRET: *nextauth_secret
MAIL_FROM: *mail_from
SMTP_HOST: *smtp_host
SMTP_PORT: *smtp_port
SMTP_SECURE_ENABLED: *smtp_secure_enabled
SMTP_USER: *smtp_user
SMTP_PASSWORD: *smtp_password
SMTP_REJECT_UNAUTHORIZED_TLS: *smtp_reject_unauthorized_tls
ENCRYPTION_KEY: *encryption_key
SHORT_URL_BASE: *short_url_base
PRIVACY_URL: *privacy_url
TERMS_URL: *terms_url
IMPRINT_URL: *imprint_url
EMAIL_VERIFICATION_DISABLED: *email_verification_disabled
PASSWORD_RESET_DISABLED: *password_reset_disabled
EMAIL_AUTH_DISABLED: *email_auth_disabled
SIGNUP_DISABLED: *signup_disabled
INVITE_DISABLED: *invite_disabled
SENTRY_IGNORE_API_RESOLUTION_ERROR: *sentry_ignore_api_resolution_error
NEXT_PUBLIC_SENTRY_DSN: *next_public_sentry_dsn
GITHUB_ID: *github_id
GITHUB_SECRET: *github_secret
GOOGLE_CLIENT_ID: *google_client_id
GOOGLE_CLIENT_SECRET: *google_client_secret
CRON_SECRET: *cron_secret
REDIS_URL: *redis_url
volumes:
- uploads:/home/nextjs/apps/web/uploads/
volumes:
postgres:
driver: local
uploads:

View File

@@ -53,7 +53,7 @@ export const INVITE_DISABLED = env.INVITE_DISABLED === "1";
export const SLACK_CLIENT_SECRET = env.SLACK_CLIENT_SECRET;
export const SLACK_CLIENT_ID = env.SLACK_CLIENT_ID;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize`;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize,groups:read`;
export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID;
export const GOOGLE_SHEETS_CLIENT_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET;

View File

@@ -107,6 +107,7 @@
"add_filter": "Filter hinzufügen",
"add_logo": "Logo hinzufügen",
"add_product": "Produkt hinzufügen",
"all": "Alle",
"all_questions": "Alle Fragen",
"allow": "erlauben",
"allow_users_to_exit_by_clicking_outside_the_survey": "Erlaube Nutzern, die Umfrage zu verlassen, indem sie außerhalb klicken",
@@ -684,7 +685,9 @@
"select_channel": "Kanal auswählen",
"slack_integration": "Slack Integration",
"slack_integration_description": "Sende Antworten direkt an Slack.",
"slack_integration_is_not_configured": "Slack Integration ist in deiner Instanz von Formbricks nicht konfiguriert."
"slack_integration_is_not_configured": "Slack Integration ist in deiner Instanz von Formbricks nicht konfiguriert.",
"slack_reconnect_button": "Erneut verbinden",
"slack_reconnect_button_description": "<b>Hinweis:</b> Wir haben kürzlich unsere Slack-Integration geändert, um auch private Kanäle zu unterstützen. Bitte verbinden Sie Ihren Slack-Workspace erneut."
},
"slack_integration_description": "Verbinde deinen Slack Arbeitsbereich sofort mit Formbricks",
"to_configure_it": "es zu konfigurieren.",
@@ -958,7 +961,7 @@
"value_must_be_a_number": "Wert muss eine Zahl sein.",
"view_filters": "Filter anzeigen",
"where": "Wo",
"with_the_formbricks_sdk": "mit dem Formbricks SDK."
"with_the_formbricks_sdk": "mit dem Formbricks SDK"
},
"settings": {
"billing": {
@@ -1571,7 +1574,7 @@
"welcome_message": "Willkommensnachricht",
"when": "Wann",
"when_conditions_match_waiting_time_will_be_ignored_and_survey_shown": "Wenn die Bedingungen übereinstimmen, wird die Wartezeit ignoriert und die Umfrage angezeigt.",
"with_the_formbricks_sdk": "mit dem Formbricks SDK.",
"with_the_formbricks_sdk": "mit dem Formbricks SDK",
"without_a_filter_all_of_your_users_can_be_surveyed": "Ohne Filter können alle deine Nutzer befragt werden.",
"you_need_to_have_two_or_more_languages_set_up_in_your_product_to_work_with_translations": "Du musst zwei oder mehr Sprachen in deinem Produkt einrichten, um mit Übersetzungen zu arbeiten.",
"your_description_here_recall_information_with": "Deine Beschreibung hier. Informationen abrufen mit @",

View File

@@ -107,6 +107,7 @@
"add_filter": "Add filter",
"add_logo": "Add logo",
"add_product": "Add product",
"all": "All",
"all_questions": "All questions",
"allow": "Allow",
"allow_users_to_exit_by_clicking_outside_the_survey": "Allow users to exit by clicking outside the survey",
@@ -684,7 +685,9 @@
"select_channel": "Select Channel",
"slack_integration": "Slack Integration",
"slack_integration_description": "Send responses directly to Slack.",
"slack_integration_is_not_configured": "Slack Integration is not configured in your instance of Formbricks."
"slack_integration_is_not_configured": "Slack Integration is not configured in your instance of Formbricks.",
"slack_reconnect_button": "Reconnect",
"slack_reconnect_button_description": "<b>Note:</b> We recently changed our Slack integration to also support private channels. Please reconnect your Slack workspace."
},
"slack_integration_description": "Instantly connect your Slack Workspace with Formbricks",
"to_configure_it": "to configure it.",
@@ -788,7 +791,7 @@
"tag_of_your_app": "tag of your app",
"to_the": "to the",
"to_the_url_where_you_load_the": "to the URL where you load the",
"to_the_url_where_you_load_the_formbricks_sdk": "to the URL where you load the Formbricks SDK.",
"to_the_url_where_you_load_the_formbricks_sdk": "to the URL where you load the Formbricks SDK",
"want_to_learn_how_to_add_user_attributes": "Want to learn how to add user attributes, custom events and more?",
"you_also_need_to_pass_a": "you also need to pass a",
"you_are_done": "You're done 🎉",
@@ -958,7 +961,7 @@
"value_must_be_a_number": "Value must be a number.",
"view_filters": "View filters",
"where": "Where",
"with_the_formbricks_sdk": "with the Formbricks SDK."
"with_the_formbricks_sdk": "with the Formbricks SDK"
},
"settings": {
"billing": {
@@ -1571,7 +1574,7 @@
"welcome_message": "Welcome message",
"when": "When",
"when_conditions_match_waiting_time_will_be_ignored_and_survey_shown": "When conditions match, waiting time will be ignored and survey shown.",
"with_the_formbricks_sdk": "with the Formbricks SDK.",
"with_the_formbricks_sdk": "with the Formbricks SDK",
"without_a_filter_all_of_your_users_can_be_surveyed": "Without a filter, all of your users can be surveyed.",
"you_need_to_have_two_or_more_languages_set_up_in_your_product_to_work_with_translations": "You need to have two or more languages set up in your product to work with translations.",
"your_description_here_recall_information_with": "Your description here. Recall information with @",

View File

@@ -107,6 +107,7 @@
"add_filter": "Adicionar filtro",
"add_logo": "Adicionar logo",
"add_product": "Adicionar produto",
"all": "Todos",
"all_questions": "Todas as perguntas",
"allow": "permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir que os usuários saiam clicando fora da pesquisa",
@@ -684,7 +685,9 @@
"select_channel": "Selecionar Canal",
"slack_integration": "Integração com o Slack",
"slack_integration_description": "Manda as respostas direto pro Slack.",
"slack_integration_is_not_configured": "A integração do Slack não está configurada na sua instância do Formbricks."
"slack_integration_is_not_configured": "A integração do Slack não está configurada na sua instância do Formbricks.",
"slack_reconnect_button": "Reconectar",
"slack_reconnect_button_description": "<b>Observação:</b> Recentemente, alteramos nossa integração com o Slack para também suportar canais privados. Por favor, reconecte seu workspace do Slack."
},
"slack_integration_description": "Conecte instantaneamente seu Workspace do Slack com o Formbricks",
"to_configure_it": "configurar isso.",

View File

@@ -1,5 +1,5 @@
import { Prisma } from "@prisma/client";
import { DatabaseError } from "@formbricks/types/errors";
import { DatabaseError, UnknownError } from "@formbricks/types/errors";
import { TIntegration, TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationSlack, TIntegrationSlackCredential } from "@formbricks/types/integration/slack";
import { deleteIntegration, getIntegrationByType } from "../integration/service";
@@ -11,8 +11,9 @@ export const fetchChannels = async (slackIntegration: TIntegration): Promise<TIn
let nextCursor: string | undefined = undefined;
do {
const url = new URL("https://slack.com/api/conversations.list");
const url = new URL("https://slack.com/api/users.conversations");
url.searchParams.append("limit", "200");
url.searchParams.append("types", "private_channel,public_channel");
if (nextCursor) {
url.searchParams.append("cursor", nextCursor);
}
@@ -65,7 +66,7 @@ export const getSlackChannels = async (environmentId: string): Promise<TIntegrat
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");
}
throw error;
throw new UnknownError(error);
}
};

View File

@@ -1041,7 +1041,7 @@ export const ZSurvey = z
if (question.type === TSurveyQuestionTypeEnum.Cal) {
if (question.calHost !== undefined) {
const hostnameRegex = /^[a-zA-Z0-9]+(?<domain>\.[a-zA-Z0-9]+)+$/;
const hostnameRegex = /^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,}$/i;
if (!hostnameRegex.test(question.calHost)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,