merged main into feat/embed-xp

This commit is contained in:
Piyush Gupta
2023-10-04 17:35:53 +05:30
81 changed files with 2721 additions and 902 deletions

View File

@@ -6,12 +6,16 @@ export const meta = {
"Dive deep into Formbricks' Public Client API designed for customisation. This comprehensive guide provides detailed instructions on how to mark surveys as displayed as well as responded for individual persons, ensuring seamless client-side interactions without compromising data security.",
};
#### Management API
#### Client API
# Displays API
The Public Client API is designed for the JavaScript SDK and does not require authentication. It's primarily used for creating persons, sessions, and responses within the Formbricks platform. This API is ideal for client-side interactions, as it doesn't expose sensitive information.
This set of API can be used to
- [Mark Survey as Displayed](#mark-survey-as-displayed-for-person)
- [Mark Survey as Responded](#mark-survey-as-responded-for-person)
---
## Mark Survey as Displayed for Person {{ tag: 'POST', label: '/api/v1/client/diplays' }}

View File

@@ -10,10 +10,15 @@ export const meta = {
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.
Checkout the [API Key Setup](/docs/api/api-key-setup) - to generate, store, or delete API Keys.
## Public Client API
The Public Client API is designed for the JavaScript SDK and does not require authentication. It's primarily used for creating persons, sessions, and responses within the Formbricks platform. This API is ideal for client-side interactions, as it doesn't expose sensitive information.
- [Displays API](/docs/api/client/displays) - Mark Survey as Displayed or Responded for a Person
- [Responses API](/docs/api/client/responses) - Create & update responses for a survey
## Management API
The Management API provides access to all data and settings that are visible in the Formbricks App. This API requires a personal API Key for authentication, which can be generated in the Settings section of the Formbricks App. With the Management API, you can manage your Formbricks account programmatically, accessing and modifying data and settings as needed.
@@ -24,6 +29,14 @@ API requests made to the Management API are authorized using a personal API key.
To generate, store, or delete an API key, follow the instructions provided on the following page [API Key](/docs/api/api-key-setup).
- [Action Class API](/docs/api/management/action-classes) - Create, Update, and Delete Action Classes
- [Attribute Class API](/docs/api/management/attribute-classes) - Create, Update, and Delete Attribute Classes
- [Me API](/docs/api/management/me) - Retrieve Account Information
- [People API](/docs/api/management/people) - Create, Update, and Delete People
- [Responses API](/docs/api/management/responses) - Create, Update, and Delete Responses
- [Surveys API](/docs/api/management/surveys) - Create, Update, and Delete Surveys
- [Webhook API](/docs/api/management/webhooks) - Create, Update, and Delete Webhooks
<Note>
By understanding the differences between these two APIs, you can choose the appropriate one for your needs,
ensuring a secure and efficient integration with the Formbricks platform.

View File

@@ -14,104 +14,6 @@ The Public Client API is designed for the JavaScript SDK and does not require au
---
## List all responses {{ tag: 'GET', label: '/api/v1/management/responses' }}
<Row>
<Col>
Retrieve all the responses you have received for all your surveys.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
### Optional Query Params
<Properties>
<Property name="surveyId" type="string">
SurveyId to filter responses by.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/responses">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/responses' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data":[
{
"id":"cllnfybxd051rpl0gieavthr9",
"createdAt":"2023-08-23T07:56:33.121Z",
"updatedAt":"2023-08-23T07:56:41.793Z",
"surveyId":"cllnfy2780fromy0hy7uoxvtn",
"finished":true,
"data":{
"ec7agikkr58j8uonhioinkyk":"Loved it!",
"gml6mgy71efgtq8np3s9je5p":"clicked",
"klvpwd4x08x8quesihvw5l92":"Product Manager",
"kp62fbqe8cfzmvy8qwpr81b2":"Very disappointed",
"lkjaxb73ulydzeumhd51sx9g":"Not interesed.",
"ryo75306flyg72iaeditbv51":"Would love if it had dark theme."
},
"meta":{
"url":"https://app.formbricks.com/s/cllnfy2780fromy0hy7uoxvtn",
"userAgent":{
"os":"Linux",
"browser":"Chrome"
}
},
"personAttributes":null,
"person":null,
"notes":[],
"tags":[]
}
]
}
```
```json {{ title: '404 Not Found' }}
{
"code": "not_found",
"message": "cllnfy2780fromy0hy7uoxvt not found",
"details": {
"resource_id": "survey",
"resource_type": "cllnfy2780fromy0hy7uoxvt"
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Create a response {{ tag: 'POST', label: '/api/v1/client/responses' }}
Add a new response to a survey.
@@ -222,7 +124,7 @@ Add a new response to a survey.
---
## Update a response {{ tag: 'POST', label: '/api/v1/client/responses/[responseId]' }}
## Update a response {{ tag: 'POST', label: '/api/v1/client/responses/<response-id>' }}
Update an existing response in a survey.
@@ -247,10 +149,10 @@ Update an existing response in a survey.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/api/v1/client/responses/[responseId]">
<CodeGroup title="Request" tag="POST" label="/api/v1/client/responses/<response-id>">
```bash {{ title: 'cURL' }}
curl --location --request POST 'https://app.formbricks.com/api/v1/client/responses/[responseId]' \
curl --location --request POST 'https://app.formbricks.com/api/v1/client/responses/<response-id>' \
--data-raw '{
"personId": "clfqjny0v000ayzgsycx54a2c",
"surveyId": "clfqz1esd0000yzah51trddn8",

View File

@@ -0,0 +1,301 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks People API: Fetch or Create Person Overview",
description:
"Dive into Formbricks' People API within the Public Client API suite, designed to work without authentication requirements. Seamlessly fetch or create a person by their userId and environmentId, optimizing client-side interactions while maintaining data privacy.",
};
#### Management API
# Action Classes API
This set of API can be used to
- [List Actions](#get-all-action-classes)
- [Get Action](#get-action-class-by-id)
- [Create Actions](#create-action-class)
- [Delete Actions](#delete-action-class)
<Note>You will need an API Key to interact with these APIs.</Note>
---
## Get all Action Classes {{ tag: 'GET', label: '/api/v1/management/action-classes' }}
<Row>
<Col>
Get all the existing action classes in your environment.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/action-classes">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/action-classes' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": [
{
"id": "cln8k0t47000gz87nw4ibwv35",
"createdAt": "2023-10-02T07:13:19.207Z",
"updatedAt": "2023-10-02T07:13:19.207Z",
"name": "New Session",
"description": "Gets fired when a new session is created",
"type": "automatic",
"noCodeConfig": null,
"environmentId": "cln8k0t47000fz87njmmu2bck"
},
{
"id": "cln8k0t55000uz87noerwdooj",
"createdAt": "2023-10-02T07:13:19.241Z",
"updatedAt": "2023-10-02T07:13:19.241Z",
"name": "Invited Team Member",
"description": "Person invited a team member",
"type": "noCode",
"noCodeConfig": {
"type": "innerHtml",
"innerHtml": {
"value": "Add Team Member"
}
},
"environmentId": "cln8k0t47000fz87njmmu2bck"
},
]
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Get Action Class by ID {{ tag: 'GET', label: '/api/v1/management/action-classes/<action-class-id>' }}
<Row>
<Col>
Fetch an action class by its ID.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/action-classes/<action-class-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/action-classes/<action-class-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln8k0t55000uz87noerwdooj",
"createdAt": "2023-10-02T07:13:19.241Z",
"updatedAt": "2023-10-02T07:13:19.241Z",
"name": "Invited Team Member",
"description": "Person invited a team member",
"type": "noCode",
"noCodeConfig": {
"type": "innerHtml",
"innerHtml": {
"value": "Add Team Member"
}
},
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Create Action Class {{ tag: 'POST', label: '/api/v1/management/action-classes/' }}
<Row>
<Col>
Create an action class.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
### Body
<CodeGroup title="Request Body">
```json {{ title: 'cURL' }}
{
"environmentId": "cln8k0t47000fz87njmmu2bck",
"name": "My Action from API",
"type": "code"
}
```
</CodeGroup>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/api/v1/management/action-classes/">
```bash {{ title: 'cURL' }}
curl -X POST https://app.formbricks.com/api/v1/management/action-classes/ \
--header 'Content-Type: application/json' \
--header 'x-api-key: <your-api-key>' \
-d '{"environmentId": "cln8k0t47000fz87njmmu2bck", "name": "My Action from API", "type": "code"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln9w1cno0008z8zu79nk5w0c",
"createdAt": "2023-10-03T05:37:26.100Z",
"updatedAt": "2023-10-03T05:37:26.100Z",
"name": "My Action from API",
"description": null,
"type": "code",
"noCodeConfig": null,
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Delete Action Class {{ tag: 'DELETE', label: '/api/v1/management/action-classes/<action-class-id>' }}
<Row>
<Col>
Delete an action class by its ID.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/management/action-classes/<action-class-id>">
```bash {{ title: 'cURL' }}
curl -X DELETE https://app.formbricks.com/api/v1/management/action-classes/<action-class-id> \
--header 'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln9w1cno0008z8zu79nk5w0c",
"createdAt": "2023-10-03T05:37:26.100Z",
"updatedAt": "2023-10-03T05:37:26.100Z",
"name": "My Action from API",
"description": null,
"type": "code",
"noCodeConfig": null,
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,292 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks People API: Fetch or Create Person Overview",
description:
"Dive into Formbricks' People API within the Public Client API suite, designed to work without authentication requirements. Seamlessly fetch or create a person by their userId and environmentId, optimizing client-side interAttributes while maintaining data privacy.",
};
#### Management API
# Attribute Classes API
This set of API can be used to
- [List Attributes](#get-all-attribute-classes)
- [Get Attributes](#get-attribute-class-by-id)
- [Create Attributes](#create-attribute-class)
- [Delete Attributes](#delete-attribute-class)
<Note>You will need an API Key to interact with these APIs.</Note>
---
## Get all Attribute Classes {{ tag: 'GET', label: '/api/v1/management/attribute-classes' }}
<Row>
<Col>
Get all the existing attribute classes in your environment.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/attribute-classes">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/attribute-classes' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": [
{
"id": "cln8k0t47000kz87n3lh23zf0",
"createdAt": "2023-10-02T07:13:19.207Z",
"updatedAt": "2023-10-02T07:13:19.207Z",
"name": "email",
"description": "The email of the person",
"archived": false,
"type": "automatic",
"environmentId": "cln8k0t47000fz87njmmu2bck"
},
{
"id": "cln8k0t55000xz87nrtwbo7sf",
"createdAt": "2023-10-02T07:13:19.241Z",
"updatedAt": "2023-10-02T07:13:19.241Z",
"name": "Name",
"description": "Full Name of the Person",
"archived": false,
"type": "code",
"environmentId": "cln8k0t47000fz87njmmu2bck"
},
]
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Get Attribute Class by ID {{ tag: 'GET', label: '/api/v1/management/attribute-classes/<attribute-class-id>' }}
<Row>
<Col>
Fetch an Attribute class by its ID.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/attribute-classes/<attribute-class-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/attribute-classes/<attribute-class-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln8k0t47000jz87nfwcey6mh",
"createdAt": "2023-10-02T07:13:19.207Z",
"updatedAt": "2023-10-02T07:13:19.207Z",
"name": "userId",
"description": "The internal ID of the person",
"archived": false,
"type": "automatic",
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Create Attribute Class {{ tag: 'POST', label: '/api/v1/management/attribute-classes/' }}
<Row>
<Col>
Create an Attribute class.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
### Body
<CodeGroup title="Request Body">
```json {{ title: 'cURL' }}
{
"environmentId": "clmlmwdqq0003196ufewo6ibg",
"name": "My Attribute from API",
"type": "code",
"description": "My description"
}
```
</CodeGroup>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/api/v1/management/attribute-classes/">
```bash {{ title: 'cURL' }}
curl -X POST https://app.formbricks.com/api/v1/management/attribute-classes/ \
--header 'Content-Type: application/json' \
--header 'x-api-key: <your-api-key>' \
-d '{"environmentId": "clmlmwdqq0003196ufewo6ibg", "name": "My Attribute from API", "type": "code", "description":"My description"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "clna0hd7z0009z8zue2z3a7wy",
"createdAt": "2023-10-03T07:41:51.792Z",
"updatedAt": "2023-10-03T07:41:51.792Z",
"name": "My Attribute from API",
"description": null,
"archived": false,
"type": "code",
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Delete Attribute Class {{ tag: 'DELETE', label: '/api/v1/management/attribute-classes/<attribute-class-id>' }}
<Row>
<Col>
Delete an Attribute class by its ID.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/management/attribute-classes/<attribute-class-id>">
```bash {{ title: 'cURL' }}
curl -X DELETE https://app.formbricks.com/api/v1/management/attribute-classes/<attribute-class-id> \
--header 'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "clna0hd7z0009z8zue2z3a7wy",
"createdAt": "2023-10-03T07:41:51.792Z",
"updatedAt": "2023-10-03T07:41:51.792Z",
"name": "My Attribute from API",
"description": null,
"archived": false,
"type": "code",
"environmentId": "cln8k0t47000fz87njmmu2bck"
}
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -0,0 +1,78 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks People API: Fetch or Create Person Overview",
description:
"Dive into Formbricks' People API within the Public Client API suite, designed to work without authentication requirements. Seamlessly fetch or create a person by their userId and environmentId, optimizing client-side interactions while maintaining data privacy.",
};
#### Management API
# Me API
This API can be used to get your own current environment details.
<Note>You will need an API Key to interact with these APIs.</Note>
---
## Get Environment {{ tag: 'GET', label: '/api/v1/management/me' }}
<Row>
<Col>
Get your current environment details.
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/me">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/me' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"id": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.207Z",
"updatedAt": "2023-10-02T07:14:14.162Z",
"type": "production",
"product": {
"id": "cln8k0t47000ez87n57aqywvz",
"name": "Demo Product"
},
"widgetSetupCompleted": true
}
```
```json {{ title: '401 Unauthorized' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -0,0 +1,234 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks People API: Fetch or Create Person Overview",
description:
"Dive into Formbricks' People API within the Public Client API suite, designed to work without authentication requirements. Seamlessly fetch or create a person by their userId and environmentId, optimizing client-side interactions while maintaining data privacy.",
};
#### Management API
# People API
This set of API can be used to
- [List People](#list-people)
- [Get Person](#get-person)
- [Delete Person](#delete-person)
<Note>You will need an API Key to interact with these APIs.</Note>
---
## List People {{ tag: 'GET', label: '/api/v1/management/people' }}
<Row>
<Col>
List People
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/people">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/people' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": [
{
"id": "b4wgrzl363dn3zb6yy5gf265",
"attributes": {
"userId": "CYO618",
"email": "sophia@amazon.com",
"Name": "Sophia Johnson",
"Role": "Designer",
"Company": "Amazon",
"Experience": "7 years",
"Usage Frequency": "Yearly",
"Company Size": "1628 employees",
"Product Satisfaction Score": "62",
"Recommendation Likelihood": "9"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
},
{
"id": "jrb5iyzqvnkg9322ckhde3j4",
"attributes": {
"userId": "CYO511",
"email": "antonio@ibm.com",
"Name": "Antonio García",
"Role": "Designer",
"Company": "IBM",
"Experience": "1 years",
"Usage Frequency": "Weekly",
"Company Size": "4023 employees",
"Product Satisfaction Score": "77",
"Recommendation Likelihood": "4"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
},
]
}
```
```json {{ title: '400 Bad Request' }}
{
"code": "bad_request",
"message": "Fields are missing or incorrectly formatted",
"details": {
"userId": ""
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Get Person {{ tag: 'GET', label: '/api/v1/management/people/<person-id>' }}
<Row>
<Col>
Get Person by ID
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/people/<person-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/people/<person-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "jrb5iyzqvnkg9322ckhde3j4",
"attributes": {
"userId": "CYO511",
"email": "antonio@ibm.com",
"Name": "Antonio García",
"Role": "Designer",
"Company": "IBM",
"Experience": "1 years",
"Usage Frequency": "Weekly",
"Company Size": "4023 employees",
"Product Satisfaction Score": "77",
"Recommendation Likelihood": "4"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
}
}
```
```json {{ title: '404 Not Found' }}
{
"code": "not_found",
"message": "Person not found",
"details": {
"resource_id": "clmlmykc2000019vz5o3jglsa",
"resource_type": "Person"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Delete Person {{ tag: 'DELETE', label: '/api/v1/management/people/<person-id>' }}
<Row>
<Col>
Delete Person by ID
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/management/people/<person-id>">
```bash {{ title: 'cURL' }}
curl -X DELETE https://app.formbricks.com/api/v1/management/people/<person-id> \
--header 'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"success": "Person deleted successfully"
}
}
```
```json {{ title: '404 Not Found' }}
{
"code": "not_found",
"message": "Person not found",
"details": {
"resource_id": "clmlmykc2000019vz5o3jglsa",
"resource_type": "Person"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -0,0 +1,289 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks Responses API Documentation - Manage Your Survey Data Seamlessly",
description:
"Unlock the full potential of Formbricks' Responses API. From fetching to updating survey responses, our comprehensive guide helps you integrate and manage survey data efficiently without compromising security. Ideal for client-side interactions.",
};
#### Management API
# Responses API
This set of API can be used to
- [List Responses](#list-all-responses)
- [Get Response](#get-response-by-id)
- [Delete Response](#delete-a-response)
<Note>You will need an API Key to interact with these APIs.</Note>
---
## List all Responses {{ tag: 'GET', label: '/api/v1/management/responses' }}
<Row>
<Col>
Retrieve all the responses you have received in your environment.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/responses">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/responses' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data":[
{
"id": "cln8k0tqv00pcz87no4qrw333",
"createdAt": "2023-10-02T07:13:20.023Z",
"updatedAt": "2023-10-02T07:13:20.023Z",
"surveyId": "cln8k0tqu00p7z87nqr4thi3k",
"finished": true,
"data": {
"interview-prompt": "clicked"
},
"meta": {
"userAgent": {
"os": "MacOS",
"browser": "Chrome"
}
},
"personAttributes": null,
"person": {
"id": "e0x4i5tvsp8puxfztyrwykvn",
"attributes": {
"userId": "CYO675",
"email": "ravi@netflix.com",
"Name": "Ravi Kumar",
"Role": "Manager",
"Company": "Netflix",
"Experience": "6 years",
"Usage Frequency": "Monthly",
"Company Size": "4610 employees",
"Product Satisfaction Score": "43",
"Recommendation Likelihood": "4"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
},
"notes": [],
"tags": []
},
]
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Get Response by ID {{ tag: 'GET', label: '/api/v1/management/responses/<response-id>' }}
<Row>
<Col>
Retrieve a response by its ID.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/responses/<response-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/responses/<response-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data":
{
"id": "cln8k0tqv00pbz87nwo5lr72b",
"createdAt": "2023-10-02T07:13:20.023Z",
"updatedAt": "2023-10-02T07:13:20.023Z",
"surveyId": "cln8k0tqu00p7z87nqr4thi3k",
"finished": true,
"data": {
"interview-prompt": "clicked"
},
"meta": {
"userAgent": {
"os": "Windows",
"browser": "Edge"
}
},
"personAttributes": null,
"person": {
"id": "hsx38f15v50ua8383uadagq5",
"attributes": {
"userId": "CYO278",
"email": "jorge@facebook.com",
"Name": "Jorge Sanchez",
"Role": "Product Manager",
"Company": "Facebook",
"Experience": "10 years",
"Usage Frequency": "Daily",
"Company Size": "1685 employees",
"Product Satisfaction Score": "84",
"Recommendation Likelihood": "6"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
},
"notes": [],
"tags": []
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Delete a response {{ tag: 'DELETE', label: '/api/v1/client/responses/<response-id>' }}
<Row>
<Col>
Delete Response by ID
### Mandatory Headers
<Properties>
<Property name="x-api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/client/responses/<response-id>">
```bash {{ title: 'cURL' }}
curl -X DELETE https://app.formbricks.com/api/v1/management/resposnes/<response-id> \
--header 'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{ title: '200 Success' }}
{
"data": {
"id": "cln8k0tqv00pbz87nwo5lr72b",
"createdAt": "2023-10-02T07:13:20.023Z",
"updatedAt": "2023-10-02T07:13:20.023Z",
"surveyId": "cln8k0tqu00p7z87nqr4thi3k",
"finished": true,
"data": {
"interview-prompt": "clicked"
},
"meta": {
"userAgent": {
"os": "Windows",
"browser": "Edge"
}
},
"personAttributes": null,
"person": {
"id": "hsx38f15v50ua8383uadagq5",
"attributes": {
"userId": "CYO278",
"email": "jorge@facebook.com",
"Name": "Jorge Sanchez",
"Role": "Product Manager",
"Company": "Facebook",
"Experience": "10 years",
"Usage Frequency": "Daily",
"Company Size": "1685 employees",
"Product Satisfaction Score": "84",
"Recommendation Likelihood": "6"
},
"environmentId": "cln8k0t47000fz87njmmu2bck",
"createdAt": "2023-10-02T07:13:19.444Z",
"updatedAt": "2023-10-02T07:13:19.444Z"
},
"notes": [],
"tags": []
}
}
```
```json {{ title: '400 Bad Request' }}
{
"code": "bad_request",
"message": "surveyId was not provided.",
"details": {
"surveyId": "This field is required."
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -0,0 +1,667 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks Surveys API Documentation - How to Retrieve All Surveys",
description:
"Explore the comprehensive guide to the Formbricks Surveys API. Learn how to effectively retrieve all the surveys in your environment with the necessary headers and API key setup. Includes sample request and response formats.",
};
#### Management API
# Surveys API
This set of API can be used to
- [List All Surveys](#list-all-surveys)
- [Get Survey](#get-survey-by-id)
- [Create Survey](#create-survey)
- [Delete Survey](#delete-survey-by-id)
<Note>You will need an API Key to interact with these APIs.</Note>
---
## List all surveys {{ tag: 'GET', label: '/api/v1/management/surveys' }}
<Row>
<Col>
Retrieve all the surveys you have for the environment.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/surveys">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/surveys' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": [
{
"id": "cllnfy2780fromy0hy7uoxvtn",
"createdAt": "2023-08-23T07:56:20.516Z",
"updatedAt": "2023-08-23T07:56:26.947Z",
"name": "Product Market Fit (Superhuman)",
"type": "link",
"environmentId": "cll2m30r70004mx0huqkitgqv",
"status": "inProgress",
"attributeFilters": [],
"displayOption": "displayOnce",
"autoClose": null,
"triggers": [],
"redirectUrl": null,
"recontactDays": null,
"questions": [
{
"id": "gml6mgy71efgtq8np3s9je5p",
"type": "cta",
"headline": "You are one of our power users! Do you have 5 minutes?",
"required": false,
"buttonLabel": "Happy to help!",
"logic": [
{
"condition": "skipped",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We would love to understand your user experience better. Sharing your insight helps a lot!</span></p>",
"buttonExternal": false,
"dismissButtonLabel": "No, thanks."
},
{
"id": "kp62fbqe8cfzmvy8qwpr81b2",
"type": "multipleChoiceSingle",
"headline": "How disappointed would you be if you could no longer use My Product?",
"subheader": "Please select one of the following options:",
"required": true,
"choices": [
{
"id": "bdgy1hnwd7uwmfxk1ljqp1n5",
"label": "Not at all disappointed"
},
{
"id": "poabnvgtwenp8rb2v70gj4hj",
"label": "Somewhat disappointed"
},
{
"id": "opfiqyqz8wrqn0i0f7t24d3n",
"label": "Very disappointed"
}
],
"shuffleOption": "none"
},
{
"id": "klvpwd4x08x8quesihvw5l92",
"type": "multipleChoiceSingle",
"headline": "What is your role?",
"subheader": "Please select one of the following options:",
"required": true,
"choices": [
{
"id": "c8nerw6l9gpsxcmqkn10f9hy",
"label": "Founder"
},
{
"id": "ebjqezei6a2axtuq86cleetn",
"label": "Executive"
},
{
"id": "ctiijjblyhlp22snypfamqt1",
"label": "Product Manager"
},
{
"id": "ibalyr0mhemfkkr82vypmg40",
"label": "Product Owner"
},
{
"id": "fipk606aegslbd0e7yhc0xjx",
"label": "Software Engineer"
}
],
"shuffleOption": "none"
},
{
"id": "ryo75306flyg72iaeditbv51",
"type": "openText",
"headline": "What type of people do you think would most benefit from My Product?",
"required": true
},
{
"id": "lkjaxb73ulydzeumhd51sx9g",
"type": "openText",
"headline": "What is the main benefit your receive from My Product?",
"required": true
},
{
"id": "ec7agikkr58j8uonhioinkyk",
"type": "openText",
"headline": "How can we improve My Product for you?",
"subheader": "Please be as specific as possible.",
"required": true
}
],
"thankYouCard": {
"enabled": true,
"headline": "Thank you!",
"subheader": "We appreciate your feedback."
},
"delay": 0,
"autoComplete": null,
"closeOnDate": null
}
]
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Get Survey by ID {{ tag: 'GET', label: '/api/v1/management/surveys/<survey-id>' }}
<Row>
<Col>
Get a specific survey by its ID.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/surveys/<survey-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/surveys/<survey-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln8k0tjz00n5z87nwq527h3z",
"createdAt": "2023-10-02T07:13:19.775Z",
"updatedAt": "2023-10-02T07:13:19.775Z",
"name": "Churn Survey",
"type": "link",
"environmentId": "cln8k0t47000fz87njmmu2bck",
"status": "inProgress",
"attributeFilters": [],
"displayOption": "displayOnce",
"autoClose": null,
"triggers": [],
"redirectUrl": null,
"recontactDays": null,
"questions": [
{
"id": "churn-reason",
"type": "multipleChoiceSingle",
"headline": "Why did you cancel your subscription?",
"subheader": "We're sorry to see you leave. Help us do better:",
"required": true,
"logic": [
{
"condition": "equals",
"value": "Difficult to use",
"destination": "easier-to-use"
},
{
"condition": "equals",
"value": "It's too expensive",
"destination": "30-off"
},
{
"condition": "equals",
"value": "I am missing features",
"destination": "missing-features"
},
{
"condition": "equals",
"value": "Poor customer service",
"destination": "poor-service"
},
{
"condition": "equals",
"value": "I just didn't need it anymore",
"destination": "end"
}
],
"choices": [
{
"id": "isud2xethsw63dlwl89kr4kj",
"label": "Difficult to use"
},
{
"id": "opuu4ba3dlele3n0gjkuh27c",
"label": "It's too expensive"
},
{
"id": "gnypapo0rhvkt8pwosrphvbl",
"label": "I am missing features"
},
{
"id": "wkgsrsrazd9kfunqhzjezx6t",
"label": "Poor customer service"
},
{
"id": "pykmgyyw74vg0gaeryj6bo4c",
"label": "I just didn't need it anymore"
}
]
},
{
"id": "easier-to-use",
"type": "openText",
"headline": "What would have made {{productName}} easier to use?",
"subheader": "",
"required": true,
"buttonLabel": "Send",
"logic": [
{
"condition": "submitted",
"destination": "end"
}
]
},
{
"id": "30-off",
"type": "cta",
"headline": "Get 30% off for the next year!",
"required": true,
"buttonLabel": "Get 30% off",
"logic": [
{
"condition": "clicked",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We'd love to keep you as a customer. Happy to offer a 30% discount for the next year.</span></p>",
"buttonUrl": "https://formbricks.com",
"buttonExternal": true,
"dismissButtonLabel": "Skip"
},
{
"id": "missing-features",
"type": "openText",
"headline": "What features are you missing?",
"subheader": "",
"required": true,
"logic": [
{
"condition": "submitted",
"destination": "end"
}
]
},
{
"id": "poor-service",
"type": "cta",
"headline": "So sorry to hear 😔 Talk to our CEO directly!",
"required": true,
"buttonLabel": "Send email to CEO",
"logic": [
{
"condition": "clicked",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.</span></p>",
"buttonUrl": "mailto:ceo@company.com",
"buttonExternal": true,
"dismissButtonLabel": "Skip"
}
],
"thankYouCard": {
"enabled": false
},
"delay": 0,
"autoComplete": null,
"closeOnDate": null,
"surveyClosedMessage": null,
"verifyEmail": null
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Create Survey {{ tag: 'POST', label: '/api/v1/management/surveys' }}
<Row>
<Col>
Create a survey
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
### Body
<CodeGroup title="Request Body">
```json {{ title: 'cURL' }}
{
"environmentId": "clmlmwdqq0003196ufewo6ibg",
"type": "link",
"name": "My new Survey"
}
```
</CodeGroup>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/api/v1/management/surveys">
```bash {{ title: 'cURL' }}
curl -X DELETE \
'https://app.formbricks.com/api/v1/management/surveys' \
--header \
'x-api-key: <your-api-key>'
```
```bash {{ title: 'cURL' }}
curl -X POST https://app.formbricks.com/api/v1/management/surveys/ \
--header 'Content-Type: application/json' \
--header 'x-api-key: <your-api-key>' \
-d '{"environmentId": "cln8k0t47000fz87njmmu2bck", "name": "My Survey from API", "type": "link"}'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "clna6bqnz000az8zubq3e757t",
"createdAt": "2023-10-03T10:25:26.975Z",
"updatedAt": "2023-10-03T10:25:26.975Z",
"name": "My new Survey",
"redirectUrl": null,
"type": "link",
"environmentId": "cln8k0t47000fz87njmmu2bck",
"status": "draft",
"questions": [],
"thankYouCard": {
"enabled": false
},
"displayOption": "displayOnce",
"recontactDays": null,
"autoClose": null,
"delay": 0,
"autoComplete": null,
"closeOnDate": null,
"surveyClosedMessage": null,
"verifyEmail": null
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---
## Delete Survey by ID {{ tag: 'DELETE', label: '/api/v1/management/surveys/<survey-id>' }}
<Row>
<Col>
Delete a survey by its ID.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/management/surveys/<survey-id>">
```bash {{ title: 'cURL' }}
curl -X DELETE \
'https://app.formbricks.com/api/v1/management/surveys/<survey-id>' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"id": "cln8k0tjz00n5z87nwq527h3z",
"createdAt": "2023-10-02T07:13:19.775Z",
"updatedAt": "2023-10-02T07:13:19.775Z",
"name": "Churn Survey",
"type": "link",
"environmentId": "cln8k0t47000fz87njmmu2bck",
"status": "inProgress",
"attributeFilters": [],
"displayOption": "displayOnce",
"autoClose": null,
"triggers": [],
"redirectUrl": null,
"recontactDays": null,
"questions": [
{
"id": "churn-reason",
"type": "multipleChoiceSingle",
"headline": "Why did you cancel your subscription?",
"subheader": "We're sorry to see you leave. Help us do better:",
"required": true,
"logic": [
{
"condition": "equals",
"value": "Difficult to use",
"destination": "easier-to-use"
},
{
"condition": "equals",
"value": "It's too expensive",
"destination": "30-off"
},
{
"condition": "equals",
"value": "I am missing features",
"destination": "missing-features"
},
{
"condition": "equals",
"value": "Poor customer service",
"destination": "poor-service"
},
{
"condition": "equals",
"value": "I just didn't need it anymore",
"destination": "end"
}
],
"choices": [
{
"id": "isud2xethsw63dlwl89kr4kj",
"label": "Difficult to use"
},
{
"id": "opuu4ba3dlele3n0gjkuh27c",
"label": "It's too expensive"
},
{
"id": "gnypapo0rhvkt8pwosrphvbl",
"label": "I am missing features"
},
{
"id": "wkgsrsrazd9kfunqhzjezx6t",
"label": "Poor customer service"
},
{
"id": "pykmgyyw74vg0gaeryj6bo4c",
"label": "I just didn't need it anymore"
}
]
},
{
"id": "easier-to-use",
"type": "openText",
"headline": "What would have made {{productName}} easier to use?",
"subheader": "",
"required": true,
"buttonLabel": "Send",
"logic": [
{
"condition": "submitted",
"destination": "end"
}
]
},
{
"id": "30-off",
"type": "cta",
"headline": "Get 30% off for the next year!",
"required": true,
"buttonLabel": "Get 30% off",
"logic": [
{
"condition": "clicked",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We'd love to keep you as a customer. Happy to offer a 30% discount for the next year.</span></p>",
"buttonUrl": "https://formbricks.com",
"buttonExternal": true,
"dismissButtonLabel": "Skip"
},
{
"id": "missing-features",
"type": "openText",
"headline": "What features are you missing?",
"subheader": "",
"required": true,
"logic": [
{
"condition": "submitted",
"destination": "end"
}
]
},
{
"id": "poor-service",
"type": "cta",
"headline": "So sorry to hear 😔 Talk to our CEO directly!",
"required": true,
"buttonLabel": "Send email to CEO",
"logic": [
{
"condition": "clicked",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.</span></p>",
"buttonUrl": "mailto:ceo@company.com",
"buttonExternal": true,
"dismissButtonLabel": "Skip"
}
],
"thankYouCard": {
"enabled": false
},
"delay": 0,
"autoComplete": null,
"closeOnDate": null,
"surveyClosedMessage": null,
"verifyEmail": null
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -1,30 +1,31 @@
export const meta = {
title: "Formbricks Webhook API Documentation - List, Retrieve, Create, and Delete Webhooks",
description: "Explore the comprehensive guide to the Formbricks Webhooks API. This is all you need to interact and play with the Formbricks Webhooks and integrate them into any third party app of your choice",
description:
"Explore the comprehensive guide to the Formbricks Webhooks API. This is all you need to interact and play with the Formbricks Webhooks and integrate them into any third party app of your choice",
};
#### Webhook API
#### Management API
# Webhook API
Formbricks' Webhook API offers a powerful interface for interacting with webhooks. Webhooks in Formbricks allow you to receive real-time HTTP notifications of changes to specific objects in the Formbricks environment.
Before you start managing webhooks, you need to create an API key. This will be used for authorization when making requests to the Webhook API. Please see the [API key setup page](/docs/api/api-key-setup) for more details on how to create and manage your API keys.
Formbricks' Webhook API offers a powerful interface for interacting with webhooks. Webhooks allow you to receive real-time HTTP notifications of changes to specific objects in the Formbricks environment.
The behavior of the webhooks is determined by their trigger settings. The trigger determines which updates the webhook sends. Current available triggers include "responseCreated", "responseUpdated", and "responseFinished". This allows you to customize your webhooks to only send notifications for the events that are relevant to your application.
Webhooks are tied to a specific Formbricks environment. Once set, a webhook will receive updates from all surveys within this environment. This makes it easy to manage your data flow and ensure that all relevant updates are caught by the webhook.
Our API has several REST endpoints enabling you to manage these webhooks, providing a great deal of flexibility:
This set of API can be used to
- [List All Webhooks](#list-webhooks)
- [Get Webhook](#retrieve-webhook-by-id)
- [Create Webhook](#create-webhook)
- [Delete Webhook](#delete-webhook-by-id)
1. **List Webhooks:** Retrieve a list of all existing webhooks.
1. **Retrieve Webhook by ID:** Retrieve a specific webhook by it's ID.
1. **Create a New Webhook:** Add a new webhook to your system.
1. **Get a Specific Webhook:** Query the details of a specific webhook using its unique ID.
1. **Delete a Webhook:** Remove an existing webhook.
And the detailed Webhook Paylod is elaborated [here](#webhook-payload).
These APIs are designed to facilitate seamless integration of Formbricks with third-party systems. By making use of our webhook API, you can automate the process of sending data to these systems whenever significant events occur within your Formbricks environment.
<Note>You will need an API Key to interact with these APIs.</Note>
---
## List Webhooks {{ tag: 'GET', label: '/api/v1/webhooks' }}
@@ -92,7 +93,7 @@ These APIs are designed to facilitate seamless integration of Formbricks with th
---
## Retrieve Webhook by ID {{ tag: 'GET', label: '/api/v1/webhooks/[webhookId]' }}
## Retrieve Webhook by ID {{ tag: 'GET', label: '/api/v1/webhooks/<webhook-id>' }}
<Row>
<Col>
@@ -108,11 +109,11 @@ These APIs are designed to facilitate seamless integration of Formbricks with th
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/webhooks/[webhookId]">
<CodeGroup title="Request" tag="GET" label="/api/v1/webhooks/<webhook-id>">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/webhooks/[webhookId]' \
'https://app.formbricks.com/api/v1/webhooks/<webhook-id>' \
--header \
'x-api-key: <your-api-key>'
```
@@ -255,7 +256,7 @@ Add a webhook to your product.
---
## Delete Webhook by ID {{ tag: 'DELETE', label: '/api/v1/webhooks/[webhookId]' }}
## Delete Webhook by ID {{ tag: 'DELETE', label: '/api/v1/webhooks/<webhook-id>' }}
<Row>
<Col>
@@ -271,10 +272,10 @@ Add a webhook to your product.
</Col>
<Col sticky>
<CodeGroup title="Request" tag="DELETE" label="/api/v1/webhooks/[webhookId]">
<CodeGroup title="Request" tag="DELETE" label="/api/v1/webhooks/<webhook-id>">
```bash {{ title: 'cURL' }}
curl --location --request DELETE 'https://app.formbricks.com/api/v1/webhooks/[webhookId]' \
curl --location --request DELETE 'https://app.formbricks.com/api/v1/webhooks/<webhook-id>' \
--header 'x-api-key: <your-api-key>'
```

View File

@@ -1,76 +0,0 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks People API: Fetch or Create Person Overview",
description:
"Dive into Formbricks' People API within the Public Client API suite, designed to work without authentication requirements. Seamlessly fetch or create a person by their userId and environmentId, optimizing client-side interactions while maintaining data privacy.",
};
#### Management API
# People API
The Public Client API is designed for the JavaScript SDK and does not require authentication. It's primarily used for creating persons, sessions, and responses within the Formbricks platform. This API is ideal for client-side interactions, as it doesn't expose sensitive information.
---
## Get or Create Person {{ tag: 'GET', label: '/api/v1/client/people/getOrCreate' }}
<Row>
<Col>
Fetch a Person or create (if they don't exist) by their userId and the environmentId.
### Mandatory Query Parameters
<Properties>
<Property name="userId" type="string">
User ID to fetch or create (if it does not exist)
</Property>
</Properties>
<Properties>
<Property name="enviornmentId" type="string">
Formbricks Environment ID
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/client/people/getOrCreate">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/client/people/getOrCreate?userId=<user-id>&environmentId=<env-id>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": {
"person": {
"id": "clm4bcxms02hspj0ht05k6q5c",
"environmentId": "cll2m30r70004mx0huqkitgqv"
}
}
}
```
```json {{ title: '400 Bad Request' }}
{
"code": "bad_request",
"message": "Fields are missing or incorrectly formatted",
"details": {
"userId": ""
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -1,180 +0,0 @@
import { Fence } from "@/components/shared/Fence";
export const meta = {
title: "Formbricks Surveys API Documentation - How to Retrieve All Surveys",
description:
"Explore the comprehensive guide to the Formbricks Surveys API. Learn how to effectively retrieve all the surveys in your environment with the necessary headers and API key setup. Includes sample request and response formats.",
};
#### Management API
# Surveys API
The Survey API currently has one endpoint that allows you to get all the surveys you have in your Formbricks environment. You will need the [API Key](/docs/api/api-key-setup) to access the same!
---
## List all surveys {{ tag: 'GET', label: '/api/v1/management/surveys' }}
<Row>
<Col>
Retrieve all the surveys you have for the environment.
### Mandatory Headers
<Properties>
<Property name="x-Api-Key" type="string">
Your Formbricks API key.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/surveys">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/surveys' \
--header \
'x-api-key: <your-api-key>'
```
</CodeGroup>
<CodeGroup title="Response">
```json {{title:'200 Success'}}
{
"data": [
{
"id": "cllnfy2780fromy0hy7uoxvtn",
"createdAt": "2023-08-23T07:56:20.516Z",
"updatedAt": "2023-08-23T07:56:26.947Z",
"name": "Product Market Fit (Superhuman)",
"type": "link",
"environmentId": "cll2m30r70004mx0huqkitgqv",
"status": "inProgress",
"attributeFilters": [],
"displayOption": "displayOnce",
"autoClose": null,
"triggers": [],
"redirectUrl": null,
"recontactDays": null,
"questions": [
{
"id": "gml6mgy71efgtq8np3s9je5p",
"type": "cta",
"headline": "You are one of our power users! Do you have 5 minutes?",
"required": false,
"buttonLabel": "Happy to help!",
"logic": [
{
"condition": "skipped",
"destination": "end"
}
],
"html": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span>We would love to understand your user experience better. Sharing your insight helps a lot!</span></p>",
"buttonExternal": false,
"dismissButtonLabel": "No, thanks."
},
{
"id": "kp62fbqe8cfzmvy8qwpr81b2",
"type": "multipleChoiceSingle",
"headline": "How disappointed would you be if you could no longer use My Product?",
"subheader": "Please select one of the following options:",
"required": true,
"choices": [
{
"id": "bdgy1hnwd7uwmfxk1ljqp1n5",
"label": "Not at all disappointed"
},
{
"id": "poabnvgtwenp8rb2v70gj4hj",
"label": "Somewhat disappointed"
},
{
"id": "opfiqyqz8wrqn0i0f7t24d3n",
"label": "Very disappointed"
}
],
"shuffleOption": "none"
},
{
"id": "klvpwd4x08x8quesihvw5l92",
"type": "multipleChoiceSingle",
"headline": "What is your role?",
"subheader": "Please select one of the following options:",
"required": true,
"choices": [
{
"id": "c8nerw6l9gpsxcmqkn10f9hy",
"label": "Founder"
},
{
"id": "ebjqezei6a2axtuq86cleetn",
"label": "Executive"
},
{
"id": "ctiijjblyhlp22snypfamqt1",
"label": "Product Manager"
},
{
"id": "ibalyr0mhemfkkr82vypmg40",
"label": "Product Owner"
},
{
"id": "fipk606aegslbd0e7yhc0xjx",
"label": "Software Engineer"
}
],
"shuffleOption": "none"
},
{
"id": "ryo75306flyg72iaeditbv51",
"type": "openText",
"headline": "What type of people do you think would most benefit from My Product?",
"required": true
},
{
"id": "lkjaxb73ulydzeumhd51sx9g",
"type": "openText",
"headline": "What is the main benefit your receive from My Product?",
"required": true
},
{
"id": "ec7agikkr58j8uonhioinkyk",
"type": "openText",
"headline": "How can we improve My Product for you?",
"subheader": "Please be as specific as possible.",
"required": true
}
],
"thankYouCard": {
"enabled": true,
"headline": "Thank you!",
"subheader": "We appreciate your feedback."
},
"delay": 0,
"autoComplete": null,
"closeOnDate": null
}
]
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
"message": "Not authenticated",
"details": {
"x-Api-Key": "Header not provided or API Key invalid"
}
}
```
</CodeGroup>
</Col>
</Row>
---

View File

@@ -14,7 +14,7 @@ export const meta = {
# Troubleshooting
Here you'll find help with Frequently Recurring Problems
Here you'll find help with frequently recurring problems
## "The app doesn't work after doing a prisma migration"
@@ -24,7 +24,7 @@ This can happen but fear not, the fix is easy: Delete the application storage of
src={ClearAppData}
alt="Demo App Preview"
quality="100"
className="rounded-lg max-w-full sm:max-w-3xl"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
## "I ran 'pnpm i' but there seems to be an error with the packages"
@@ -74,9 +74,9 @@ However, in our experience it's better to run `pnpm dev` than having two termina
src={UncaughtPromise}
alt="Uncaught promise"
quality="100"
className="rounded-lg max-w-full sm:max-w-3xl"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
This happens when you're using the Demo App and delete the Person within the Formbricks app which the widget is currently connected with. We're fixing it, but you can also just logout your test person and reload the page to get rid of it.
<Image src={Logout} alt="Logout Person" quality="100" className="rounded-lg max-w-full sm:max-w-3xl" />
<Image src={Logout} alt="Logout Person" quality="100" className="max-w-full rounded-lg sm:max-w-3xl" />

View File

@@ -0,0 +1,14 @@
import FAQ from "@/components/docs/docsFaq";
export const meta = {
title: "FAQ",
description: "Frequently Asked Questions about Formbricks and how to use it.",
};
#### FAQ
# Frequently Asked Questions
Here you'll find help with frequently recurring problems. If you can't find an answer to your question, please join our [Discord server](https://formbricks.com/discord).
<FAQ />

View File

@@ -45,5 +45,3 @@ Natively embed qualitative user research into your B2B SaaS. Leverage Best Pract
</div>
<GettingStarted />
<BestPractices />

View File

@@ -42,16 +42,6 @@ The `userId` we are refering to is the `userId` of your own system. For example,
This allows you to connect the response to the user profile of this specific in the Formbricks database. You can then use the response data to create segments for further surveying or invite them to an interview, etc.
## getOrCreateUser - how it works exactly
By default, respondents of link surveys are **not** recorded and displayed in the People view. This would lead to your People view to be spammend with unidentified people.
If you add the `userId` URL parameter a Person will be created, if there is no existing person in your database with a matching `userId`.
**Case 1:** User with userId `ABC111` exists, the response from the link survey will be added to this users profile.
**Case 2:** User with userId `ABC222` does not yet exists, so it is created with the response from the link survey connected to this user.
<Image
src={PeopleView}
alt="If users are identified by email, it will show. If not, the internal Id will be set."

View File

@@ -258,18 +258,27 @@ export const navigation: Array<NavGroup> = [
{ title: "Gitpod", href: "/docs/contributing/gitpod" },
{ title: "Demo App", href: "/docs/contributing/demo" },
{ title: "Troubleshooting", href: "/docs/contributing/troubleshooting" },
{ title: "FAQ", href: "/docs/faq" },
],
},
{
title: "API",
title: "Client API",
links: [
{ title: "Overview", href: "/docs/api/overview" },
{ title: "API Key Setup", href: "/docs/api/api-key-setup" },
{ title: "Displays", href: "/docs/api/display" },
{ title: "People", href: "/docs/api/people" },
{ title: "Responses", href: "/docs/api/responses" },
{ title: "Surveys", href: "/docs/api/surveys" },
{ title: "Webhook", href: "/docs/api/webhooks" },
{ title: "Overview", href: "/docs/api/client/overview" },
{ title: "Displays", href: "/docs/api/client/displays" },
{ title: "Responses", href: "/docs/api/client/responses" },
],
},
{
title: "Management API",
links: [
{ title: "API Key Setup", href: "/docs/api/management/api-key-setup" },
{ title: "Action Classes", href: "/docs/api/management/action-classes" },
{ title: "Attribute Classes", href: "/docs/api/management/attribute-classes" },
{ title: "Me", href: "/docs/api/management/me" },
{ title: "People", href: "/docs/api/management/people" },
{ title: "Surveys", href: "/docs/api/management/surveys" },
{ title: "Webhooks", href: "/docs/api/management/webhooks" },
],
},
];

View File

@@ -0,0 +1,69 @@
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui";
import FaqJsonLdComponent from "./faQJsonLD";
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="/docs/self-hosting/deployment" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
},
];
export const faqJsonLdData = FAQ_DATA.map((faq) => ({
questionName: faq.question,
acceptedAnswerText: faq.answer(),
}));
export default function 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>
</>
);
}

View File

@@ -0,0 +1,12 @@
"use client";
import { FAQPageJsonLd } from "next-seo";
export default function FaqJsonLdComponent({ data }) {
const faqEntities = data.map(({ question, answer }) => ({
questionName: question,
acceptedAnswerText: answer,
}));
return <FAQPageJsonLd mainEntity={faqEntities} />;
}

View File

@@ -0,0 +1,87 @@
import { FAQPageJsonLd } from "next-seo";
import HeadingCentered from "@/components/shared/HeadingCentered";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui";
const FAQ_DATA = [
{
question: "What is Formbricks?",
answer: () => (
<>
Formbricks is an open-source Experience Management tool that helps businesses understand what
customers think and feel about their products. It integrates natively into your platform to conduct
user research with a focus on data privacy and minimal development intervention.
</>
),
},
{
question: "How do I integrate Formbricks into my application?",
answer: () => (
<>
Integrating Formbricks is a breeze. Simply copy a script tag to your HTML head, or use NPM to install
Formbricks for platforms like React, Vue, Svelte, etc. Once installed, initialize Formbricks with your
environment details. Learn more with our framework guides{" "}
<a href="/docs/getting-started/framework-guides" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
},
{
question: "Is Formbricks GDPR compliant?",
answer: () => (
<>
Yes, Formbricks is fully GDPR compliant. Whether you use our cloud solution or decide to self-host, we
ensure compliance with all data privacy regulations.
</>
),
},
{
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="/docs/self-hosting/deployment" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
},
{
question: "How does Formbricks pricing work?",
answer: () => (
<>
Formbricks offers a Free forever plan on the cloud that includes unlimited surveys, in-product
surveys, and more. We also provide a self-hosting option which includes all free features and more,
available at no cost. If you require additional features or responses, check out our pricing section
above for more details.
</>
),
},
];
const faqJsonLdData = FAQ_DATA.map((faq) => ({
questionName: faq.question,
acceptedAnswerText: faq.answer(),
}));
export default function FAQ() {
return (
<div className="max-w-7xl py-4 sm:px-6 sm:pb-6 lg:px-8" id="faq">
<FAQPageJsonLd mainEntity={faqJsonLdData} />
<HeadingCentered heading="Frequently Asked Questions" teaser="FAQ" closer />
<Accordion type="single" collapsible className="px-4 sm:px-0">
{FAQ_DATA.map((faq, index) => (
<AccordionItem key={`item-${index}`} value={`item-${index + 1}`}>
<AccordionTrigger>{faq.question}</AccordionTrigger>
<AccordionContent>{faq.answer()}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
);
}

View File

@@ -299,8 +299,8 @@ const Leaderboard = [
points: "200",
},
{
name: "Arjun",
points: "100",
name: "Naitik Kapadia (Arjun)",
points: "200",
},
{
name: "Yashhhh",
@@ -332,7 +332,7 @@ const Leaderboard = [
},
{
name: "Eldemarkki",
points: "100",
points: "500",
},
{
name: "Suyash",
@@ -360,17 +360,57 @@ const Leaderboard = [
},
{
name: "Aditya Deshlahre",
points: "450",
points: "550",
link: "https://github.com/adityadeshlahre",
},
{
name: "Rutam",
points: "250",
points: "350",
},
{
name: "Sagnik Sahoo",
points: "100",
},
{
name: "Prasoon Mahawar",
points: "100",
},
{
name: "Dushmanta",
points: "100",
},
{
name: "Arjavv",
points: "100",
},
{
name: "Ashish Khare",
points: "100",
},
{
name: "Rohit Mondal",
points: "100",
},
{
name: "noobcoder",
points: "100",
},
{
name: "Rayyan Alam (Rayy)",
points: "100",
},
{
name: "Ayush",
points: "100",
},
{
name: "Zechariah",
points: "100",
},
{
name: "Rajarshi Misra",
points: "100",
},
];
export default function FormTribeHackathon() {

View File

@@ -1,11 +1,12 @@
import Layout from "@/components/shared/Layout";
import Hero from "@/components/home/Hero";
import Faq from "@/components/home/Faq";
import Features from "@/components/home/Features";
import Highlights from "@/components/home/Highlights";
import BreakerCTA from "@/components/shared/BreakerCTA";
import Steps from "@/components/home/Steps";
import GitHubSponsorship from "@/components/home/GitHubSponsorship";
import Hero from "@/components/home/Hero";
import Highlights from "@/components/home/Highlights";
import Steps from "@/components/home/Steps";
import BestPractices from "@/components/shared/BestPractices";
import BreakerCTA from "@/components/shared/BreakerCTA";
import Layout from "@/components/shared/Layout";
const IndexPage = () => (
<Layout
@@ -41,6 +42,8 @@ const IndexPage = () => (
href="https://app.formbricks.com/auth/signup"
inverted
/>
<Faq />
</Layout>
);

View File

@@ -12,7 +12,7 @@ import {
getActionCountInLast7Days,
getActionCountInLastHour,
} from "@formbricks/lib/services/actions";
import { getSurveysByActionClassId } from "@formbricks/lib/services/survey";
import { getSurveysByActionClassId } from "@formbricks/lib/survey/service";
import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function deleteActionClassAction(environmentId, actionClassId: string) {

View File

@@ -1,6 +1,6 @@
"use server";
import { getSurveysByAttributeClassId } from "@formbricks/lib/services/survey";
import { getSurveysByAttributeClassId } from "@formbricks/lib/survey/service";
export const GetActiveInactiveSurveysAction = async (
attributeClassId: string

View File

@@ -1,133 +1,45 @@
"use server";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { prisma } from "@formbricks/database";
import { ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { INTERNAL_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { deleteSurvey, getSurvey } from "@formbricks/lib/services/survey";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { deleteSurvey, getSurvey } from "@formbricks/lib/survey/service";
import { AuthorizationError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { Team } from "@prisma/client";
import { Prisma as prismaClient } from "@prisma/client/";
import { createProduct } from "@formbricks/lib/services/product";
import { getServerSession } from "next-auth";
export async function createTeam(teamName: string, ownerUserId: string): Promise<Team> {
const newTeam = await prisma.team.create({
data: {
name: teamName,
memberships: {
create: {
user: { connect: { id: ownerUserId } },
role: "owner",
accepted: true,
},
},
products: {
create: [
{
name: "My Product",
environments: {
create: [
{
type: "production",
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: "automatic",
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: "automatic",
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: "automatic",
},
],
},
attributeClasses: {
create: [
{
name: "userId",
description: "The internal ID of the person",
type: "automatic",
},
{
name: "email",
description: "The email of the person",
type: "automatic",
},
],
},
},
{
type: "development",
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: "automatic",
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: "automatic",
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: "automatic",
},
],
},
attributeClasses: {
create: [
{
name: "userId",
description: "The internal ID of the person",
type: "automatic",
},
{
name: "email",
description: "The email of the person",
type: "automatic",
},
],
},
},
],
},
},
],
},
},
include: {
memberships: true,
products: {
include: {
environments: true,
},
},
},
export async function createTeamAction(teamName: string): Promise<Team> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const newTeam = await createTeam({
name: teamName,
});
const teamId = newTeam?.id;
await createMembership(newTeam.id, session.user.id, {
role: "owner",
accepted: true,
});
if (teamId) {
fetch(`${WEBAPP_URL}/api/v1/teams/${teamId}/add_demo_product`, {
method: "POST",
headers: {
"x-api-key": INTERNAL_SECRET,
},
});
}
await createProduct(newTeam.id, {
name: "My Product",
});
return newTeam;
}
export async function duplicateSurveyAction(environmentId: string, surveyId: string) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const existingSurvey = await getSurvey(surveyId);
if (!existingSurvey) {
@@ -180,6 +92,24 @@ export async function copyToOtherEnvironmentAction(
surveyId: string,
targetEnvironmentId: string
) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorizedToAccessSourceEnvironment = await hasUserEnvironmentAccess(
session.user.id,
environmentId
);
if (!isAuthorizedToAccessSourceEnvironment) throw new AuthorizationError("Not authorized");
const isAuthorizedToAccessTargetEnvironment = await hasUserEnvironmentAccess(
session.user.id,
targetEnvironmentId
);
if (!isAuthorizedToAccessTargetEnvironment) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const existingSurvey = await prisma.survey.findFirst({
where: {
id: surveyId,
@@ -305,12 +235,32 @@ export async function copyToOtherEnvironmentAction(
}
export const deleteSurveyAction = async (surveyId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
await deleteSurvey(surveyId);
};
export const createProductAction = async (environmentId: string, productName: string) => {
const productCreated = await createProduct(environmentId, productName);
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const newEnvironment = productCreated.environments[0];
return newEnvironment;
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const team = await getTeamByEnvironmentId(environmentId);
if (!team) throw new ResourceNotFoundError("Team from environment", environmentId);
const product = await createProduct(team.id, {
name: productName,
});
// get production environment
const productionEnvironment = product.environments.find((environment) => environment.type === "production");
if (!productionEnvironment) throw new ResourceNotFoundError("Production environment", environmentId);
return productionEnvironment;
};

View File

@@ -2,7 +2,7 @@ import GoogleSheetWrapper from "@/app/(app)/environments/[environmentId]/integra
import GoBackButton from "@/components/shared/GoBackButton";
import { getSpreadSheets } from "@formbricks/lib/services/googleSheet";
import { getIntegrations } from "@formbricks/lib/services/integrations";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TGoogleSpreadsheet } from "@formbricks/types/v1/integrations";
import {
GOOGLE_SHEETS_CLIENT_ID,

View File

@@ -4,7 +4,7 @@ import WebhookRowData from "@/app/(app)/environments/[environmentId]/integration
import WebhookTable from "@/app/(app)/environments/[environmentId]/integrations/webhooks/WebhookTable";
import WebhookTableHeading from "@/app/(app)/environments/[environmentId]/integrations/webhooks/WebhookTableHeading";
import GoBackButton from "@/components/shared/GoBackButton";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import { getWebhooks } from "@formbricks/lib/services/webhook";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";

View File

@@ -1,6 +1,6 @@
import ResponseTimeline from "@/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseTimeline";
import { getSurveys } from "@formbricks/lib/survey/service";
import { getResponsesByPersonId } from "@formbricks/lib/response/service";
import { getSurveys } from "@formbricks/lib/services/survey";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TResponseWithSurvey } from "@formbricks/types/v1/responses";
import { TSurvey } from "@formbricks/types/v1/surveys";

View File

@@ -1,6 +1,6 @@
"use client";
import { updateTeamAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions";
import { updateTeamNameAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions";
import { TTeam } from "@formbricks/types/v1/teams";
import { Button, Input, Label } from "@formbricks/ui";
import { useRouter } from "next/navigation";
@@ -43,7 +43,7 @@ export default function EditTeamName({ team }: TEditTeamNameProps) {
const handleUpdateTeamName: SubmitHandler<TEditTeamNameForm> = async (data) => {
try {
setIsUpdatingTeam(true);
await updateTeamAction(team.id, data);
await updateTeamNameAction(team.id, data.name);
setIsUpdatingTeam(false);
toast.success("Team name updated successfully.");

View File

@@ -20,13 +20,22 @@ import {
import { deleteTeam, updateTeam } from "@formbricks/lib/services/team";
import { TInviteUpdateInput } from "@formbricks/types/v1/invites";
import { TMembershipRole, TMembershipUpdateInput } from "@formbricks/types/v1/memberships";
import { TTeamUpdateInput } from "@formbricks/types/v1/teams";
import { getServerSession } from "next-auth";
import { hasTeamAccess, hasTeamAuthority, hasTeamOwnership, isOwner } from "@formbricks/lib/auth";
import { INVITE_DISABLED } from "@formbricks/lib/constants";
export const updateTeamAction = async (teamId: string, data: TTeamUpdateInput) => {
return await updateTeam(teamId, data);
export const updateTeamNameAction = async (teamId: string, teamName: string) => {
const session = await getServerSession(authOptions);
if (!session) {
throw new AuthenticationError("Not authenticated");
}
const isUserAuthorized = await hasTeamAuthority(session.user.id, teamId);
if (!isUserAuthorized) {
throw new AuthenticationError("Not authorized");
}
return await updateTeam(teamId, { name: teamName });
};
export const updateMembershipAction = async (

View File

@@ -5,7 +5,7 @@ import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { SURVEY_BASE_URL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurveys } from "@formbricks/lib/services/survey";
import { getSurveys } from "@formbricks/lib/survey/service";
import type { TEnvironment } from "@formbricks/types/v1/environment";
import { Badge } from "@formbricks/ui";
import { ComputerDesktopIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/solid";

View File

@@ -4,6 +4,7 @@ import TemplateList from "@/app/(app)/environments/[environmentId]/surveys/templ
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import type { TEnvironment } from "@formbricks/types/v1/environment";
import type { TProduct } from "@formbricks/types/v1/product";
import { TSurveyInput } from "@formbricks/types/v1/surveys";
import { TTemplate } from "@formbricks/types/v1/templates";
import { useRouter } from "next/navigation";
import { useState } from "react";
@@ -28,7 +29,7 @@ export default function SurveyStarter({
...template.preset,
type: surveyType,
autoComplete,
};
} as Partial<TSurveyInput>;
try {
const survey = await createSurveyAction(environmentId, augmentedTemplate);
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);

View File

@@ -1,7 +1,7 @@
import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getSurveyResponses } from "@formbricks/lib/response/service";
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
export const getAnalysisData = async (surveyId: string, environmentId: string) => {

View File

@@ -30,7 +30,12 @@ export default async function Page({ params }) {
getEnvironment(params.environmentId),
]);
const isSingleUseSurvey = survey.singleUse?.enabled ?? false;
const singleUseIds = generateSingleUseIds(survey.singleUse?.isEncrypted ?? false);
let singleUseIds: string[] | undefined = undefined;
if (isSingleUseSurvey) {
singleUseIds = generateSingleUseIds(survey.singleUse?.isEncrypted ?? false);
}
if (!environment) {
throw new Error("Environment not found");
}

View File

@@ -23,7 +23,7 @@ import LinkSurveyShareButton from "@/app/(app)/environments/[environmentId]/surv
import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TProduct } from "@formbricks/types/v1/product";
import { surveyMutateAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { updateSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
interface SummaryHeaderProps {
surveyId: string;
@@ -114,7 +114,7 @@ const SummaryHeader = ({
value={survey.status}
onValueChange={(value) => {
const castedValue = value as "draft" | "inProgress" | "paused" | "completed";
surveyMutateAction({ ...survey, status: castedValue })
updateSurveyAction({ ...survey, status: castedValue })
.then(() => {
toast.success(
value === "inProgress"

View File

@@ -14,7 +14,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { validateQuestion } from "./Validation";
import { deleteSurveyAction, surveyMutateAction } from "./actions";
import { deleteSurveyAction, updateSurveyAction } from "./actions";
interface SurveyMenuBarProps {
localSurvey: TSurveyWithAnalytics;
@@ -143,7 +143,7 @@ export default function SurveyMenuBar({
}
try {
await surveyMutateAction({ ...strippedSurvey });
await updateSurveyAction({ ...strippedSurvey });
router.refresh();
setIsMutatingSurvey(false);
toast.success("Changes saved.");
@@ -242,7 +242,7 @@ export default function SurveyMenuBar({
if (!validateSurvey(localSurvey)) {
return;
}
await surveyMutateAction({ ...localSurvey, status: "inProgress" });
await updateSurveyAction({ ...localSurvey, status: "inProgress" });
router.refresh();
setIsMutatingSurvey(false);
router.push(`/environments/${environment.id}/surveys/${localSurvey.id}/summary?success=true`);

View File

@@ -1,12 +1,28 @@
"use server";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { deleteSurvey, updateSurvey } from "@formbricks/lib/services/survey";
import { deleteSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { AuthorizationError } from "@formbricks/types/v1/errors";
export async function updateSurveyAction(survey: TSurvey): Promise<TSurvey> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, survey.id);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function surveyMutateAction(survey: TSurvey): Promise<TSurvey> {
return await updateSurvey(survey);
}
export async function deleteSurveyAction(surveyId: string) {
export const deleteSurveyAction = async (surveyId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
await deleteSurvey(surveyId);
}
};

View File

@@ -2,7 +2,7 @@ export const revalidate = REVALIDATION_INTERVAL;
import React from "react";
import { FORMBRICKS_ENCRYPTION_KEY, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import SurveyEditor from "./SurveyEditor";
import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getActionClasses } from "@formbricks/lib/actionClass/service";

View File

@@ -1,7 +1,18 @@
"use server";
import { createSurvey } from "@formbricks/lib/services/survey";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createSurvey } from "@formbricks/lib/survey/service";
import { AuthorizationError } from "@formbricks/types/v1/errors";
import { TSurvey } from "@formbricks/types/v1/surveys";
export async function createSurveyAction(environmentId: string, surveyBody: Partial<TSurvey>) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function createSurveyAction(environmentId: string, surveyBody: any) {
return await createSurvey(environmentId, surveyBody);
}

View File

@@ -22,6 +22,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { createSurveyAction } from "./actions";
import { customSurvey, templates } from "./templates";
import { TSurveyInput } from "@formbricks/types/v1/surveys";
type TemplateList = {
environmentId: string;
@@ -77,7 +78,7 @@ export default function TemplateList({
...activeTemplate.preset,
type: surveyType,
autoComplete,
};
} as Partial<TSurveyInput>;
const survey = await createSurveyAction(environmentId, augmentedTemplate);
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);
};

View File

@@ -1,7 +1,18 @@
"use server";
import { createSurvey } from "@formbricks/lib/services/survey";
import { createSurvey } from "@formbricks/lib/survey/service";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { AuthorizationError } from "@formbricks/types/v1/errors";
import { TSurvey } from "@formbricks/types/v1/surveys";
export async function createSurveyAction(environmentId: string, surveyBody: Partial<TSurvey>) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
export async function createSurveyAction(environmentId: string, surveyBody: any) {
return await createSurvey(environmentId, surveyBody);
}

View File

@@ -41,7 +41,11 @@ const Objective: React.FC<ObjectiveProps> = ({ next, skip, formbricksResponseId,
if (selectedObjective) {
try {
setIsProfileUpdating(true);
const updatedProfile = { ...profile, objective: selectedObjective.id };
const updatedProfile = {
...profile,
objective: selectedObjective.id,
name: profile.name ?? undefined,
};
await updateProfileAction(updatedProfile);
setIsProfileUpdating(false);
} catch (e) {

View File

@@ -1,23 +1,25 @@
export const revalidate = REVALIDATION_INTERVAL;
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProfile } from "@formbricks/lib/services/profile";
import { getServerSession } from "next-auth";
import Onboarding from "./components/Onboarding";
import { getEnvironmentByUser } from "@formbricks/lib/services/environment";
import { getProfile } from "@formbricks/lib/services/profile";
import { ErrorComponent } from "@formbricks/ui";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
export default async function OnboardingPage() {
const session = await getServerSession(authOptions);
const environment = await getEnvironmentByUser(session?.user);
if (!session) {
throw new Error("No session found");
}
const environment = await getFirstEnvironmentByUserId(session?.user.id);
const profile = await getProfile(session?.user.id!);
const product = await getProductByEnvironmentId(environment?.id!);
if (!environment || !profile || !product) {
return <ErrorComponent />;
throw new Error("Failed to get environment, profile, or product");
}
return <Onboarding session={session} environmentId={environment?.id} profile={profile} product={product} />;
return <Onboarding session={session} environmentId={environment.id} profile={profile} product={product} />;
}

View File

@@ -1,5 +1,5 @@
import { writeData } from "@formbricks/lib/services/googleSheet";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TIntegration } from "@formbricks/types/v1/integrations";
import { TPipelineInput } from "@formbricks/types/v1/pipelines";

View File

@@ -3,7 +3,7 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { createDisplay } from "@formbricks/lib/services/displays";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { TDisplay, ZDisplayInput } from "@formbricks/types/v1/displays";
import { NextResponse } from "next/server";

View File

@@ -2,8 +2,8 @@ import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { sendToPipeline } from "@/lib/pipelines";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { getSurvey } from "@formbricks/lib/survey/service";
import { updateResponse } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/services/survey";
import { ZResponseUpdateInput } from "@formbricks/types/v1/responses";
import { NextResponse } from "next/server";

View File

@@ -3,8 +3,8 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { sendToPipeline } from "@/lib/pipelines";
import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { getSurvey } from "@formbricks/lib/survey/service";
import { createResponse } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/v1/responses";
import { NextResponse } from "next/server";

View File

@@ -1,5 +1,5 @@
import { prisma } from "@formbricks/database";
import { selectSurvey } from "@formbricks/lib/services/survey";
import { selectSurvey } from "@formbricks/lib/survey/service";
import { TPerson } from "@formbricks/types/v1/people";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { unstable_cache } from "next/cache";

View File

@@ -4,7 +4,7 @@ import { transformErrorToDetails } from "@/lib/api/validator";
import { deleteResponse, getResponse, updateResponse } from "@formbricks/lib/response/service";
import { TResponse, ZResponseUpdateInput } from "@formbricks/types/v1/responses";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { authenticateRequest } from "@/app/api/v1/auth";
import { handleErrorResponse } from "@/app/api/v1/auth";

View File

@@ -1,6 +1,6 @@
import { responses } from "@/lib/api/response";
import { NextResponse } from "next/server";
import { getSurvey, updateSurvey, deleteSurvey } from "@formbricks/lib/services/survey";
import { getSurvey, updateSurvey, deleteSurvey } from "@formbricks/lib/survey/service";
import { TSurvey, ZSurveyInput } from "@formbricks/types/v1/surveys";
import { transformErrorToDetails } from "@/lib/api/validator";
import { authenticateRequest } from "@/app/api/v1/auth";

View File

@@ -2,7 +2,7 @@ import { responses } from "@/lib/api/response";
import { authenticateRequest } from "@/app/api/v1/auth";
import { NextResponse } from "next/server";
import { transformErrorToDetails } from "@/lib/api/validator";
import { createSurvey, getSurveys } from "@formbricks/lib/services/survey";
import { createSurvey, getSurveys } from "@formbricks/lib/survey/service";
import { ZSurveyInput } from "@formbricks/types/v1/surveys";
import { DatabaseError } from "@formbricks/types/v1/errors";

View File

@@ -1,16 +1,13 @@
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@/lib/email";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { populateEnvironment } from "@/lib/populate";
import { prisma } from "@formbricks/database";
import { EMAIL_VERIFICATION_DISABLED, INVITE_DISABLED, SIGNUP_ENABLED } from "@formbricks/lib/constants";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { deleteInvite } from "@formbricks/lib/services/invite";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createProfile } from "@formbricks/lib/services/profile";
import { createTeam } from "@formbricks/lib/services/team";
import { NextResponse } from "next/server";
import { Prisma } from "@prisma/client";
import {
EMAIL_VERIFICATION_DISABLED,
INTERNAL_SECRET,
INVITE_DISABLED,
SIGNUP_ENABLED,
WEBAPP_URL,
} from "@formbricks/lib/constants";
export async function POST(request: Request) {
let { inviteToken, ...user } = await request.json();
@@ -22,7 +19,6 @@ export async function POST(request: Request) {
let inviteId;
try {
let data: Prisma.UserCreateArgs;
let invite;
if (inviteToken) {
@@ -40,92 +36,35 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Invalid invite ID" }, { status: 400 });
}
data = {
data: {
...user,
memberships: {
create: {
accepted: true,
role: invite.role,
team: {
connect: {
id: invite.teamId,
},
},
},
},
},
};
} else {
data = {
data: {
...user,
memberships: {
create: [
{
accepted: true,
role: "owner",
team: {
create: {
name: `${user.name}'s Team`,
products: {
create: [
{
name: "My Product",
environments: {
create: [
{
type: "production",
...populateEnvironment,
},
{
type: "development",
...populateEnvironment,
},
],
},
},
],
},
},
},
},
],
},
},
};
}
// create a user and assign him to the team
type UserWithMemberships = Prisma.UserGetPayload<{ include: { memberships: true } }>;
const userData = (await prisma.user.create({
...data,
include: {
memberships: true,
},
// TODO: This is a hack to get the correct types (casting), we should find a better way to do this
})) as UserWithMemberships;
const teamId = userData.memberships[0].teamId;
if (teamId) {
fetch(`${WEBAPP_URL}/api/v1/teams/${teamId}/add_demo_product`, {
method: "POST",
headers: {
"x-api-key": INTERNAL_SECRET,
},
const profile = await createProfile(user);
await createMembership(invite.teamId, profile.id, {
accepted: true,
role: invite.role,
});
}
if (inviteId) {
sendInviteAcceptedEmail(invite.creator.name, user.name, invite.creator.email);
await prisma.invite.delete({ where: { id: inviteId } });
}
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(profile);
}
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(userData);
await sendInviteAcceptedEmail(invite.creator.name, user.name, invite.creator.email);
await deleteInvite(inviteId);
return NextResponse.json(profile);
} else {
const team = await createTeam({
name: `${user.name}'s Team`,
});
await createProduct(team.id, { name: "My Product" });
const profile = await createProfile(user);
await createMembership(team.id, profile.id, { role: "owner", accepted: true });
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(profile);
}
return NextResponse.json(profile);
}
return NextResponse.json(userData);
} catch (e) {
if (e.code === "P2002") {
return NextResponse.json(

View File

@@ -1,6 +1,6 @@
import ClientLogout from "@/app/ClientLogout";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getEnvironmentByUser } from "@formbricks/lib/services/environment";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
@@ -18,7 +18,10 @@ export default async function Home() {
let environment;
try {
environment = await getEnvironmentByUser(session?.user);
environment = await getFirstEnvironmentByUserId(session?.user.id);
if (!environment) {
throw new Error("No environment found");
}
} catch (error) {
console.error("error getting environment", error);
}

View File

@@ -5,7 +5,7 @@ import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
import { REVALIDATION_INTERVAL, WEBAPP_URL } from "@formbricks/lib/constants";
import { getOrCreatePersonByUserId } from "@formbricks/lib/services/person";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getEmailVerificationStatus } from "./helpers";
import { checkValidity } from "@/app/s/[surveyId]/prefilling";
import { notFound } from "next/navigation";

View File

@@ -2,7 +2,7 @@ import { cn } from "@formbricks/lib/cn";
import SurveyNavBarName from "@/components/shared/SurveyNavBarName";
import Link from "next/link";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getSurvey } from "@formbricks/lib/services/survey";
import { getSurvey } from "@formbricks/lib/survey/service";
interface SecondNavbarProps {
tabs: { id: string; label: string; href: string; icon?: React.ReactNode }[];

View File

@@ -1,6 +1,6 @@
"use client";
import { surveyMutateAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { updateSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TSurvey } from "@formbricks/types/v1/surveys";
@@ -44,7 +44,7 @@ export default function SurveyStatusDropdown({
disabled={isStatusChangeDisabled}
onValueChange={(value) => {
const castedValue = value as "draft" | "inProgress" | "paused" | "completed";
surveyMutateAction({ ...survey, status: castedValue })
updateSurveyAction({ ...survey, status: castedValue })
.then(() => {
toast.success(
value === "inProgress"

View File

@@ -1,6 +1,5 @@
import { createTeam } from "@/app/(app)/environments/[environmentId]/actions";
import { createTeamAction } from "@/app/(app)/environments/[environmentId]/actions";
import Modal from "@/components/shared/Modal";
import { useProfile } from "@/lib/profile";
import { Button, Input, Label } from "@formbricks/ui";
import { PlusCircleIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";
@@ -15,13 +14,12 @@ interface CreateTeamModalProps {
export default function CreateTeamModal({ open, setOpen }: CreateTeamModalProps) {
const router = useRouter();
const { profile } = useProfile();
const [loading, setLoading] = useState(false);
const { register, handleSubmit } = useForm();
const submitTeam = async (data) => {
setLoading(true);
const newTeam = await createTeam(data.name, (profile as any).id);
const newTeam = await createTeamAction(data.name);
toast.success("Team created successfully!");
router.push(`/teams/${newTeam.id}`);

View File

@@ -1,28 +0,0 @@
import { EventType } from "@prisma/client";
export const populateEnvironment = {
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
},
attributeClasses: {
create: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
},
};

View File

@@ -1,7 +1,6 @@
import { getSessionUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
import { prisma } from "@formbricks/database";
import { EnvironmentType } from "@prisma/client";
import { populateEnvironment } from "@/lib/populate";
import { createProduct } from "@formbricks/lib/services/product";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
@@ -93,29 +92,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
}
// Create a new product and associate it with the current team
const newProduct = await prisma.product.create({
data: {
name,
team: {
connect: { id: environment.product.teamId },
},
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
},
select: {
environments: true,
},
});
const newProduct = await createProduct(environment.product.teamId, { name });
const firstEnvironment = newProduct.environments[0];
res.json(firstEnvironment);

View File

@@ -1,12 +1,12 @@
import "server-only";
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getResponse, getResponseCacheTag } from "./service";
import { unstable_cache } from "next/cache";
import { getSurvey } from "../services/survey";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getSurvey } from "../survey/service";
import { validateInputs } from "../utils/validate";
import { getResponse, getResponseCacheTag } from "./service";
export const canUserAccessResponse = async (userId: string, responseId: string): Promise<boolean> =>
await unstable_cache(

View File

@@ -1,15 +1,24 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { z } from "zod";
import { Prisma, EnvironmentType } from "@prisma/client";
import type {
TEnvironment,
TEnvironmentCreateInput,
TEnvironmentUpdateInput,
} from "@formbricks/types/v1/environment";
import {
ZEnvironment,
ZEnvironmentCreateInput,
ZEnvironmentUpdateInput,
ZId,
} from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/v1/errors";
import type { TEnvironment, TEnvironmentId, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
import { populateEnvironment } from "../utils/createDemoProductHelpers";
import { ZEnvironment, ZEnvironmentUpdateInput, ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { unstable_cache, revalidateTag } from "next/cache";
import { EventType, Prisma } from "@prisma/client";
import { revalidateTag, unstable_cache } from "next/cache";
import "server-only";
import { z } from "zod";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { validateInputs } from "../utils/validate";
export const getEnvironmentCacheTag = (environmentId: string) => `environments-${environmentId}`;
export const getEnvironmentsCacheTag = (productId: string) => `products-${productId}-environments`;
@@ -125,86 +134,84 @@ export const updateEnvironment = async (
}
};
export const getEnvironmentByUser = async (user: any): Promise<TEnvironment | TEnvironmentId | null> => {
const firstMembership = await prisma.membership.findFirst({
where: {
userId: user.id,
},
select: {
teamId: true,
},
});
if (!firstMembership) {
// create a new team and return environment
const membership = await prisma.membership.create({
data: {
accepted: true,
role: "owner",
user: { connect: { id: user.id } },
team: {
create: {
name: `${user.name}'s Team`,
products: {
create: {
name: "My Product",
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
},
},
},
},
},
include: {
team: {
include: {
products: {
include: {
environments: true,
export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvironment | null> => {
validateInputs([userId, ZId]);
let environmentPrisma;
try {
environmentPrisma = await prisma.environment.findFirst({
where: {
type: "production",
product: {
team: {
memberships: {
some: {
userId,
},
},
},
},
},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");
}
const environment = membership.team.products[0].environments[0];
throw error;
}
try {
const environment = ZEnvironment.parse(environmentPrisma);
return environment;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(JSON.stringify(error.errors, null, 2));
}
throw new ValidationError("Data validation of environment failed");
}
const firstProduct = await prisma.product.findFirst({
where: {
teamId: firstMembership.teamId,
},
select: {
id: true,
},
});
if (firstProduct === null) {
return null;
}
const firstEnvironment = await prisma.environment.findFirst({
where: {
productId: firstProduct.id,
type: "production",
},
select: {
id: true,
},
});
if (firstEnvironment === null) {
return null;
}
return firstEnvironment;
};
export const createEnvironment = async (
productId: string,
environmentInput: Partial<TEnvironmentCreateInput>
): Promise<TEnvironment> => {
validateInputs([productId, ZId], [environmentInput, ZEnvironmentCreateInput]);
return await prisma.environment.create({
data: {
type: environmentInput.type || "development",
product: { connect: { id: productId } },
widgetSetupCompleted: environmentInput.widgetSetupCompleted || false,
eventClasses: {
create: populateEnvironment.eventClasses,
},
attributeClasses: {
create: populateEnvironment.attributeClasses,
},
},
});
};
export const populateEnvironment = {
eventClasses: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
attributeClasses: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
};

View File

@@ -62,6 +62,26 @@ export const getMembershipsByUserId = cache(async (userId: string): Promise<TMem
return memberships;
});
export const createMembership = async (
teamId: string,
userId: string,
data: Partial<TMembership>
): Promise<TMembership> => {
try {
const membership = await prisma.membership.create({
data: {
userId,
teamId,
accepted: data.accepted,
role: data.role as TMembership["role"],
},
});
return membership;
} catch (error) {
throw error;
}
};
export const updateMembership = async (
userId: string,
teamId: string,

View File

@@ -9,11 +9,9 @@ import { Prisma } from "@prisma/client";
import { revalidateTag, unstable_cache } from "next/cache";
import { cache } from "react";
import { z } from "zod";
import { validateInputs } from "../utils/validate";
import { EnvironmentType } from "@prisma/client";
import { EventType } from "@prisma/client";
import { getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { validateInputs } from "../utils/validate";
import { createEnvironment, getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
export const getProductsCacheTag = (teamId: string): string => `teams-${teamId}-products`;
const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`;
@@ -35,34 +33,6 @@ const selectProduct = {
environments: true,
};
const populateEnvironment = {
eventClasses: {
create: [
{
name: "New Session",
description: "Gets fired when a new session is created",
type: EventType.automatic,
},
{
name: "Exit Intent (Desktop)",
description: "A user on Desktop leaves the website with the cursor.",
type: EventType.automatic,
},
{
name: "50% Scroll",
description: "A user scrolled 50% of the current page",
type: EventType.automatic,
},
],
},
attributeClasses: {
create: [
{ name: "userId", description: "The internal ID of the person", type: EventType.automatic },
{ name: "email", description: "The email of the person", type: EventType.automatic },
],
},
};
export const getProducts = async (teamId: string): Promise<TProduct[]> =>
unstable_cache(
async () => {
@@ -135,6 +105,7 @@ export const updateProduct = async (
inputProduct: Partial<TProductUpdateInput>
): Promise<TProduct> => {
validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput.partial()]);
const { environments, ...data } = inputProduct;
let updatedProduct;
try {
updatedProduct = await prisma.product.update({
@@ -142,7 +113,10 @@ export const updateProduct = async (
id: productId,
},
data: {
...inputProduct,
...data,
environments: {
connect: environments?.map((environment) => ({ id: environment.id })) ?? [],
},
},
select: selectProduct,
});
@@ -210,43 +184,35 @@ export const deleteProduct = cache(async (productId: string): Promise<TProduct>
return product;
});
export const createProduct = async (environmentId: string, productName: string): Promise<TProduct> => {
const environment = await prisma.environment.findUnique({
where: { id: environmentId },
select: {
product: {
select: {
teamId: true,
},
},
},
});
if (!environment) {
throw new Error("Invalid environment");
export const createProduct = async (
teamId: string,
productInput: Partial<TProductUpdateInput>
): Promise<TProduct> => {
if (!productInput.name) {
throw new ValidationError("Product Name is required");
}
const { environments, ...data } = productInput;
const newProduct = await prisma.product.create({
let product = await prisma.product.create({
data: {
name: productName,
team: {
connect: { id: environment.product.teamId },
},
environments: {
create: [
{
type: EnvironmentType.production,
...populateEnvironment,
},
{
type: EnvironmentType.development,
...populateEnvironment,
},
],
},
...data,
name: productInput.name,
teamId,
},
select: selectProduct,
});
return newProduct;
const devEnvironment = await createEnvironment(product.id, {
type: "development",
});
const prodEnvironment = await createEnvironment(product.id, {
type: "production",
});
product = await updateProduct(product.id, {
environments: [devEnvironment, prodEnvironment],
});
return product;
};

View File

@@ -4,7 +4,12 @@ import { prisma } from "@formbricks/database";
import { ZId } from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { TMembership, TMembershipRole, ZMembershipRole } from "@formbricks/types/v1/memberships";
import { TProfile, TProfileUpdateInput, ZProfileUpdateInput } from "@formbricks/types/v1/profile";
import {
TProfile,
TProfileCreateInput,
TProfileUpdateInput,
ZProfileUpdateInput,
} from "@formbricks/types/v1/profile";
import { MembershipRole, Prisma } from "@prisma/client";
import { unstable_cache, revalidateTag } from "next/cache";
import { validateInputs } from "../utils/validate";
@@ -149,6 +154,19 @@ const deleteUser = async (userId: string): Promise<TProfile> => {
return profile;
};
export const createProfile = async (data: TProfileCreateInput): Promise<TProfile> => {
validateInputs([data, ZProfileUpdateInput]);
const profile = await prisma.user.create({
data: data,
select: responseSelection,
});
revalidateTag(getProfileByEmailCacheTag(profile.email));
revalidateTag(getProfileCacheTag(profile.id));
return profile;
};
// function to delete a user's profile including teams
export const deleteProfile = async (userId: string): Promise<void> => {
validateInputs([userId, ZId]);

View File

@@ -107,7 +107,20 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
}
)();
export const updateTeam = async (teamId: string, data: TTeamUpdateInput): Promise<TTeam> => {
export const createTeam = async (teamInput: TTeamUpdateInput): Promise<TTeam> => {
try {
const team = await prisma.team.create({
data: teamInput,
select,
});
return team;
} catch (error) {
throw error;
}
};
export const updateTeam = async (teamId: string, data: Partial<TTeamUpdateInput>): Promise<TTeam> => {
try {
const updatedTeam = await prisma.team.update({
where: {

View File

@@ -0,0 +1,24 @@
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getSurvey, getSurveyCacheTag } from "./service";
import { unstable_cache } from "next/cache";
export const canUserAccessSurvey = async (userId: string, surveyId: string): Promise<boolean> =>
await unstable_cache(
async () => {
validateInputs([surveyId, ZId], [userId, ZId]);
if (!userId) return false;
const survey = await getSurvey(surveyId);
if (!survey) throw new Error("Survey not found");
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, survey.environmentId);
if (!hasAccessToEnvironment) return false;
return true;
},
[`users-${userId}-surveys-${surveyId}`],
{ revalidate: 30 * 60, tags: [getSurveyCacheTag(surveyId)] }
)(); // 30 minutes

View File

@@ -15,22 +15,15 @@ import { revalidateTag, unstable_cache } from "next/cache";
import { z } from "zod";
import { captureTelemetry } from "../telemetry";
import { validateInputs } from "../utils/validate";
import { getDisplaysCacheTag } from "./displays";
import { getDisplaysCacheTag } from "../services/displays";
import { getResponsesCacheTag } from "../response/service";
// surveys cache key and tags
const getSurveysCacheKey = (environmentId: string): string => `environments-${environmentId}-surveys`;
const getSurveysCacheTag = (environmentId: string): string => `environments-${environmentId}-surveys`;
// survey cache key and tags
export const getSurveyCacheKey = (surveyId: string): string => `surveys-${surveyId}`;
export const getSurveyCacheTag = (surveyId: string): string => `surveys-${surveyId}`;
// survey with analytics cache key
const getSurveysWithAnalyticsCacheKey = (environmentId: string): string =>
`environments-${environmentId}-surveysWithAnalytics`;
const getSurveyWithAnalyticsCacheKey = (surveyId: string): string => `surveyWithAnalytics-${surveyId}`;
export const selectSurvey = {
id: true,
createdAt: true,
@@ -145,7 +138,7 @@ export const getSurveyWithAnalytics = async (surveyId: string): Promise<TSurveyW
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveyWithAnalyticsCacheKey(surveyId)],
[`surveyWithAnalytics-${surveyId}`],
{
tags: [getSurveyCacheTag(surveyId), getDisplaysCacheTag(surveyId), getResponsesCacheTag(surveyId)],
revalidate: 60 * 30,
@@ -204,7 +197,7 @@ export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveyCacheKey(surveyId)],
[`surveys-${surveyId}`],
{
tags: [getSurveyCacheTag(surveyId)],
revalidate: 60 * 30,
@@ -329,7 +322,7 @@ export const getSurveys = async (environmentId: string): Promise<TSurvey[]> => {
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveysCacheKey(environmentId)],
[`environments-${environmentId}-surveys`],
{
tags: [getSurveysCacheTag(environmentId)],
revalidate: 60 * 30,
@@ -393,7 +386,7 @@ export const getSurveysWithAnalytics = async (environmentId: string): Promise<TS
throw new ValidationError("Data validation of survey failed");
}
},
[getSurveysWithAnalyticsCacheKey(environmentId)],
[`environments-${environmentId}-surveysWithAnalytics`],
{
tags: [getSurveysCacheTag(environmentId)], // TODO: add tags for displays and responses
}

View File

@@ -13,6 +13,8 @@ module.exports = {
animation: {
"ping-slow": "ping 2s cubic-bezier(0, 0, 0.2, 1) infinite",
shake: "shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
@@ -49,6 +51,14 @@ module.exports = {
transform: "translate3d(4px, 0, 0)",
},
},
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
maxWidth: {
"8xl": "88rem",

View File

@@ -44,4 +44,12 @@ export const ZActionClassInput = z.object({
type: z.enum(["code", "noCode"]),
});
export const ZActionClassAutomaticInput = z.object({
name: z.string(),
description: z.string().optional(),
type: z.enum(["automatic"]),
});
export type TActionClassAutomaticInput = z.infer<typeof ZActionClassAutomaticInput>;
export type TActionClassInput = z.infer<typeof ZActionClassInput>;

View File

@@ -22,12 +22,20 @@ export const ZAttributeClassInput = z.object({
environmentId: z.string(),
});
export const ZAttributeClassAutomaticInput = z.object({
name: z.string(),
description: z.string(),
type: z.enum(["automatic"]),
});
export const ZAttributeClassUpdateInput = z.object({
name: z.string(),
description: z.string().optional(),
archived: z.boolean().optional(),
});
export type TAttributeClassAutomaticInput = z.infer<typeof ZAttributeClassAutomaticInput>;
export type TAttributeClassUpdateInput = z.infer<typeof ZAttributeClassUpdateInput>;
export type TAttributeClassInput = z.infer<typeof ZAttributeClassInput>;

View File

@@ -25,4 +25,11 @@ export const ZEnvironmentUpdateInput = z.object({
export const ZId = z.string().cuid2();
export const ZEnvironmentCreateInput = z.object({
type: z.enum(["development", "production"]).optional(),
widgetSetupCompleted: z.boolean().optional(),
});
export type TEnvironmentCreateInput = z.infer<typeof ZEnvironmentCreateInput>;
export type TEnvironmentUpdateInput = z.infer<typeof ZEnvironmentUpdateInput>;

View File

@@ -11,7 +11,7 @@ export const ZProduct = z.object({
highlightBorderColor: z
.string()
.regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)
.nullish(),
.nullable(),
recontactDays: z.number().int(),
formbricksSignature: z.boolean(),
placement: z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]),
@@ -26,7 +26,6 @@ export const ZProductUpdateInput = ZProduct.omit({
id: true,
createdAt: true,
updatedAt: true,
environments: true,
});
export type TProductUpdateInput = z.infer<typeof ZProductUpdateInput>;

View File

@@ -21,11 +21,21 @@ export const ZProfile = z.object({
export type TProfile = z.infer<typeof ZProfile>;
export const ZProfileUpdateInput = z.object({
name: z.string().nullable(),
email: z.string(),
onboardingCompleted: z.boolean(),
role: ZRole.nullable(),
objective: ZObjective.nullable(),
name: z.string().nullish(),
email: z.string().optional(),
onboardingCompleted: z.boolean().optional(),
role: ZRole.optional(),
objective: ZObjective.optional(),
});
export type TProfileUpdateInput = z.infer<typeof ZProfileUpdateInput>;
export const ZProfileCreateInput = z.object({
name: z.string().optional(),
email: z.string(),
onboardingCompleted: z.boolean().optional(),
role: ZRole.optional(),
objective: ZObjective.optional(),
});
export type TProfileCreateInput = z.infer<typeof ZProfileCreateInput>;

View File

@@ -1,5 +1,4 @@
import { z } from "zod";
import { Prisma } from "@prisma/client";
export const ZTeam = z.object({
id: z.string().cuid2(),
@@ -10,6 +9,12 @@ export const ZTeam = z.object({
stripeCustomerId: z.string().nullable(),
});
export type TTeamUpdateInput = Prisma.TeamUpdateInput;
export const ZTeamUpdateInput = z.object({
name: z.string(),
plan: z.enum(["free", "pro"]).optional(),
stripeCustomerId: z.string().nullish(),
});
export type TTeamUpdateInput = z.infer<typeof ZTeamUpdateInput>;
export type TTeam = z.infer<typeof ZTeam>;

View File

@@ -0,0 +1,53 @@
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@formbricks/lib/cn";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-left font-medium text-slate-800 transition-all hover:underline dark:text-slate-100 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm text-slate-500 transition-all dark:text-slate-300",
className
)}
{...props}>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -1,3 +1,4 @@
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./components/Accordion";
export { AdvancedOptionToggle } from "./components/AdvancedOptionToggle";
export { Alert, AlertDescription, AlertTitle } from "./components/Alert";
export { PersonAvatar, ProfileAvatar } from "./components/Avatars";
@@ -50,6 +51,7 @@ export {
export { ErrorComponent } from "./components/ErrorComponent";
export { Input } from "./components/Input";
export { Label } from "./components/Label";
export { NoMobileOverlay } from "./components/NoMobileOverlay";
export { Pagination } from "./components/Pagination";
export { PasswordInput } from "./components/PasswordInput";
export { Popover, PopoverContent, PopoverTrigger } from "./components/Popover";
@@ -67,12 +69,11 @@ export {
SelectTrigger,
SelectValue,
} from "./components/Select";
export { Skeleton } from "./components/Skeleton";
export { Switch } from "./components/Switch";
export { TabBar } from "./components/TabBar";
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/Tooltip";
export { AddVariablesDropdown, Editor } from "./components/editor";
export { Skeleton } from "./components/Skeleton";
export { NoMobileOverlay } from "./components/NoMobileOverlay";
/* Icons */
export { AngryBirdRage2Icon } from "./components/icons/AngryBirdRage2Icon";

View File

@@ -29,6 +29,7 @@
"@lexical/react": "^0.12.2",
"@lexical/rich-text": "^0.12.2",
"@lexical/table": "^0.12.2",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",

30
pnpm-lock.yaml generated
View File

@@ -717,6 +717,9 @@ importers:
'@lexical/table':
specifier: ^0.12.2
version: 0.12.2(lexical@0.12.2)
'@radix-ui/react-accordion':
specifier: ^1.1.2
version: 1.1.2(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-checkbox':
specifier: ^1.0.4
version: 1.0.4(react-dom@18.2.0)(react@18.2.0)
@@ -4748,6 +4751,33 @@ packages:
'@babel/runtime': 7.21.0
dev: false
/@radix-ui/react-accordion@1.1.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.23)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.23)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-arrow@1.0.3(@types/react@18.2.23)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
peerDependencies: