diff --git a/.env.docker b/.env.docker index 106955bcfb..ba19223174 100644 --- a/.env.docker +++ b/.env.docker @@ -101,3 +101,7 @@ GOOGLE_CLIENT_SECRET= # Cron Secret CRON_SECRET= + +# Encryption key +# You can use: `openssl rand -base64 16` to generate one +FORMBRICKS_ENCRYPTION_KEY= \ No newline at end of file diff --git a/.env.example b/.env.example index 11c21391d3..db986396c9 100644 --- a/.env.example +++ b/.env.example @@ -104,4 +104,8 @@ NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID= # Cron Secret CRON_SECRET= -*/ \ No newline at end of file + +# Encryption key +# You can use: `openssl rand -base64 16` to generate one +FORMBRICKS_ENCRYPTION_KEY= +*/ diff --git a/apps/formbricks-com/app/docs/api/display/page.mdx b/apps/formbricks-com/app/docs/api/client/displays/page.mdx similarity index 96% rename from apps/formbricks-com/app/docs/api/display/page.mdx rename to apps/formbricks-com/app/docs/api/client/displays/page.mdx index 18b746b0aa..fe7c7aa916 100644 --- a/apps/formbricks-com/app/docs/api/display/page.mdx +++ b/apps/formbricks-com/app/docs/api/client/displays/page.mdx @@ -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' }} diff --git a/apps/formbricks-com/app/docs/api/overview/page.mdx b/apps/formbricks-com/app/docs/api/client/overview/page.mdx similarity index 68% rename from apps/formbricks-com/app/docs/api/overview/page.mdx rename to apps/formbricks-com/app/docs/api/client/overview/page.mdx index 58029f9136..eb8de73d1e 100644 --- a/apps/formbricks-com/app/docs/api/overview/page.mdx +++ b/apps/formbricks-com/app/docs/api/client/overview/page.mdx @@ -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 + 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. diff --git a/apps/formbricks-com/app/docs/api/responses/page.mdx b/apps/formbricks-com/app/docs/api/client/responses/page.mdx similarity index 73% rename from apps/formbricks-com/app/docs/api/responses/page.mdx rename to apps/formbricks-com/app/docs/api/client/responses/page.mdx index 480d506073..7ff444056a 100644 --- a/apps/formbricks-com/app/docs/api/responses/page.mdx +++ b/apps/formbricks-com/app/docs/api/client/responses/page.mdx @@ -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' }} - - - - - Retrieve all the responses you have received for all your surveys. - - ### Mandatory Headers - - - - Your Formbricks API key. - - - - ### Optional Query Params - - - SurveyId to filter responses by. - - - - - - - - - ```bash {{ title: 'cURL' }} - curl --location \ - 'https://app.formbricks.com/api/v1/management/responses' \ - --header \ - 'x-api-key: ' - ``` - - - - - - ```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" - } - } - ``` - - - - - ---- - ## 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/' }} Update an existing response in a survey. @@ -247,10 +149,10 @@ Update an existing response in a survey. - + ```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/' \ --data-raw '{ "personId": "clfqjny0v000ayzgsycx54a2c", "surveyId": "clfqz1esd0000yzah51trddn8", diff --git a/apps/formbricks-com/app/docs/api/management/action-classes/page.mdx b/apps/formbricks-com/app/docs/api/management/action-classes/page.mdx new file mode 100644 index 0000000000..85a5505b3f --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/action-classes/page.mdx @@ -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) + +You will need an API Key to interact with these APIs. + +--- + +## Get all Action Classes {{ tag: 'GET', label: '/api/v1/management/action-classes' }} + + + + + Get all the existing action classes in your environment. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/action-classes' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Get Action Class by ID {{ tag: 'GET', label: '/api/v1/management/action-classes/' }} + + + + + Fetch an action class by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/action-classes/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Create Action Class {{ tag: 'POST', label: '/api/v1/management/action-classes/' }} + + + + + Create an action class. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + ### Body + + + ```json {{ title: 'cURL' }} + { + "environmentId": "cln8k0t47000fz87njmmu2bck", + "name": "My Action from API", + "type": "code" + } + ``` + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X POST https://app.formbricks.com/api/v1/management/action-classes/ \ + --header 'Content-Type: application/json' \ + --header 'x-api-key: ' \ + -d '{"environmentId": "cln8k0t47000fz87njmmu2bck", "name": "My Action from API", "type": "code"}' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Delete Action Class {{ tag: 'DELETE', label: '/api/v1/management/action-classes/' }} + + + + + Delete an action class by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE https://app.formbricks.com/api/v1/management/action-classes/ \ + --header 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- diff --git a/apps/formbricks-com/app/docs/api/api-key-setup/add-api-key.webp b/apps/formbricks-com/app/docs/api/management/api-key-setup/add-api-key.webp similarity index 100% rename from apps/formbricks-com/app/docs/api/api-key-setup/add-api-key.webp rename to apps/formbricks-com/app/docs/api/management/api-key-setup/add-api-key.webp diff --git a/apps/formbricks-com/app/docs/api/api-key-setup/api-key-secret.webp b/apps/formbricks-com/app/docs/api/management/api-key-setup/api-key-secret.webp similarity index 100% rename from apps/formbricks-com/app/docs/api/api-key-setup/api-key-secret.webp rename to apps/formbricks-com/app/docs/api/management/api-key-setup/api-key-secret.webp diff --git a/apps/formbricks-com/app/docs/api/api-key-setup/page.mdx b/apps/formbricks-com/app/docs/api/management/api-key-setup/page.mdx similarity index 100% rename from apps/formbricks-com/app/docs/api/api-key-setup/page.mdx rename to apps/formbricks-com/app/docs/api/management/api-key-setup/page.mdx diff --git a/apps/formbricks-com/app/docs/api/management/attribute-classes/page.mdx b/apps/formbricks-com/app/docs/api/management/attribute-classes/page.mdx new file mode 100644 index 0000000000..c02f5bdabd --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/attribute-classes/page.mdx @@ -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) + +You will need an API Key to interact with these APIs. + +--- + +## Get all Attribute Classes {{ tag: 'GET', label: '/api/v1/management/attribute-classes' }} + + + + + Get all the existing attribute classes in your environment. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/attribute-classes' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Get Attribute Class by ID {{ tag: 'GET', label: '/api/v1/management/attribute-classes/' }} + + + + + Fetch an Attribute class by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/attribute-classes/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Create Attribute Class {{ tag: 'POST', label: '/api/v1/management/attribute-classes/' }} + + + + + Create an Attribute class. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + ### Body + + + ```json {{ title: 'cURL' }} + { + "environmentId": "clmlmwdqq0003196ufewo6ibg", + "name": "My Attribute from API", + "type": "code", + "description": "My description" + } + ``` + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X POST https://app.formbricks.com/api/v1/management/attribute-classes/ \ + --header 'Content-Type: application/json' \ + --header 'x-api-key: ' \ + -d '{"environmentId": "clmlmwdqq0003196ufewo6ibg", "name": "My Attribute from API", "type": "code", "description":"My description"}' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Delete Attribute Class {{ tag: 'DELETE', label: '/api/v1/management/attribute-classes/' }} + + + + + Delete an Attribute class by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE https://app.formbricks.com/api/v1/management/attribute-classes/ \ + --header 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- diff --git a/apps/formbricks-com/app/docs/api/management/me/page.mdx b/apps/formbricks-com/app/docs/api/management/me/page.mdx new file mode 100644 index 0000000000..147f8f58e9 --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/me/page.mdx @@ -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. + +You will need an API Key to interact with these APIs. + +--- + +## Get Environment {{ tag: 'GET', label: '/api/v1/management/me' }} + + + + + Get your current environment details. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/me' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- diff --git a/apps/formbricks-com/app/docs/api/management/people/page.mdx b/apps/formbricks-com/app/docs/api/management/people/page.mdx new file mode 100644 index 0000000000..569a401ebd --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/people/page.mdx @@ -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) + +You will need an API Key to interact with these APIs. + +--- + +## List People {{ tag: 'GET', label: '/api/v1/management/people' }} + + + + + List People + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/people' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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": "" + } + } + ``` + + + + + +--- + +## Get Person {{ tag: 'GET', label: '/api/v1/management/people/' }} + + + + + Get Person by ID + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/people/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Delete Person {{ tag: 'DELETE', label: '/api/v1/management/people/' }} + + + + + Delete Person by ID + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE https://app.formbricks.com/api/v1/management/people/ \ + --header 'x-api-key: ' + ``` + + + + + ```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" + } + } + ``` + + + + + +--- diff --git a/apps/formbricks-com/app/docs/api/management/responses/page.mdx b/apps/formbricks-com/app/docs/api/management/responses/page.mdx new file mode 100644 index 0000000000..eb564b7c2b --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/responses/page.mdx @@ -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) + +You will need an API Key to interact with these APIs. + +--- + +## List all Responses {{ tag: 'GET', label: '/api/v1/management/responses' }} + + + + + Retrieve all the responses you have received in your environment. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/responses' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Get Response by ID {{ tag: 'GET', label: '/api/v1/management/responses/' }} + + + + + Retrieve a response by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/responses/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + +## Delete a response {{ tag: 'DELETE', label: '/api/v1/client/responses/' }} + + + + + Delete Response by ID + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE https://app.formbricks.com/api/v1/management/resposnes/ \ + --header 'x-api-key: ' + ``` + + + + + + ```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." + } + } + ``` + + + + + + +--- diff --git a/apps/formbricks-com/app/docs/api/management/surveys/page.mdx b/apps/formbricks-com/app/docs/api/management/surveys/page.mdx new file mode 100644 index 0000000000..5e12ae6b15 --- /dev/null +++ b/apps/formbricks-com/app/docs/api/management/surveys/page.mdx @@ -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) + +You will need an API Key to interact with these APIs. + +--- + +## List all surveys {{ tag: 'GET', label: '/api/v1/management/surveys' }} + + + + + Retrieve all the surveys you have for the environment. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/surveys' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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": "

We would love to understand your user experience better. Sharing your insight helps a lot!

", + "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" + } + } + ``` +
+ + +
+ +--- + +## Get Survey by ID {{ tag: 'GET', label: '/api/v1/management/surveys/' }} + + + + + Get a specific survey by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl --location \ + 'https://app.formbricks.com/api/v1/management/surveys/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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": "

We'd love to keep you as a customer. Happy to offer a 30% discount for the next year.

", + "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": "

We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.

", + "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" + } + } + ``` +
+ + +
+ +--- + +## Create Survey {{ tag: 'POST', label: '/api/v1/management/surveys' }} + + + + + Create a survey + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + ### Body + + + ```json {{ title: 'cURL' }} + { + "environmentId": "clmlmwdqq0003196ufewo6ibg", + "type": "link", + "name": "My new Survey" + } + ``` + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE \ + 'https://app.formbricks.com/api/v1/management/surveys' \ + --header \ + 'x-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: ' \ + -d '{"environmentId": "cln8k0t47000fz87njmmu2bck", "name": "My Survey from API", "type": "link"}' + ``` + + + + + + ```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" + } + } + ``` + + + + + +--- + + +## Delete Survey by ID {{ tag: 'DELETE', label: '/api/v1/management/surveys/' }} + + + + + Delete a survey by its ID. + + ### Mandatory Headers + + + + Your Formbricks API key. + + + + + + + + + ```bash {{ title: 'cURL' }} + curl -X DELETE \ + 'https://app.formbricks.com/api/v1/management/surveys/' \ + --header \ + 'x-api-key: ' + ``` + + + + + + ```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": "

We'd love to keep you as a customer. Happy to offer a 30% discount for the next year.

", + "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": "

We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.

", + "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" + } + } + ``` +
+ + +
+ +--- diff --git a/apps/formbricks-com/app/docs/api/webhooks/page.mdx b/apps/formbricks-com/app/docs/api/management/webhooks/page.mdx similarity index 90% rename from apps/formbricks-com/app/docs/api/webhooks/page.mdx rename to apps/formbricks-com/app/docs/api/management/webhooks/page.mdx index d0410f78fc..9ace9def1c 100644 --- a/apps/formbricks-com/app/docs/api/webhooks/page.mdx +++ b/apps/formbricks-com/app/docs/api/management/webhooks/page.mdx @@ -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. +You will need an API Key to interact with these APIs. + --- ## 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/' }} @@ -108,11 +109,11 @@ These APIs are designed to facilitate seamless integration of Formbricks with th - + ```bash {{ title: 'cURL' }} curl --location \ - 'https://app.formbricks.com/api/v1/webhooks/[webhookId]' \ + 'https://app.formbricks.com/api/v1/webhooks/' \ --header \ 'x-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/' }} @@ -271,10 +272,10 @@ Add a webhook to your product. - + ```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/' \ --header 'x-api-key: ' ``` diff --git a/apps/formbricks-com/app/docs/api/people/page.mdx b/apps/formbricks-com/app/docs/api/people/page.mdx deleted file mode 100644 index c682733736..0000000000 --- a/apps/formbricks-com/app/docs/api/people/page.mdx +++ /dev/null @@ -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' }} - - - - - Fetch a Person or create (if they don't exist) by their userId and the environmentId. - - ### Mandatory Query Parameters - - - User ID to fetch or create (if it does not exist) - - - - - - Formbricks Environment ID - - - - - - - - - ```bash {{ title: 'cURL' }} - curl --location \ - 'https://app.formbricks.com/api/v1/client/people/getOrCreate?userId=&environmentId=' - ``` - - - - - - ```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": "" - } - } - ``` - - - - - ---- diff --git a/apps/formbricks-com/app/docs/api/surveys/page.mdx b/apps/formbricks-com/app/docs/api/surveys/page.mdx deleted file mode 100644 index 68faa22201..0000000000 --- a/apps/formbricks-com/app/docs/api/surveys/page.mdx +++ /dev/null @@ -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' }} - - - - - Retrieve all the surveys you have for the environment. - - ### Mandatory Headers - - - - Your Formbricks API key. - - - - - - - - - ```bash {{ title: 'cURL' }} - curl --location \ - 'https://app.formbricks.com/api/v1/management/surveys' \ - --header \ - 'x-api-key: ' - ``` - - - - - - ```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": "

We would love to understand your user experience better. Sharing your insight helps a lot!

", - "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" - } - } - ``` -
- - -
- ---- diff --git a/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx b/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx index 4ea09e78c6..1e3cf0f4ed 100644 --- a/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx +++ b/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx @@ -49,7 +49,7 @@ Server actions are used to perform server actions in client components. For exam ## Use service abstraction instead of direct database calls -We utilize [prisma](https://www.prisma.io/) as our Object-Relational Mapping (ORM) tool to interact with the database. This implies that when you need to fetch or modify data in the database, you will be utilizing prisma. All prisma calls should be written in the services folder `packages/lib/services`, and before creating a new service, please ensure that one does not already exist. +We utilize [prisma](https://www.prisma.io/) as our Object-Relational Mapping (ORM) tool to interact with the database. This implies that when you need to fetch or modify data in the database, you will be utilizing prisma. All prisma calls should be written in the services folder `packages/lib`, and before creating a new service, please ensure that one does not already exist. ## Handle authentication and CORS in management APIs diff --git a/apps/formbricks-com/app/docs/contributing/troubleshooting/page.mdx b/apps/formbricks-com/app/docs/contributing/troubleshooting/page.mdx index 443ed002d4..3854d3c785 100644 --- a/apps/formbricks-com/app/docs/contributing/troubleshooting/page.mdx +++ b/apps/formbricks-com/app/docs/contributing/troubleshooting/page.mdx @@ -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. -Logout Person +Logout Person diff --git a/apps/formbricks-com/app/docs/faq/page.mdx b/apps/formbricks-com/app/docs/faq/page.mdx new file mode 100644 index 0000000000..197cb5b704 --- /dev/null +++ b/apps/formbricks-com/app/docs/faq/page.mdx @@ -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). + + diff --git a/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx b/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx index 2f974c3130..7f7db3361b 100644 --- a/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx +++ b/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx @@ -157,8 +157,8 @@ To remove the integration with Google Account, For the above, we ask for: 1. **User Email**: To identify you (that's it, nothing else, we're opensource, see this in our codebase [here](https://github.com/formbricks/formbricks/blob/main/apps/web/app/api/google-sheet/callback/route.ts#L47C17-L47C25)) -1. **Google Drive API**: To list all your google sheets (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/services/googleSheet.ts#L13)) -1. **Google Spreadsheet API**: To write to the spreadsheet you select (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/services/googleSheet.ts#L70)) +1. **Google Drive API**: To list all your google sheets (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/googleSheet/service.ts#L13)) +1. **Google Spreadsheet API**: To write to the spreadsheet you select (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/googleSheet/service.ts#L70)) We do not store any other information of yours! We value Privacy more than you and rest assured you're safe diff --git a/apps/formbricks-com/app/docs/introduction/what-is-formbricks/page.mdx b/apps/formbricks-com/app/docs/introduction/what-is-formbricks/page.mdx index 33e0d1462c..ea333e00fd 100644 --- a/apps/formbricks-com/app/docs/introduction/what-is-formbricks/page.mdx +++ b/apps/formbricks-com/app/docs/introduction/what-is-formbricks/page.mdx @@ -45,5 +45,3 @@ Natively embed qualitative user research into your B2B SaaS. Leverage Best Pract - - diff --git a/apps/formbricks-com/app/docs/link-surveys/user-identification/page.mdx b/apps/formbricks-com/app/docs/link-surveys/user-identification/page.mdx index 3e31fb1cd5..8bfcfae9a7 100644 --- a/apps/formbricks-com/app/docs/link-surveys/user-identification/page.mdx +++ b/apps/formbricks-com/app/docs/link-surveys/user-identification/page.mdx @@ -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. - If users are identified by email, it will show. If not, the internal Id will be set. = [ { 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" }, ], }, ]; diff --git a/apps/formbricks-com/components/docs/docsFaq.tsx b/apps/formbricks-com/components/docs/docsFaq.tsx new file mode 100644 index 0000000000..b0da4979fa --- /dev/null +++ b/apps/formbricks-com/components/docs/docsFaq.tsx @@ -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 Linux Ubuntu distribution. And + to forward the requests, make sure you have an A record 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{" "} + + here + + . + + ), + }, +]; + +export const faqJsonLdData = FAQ_DATA.map((faq) => ({ + questionName: faq.question, + acceptedAnswerText: faq.answer(), +})); + +export default function FAQ() { + return ( + <> + + + {FAQ_DATA.map((faq, index) => ( + + {faq.question} + {faq.answer()} + + ))} + + + ); +} diff --git a/apps/formbricks-com/components/docs/faQJsonLD.tsx b/apps/formbricks-com/components/docs/faQJsonLD.tsx new file mode 100644 index 0000000000..930261eaab --- /dev/null +++ b/apps/formbricks-com/components/docs/faQJsonLD.tsx @@ -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 ; +} diff --git a/apps/formbricks-com/components/home/Faq.tsx b/apps/formbricks-com/components/home/Faq.tsx new file mode 100644 index 0000000000..7ea60cc829 --- /dev/null +++ b/apps/formbricks-com/components/home/Faq.tsx @@ -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{" "} + + here + + . + + ), + }, + { + 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{" "} + + here + + . + + ), + }, + { + 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 ( +
+ + + + {FAQ_DATA.map((faq, index) => ( + + {faq.question} + {faq.answer()} + + ))} + +
+ ); +} diff --git a/apps/formbricks-com/pages/formtribe/index.tsx b/apps/formbricks-com/pages/formtribe/index.tsx index 409b1f4c72..9a993efd3f 100644 --- a/apps/formbricks-com/pages/formtribe/index.tsx +++ b/apps/formbricks-com/pages/formtribe/index.tsx @@ -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() { diff --git a/apps/formbricks-com/pages/index.tsx b/apps/formbricks-com/pages/index.tsx index c4b8bc18dd..5915a86874 100644 --- a/apps/formbricks-com/pages/index.tsx +++ b/apps/formbricks-com/pages/index.tsx @@ -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 = () => ( ( href="https://app.formbricks.com/auth/signup" inverted /> + + ); diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts index 11dd1b590f..fc739ff3e0 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts @@ -11,8 +11,8 @@ import { getActionCountInLast24Hours, getActionCountInLast7Days, getActionCountInLastHour, -} from "@formbricks/lib/services/actions"; -import { getSurveysByActionClassId } from "@formbricks/lib/services/survey"; +} from "@formbricks/lib/action/service"; +import { getSurveysByActionClassId } from "@formbricks/lib/survey/service"; import { AuthorizationError } from "@formbricks/types/v1/errors"; export async function deleteActionClassAction(environmentId, actionClassId: string) { diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeSettingsTab.tsx b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeSettingsTab.tsx index 55e1d4d86c..2360b75b2c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeSettingsTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeSettingsTab.tsx @@ -5,7 +5,7 @@ import type { AttributeClass } from "@prisma/client"; import { useForm } from "react-hook-form"; import { ArchiveBoxArrowDownIcon, ArchiveBoxXMarkIcon } from "@heroicons/react/24/solid"; import { useRouter } from "next/navigation"; -import { updatetAttributeClass } from "@formbricks/lib/services/attributeClass"; +import { updatetAttributeClass } from "@formbricks/lib/attributeClass/service"; import { useState } from "react"; interface AttributeSettingsTabProps { diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/actions.ts index 55f9b34315..54bdcabb3e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/actions.ts @@ -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 diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/page.tsx b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/page.tsx index 6a37ff53bf..6eda4be838 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/page.tsx @@ -5,7 +5,7 @@ import AttributeClassDataRow from "@/app/(app)/environments/[environmentId]/(act import AttributeTableHeading from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeTableHeading"; import HowToAddAttributesButton from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/HowToAddAttributesButton"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getAttributeClasses } from "@formbricks/lib/services/attributeClass"; +import { getAttributeClasses } from "@formbricks/lib/attributeClass/service"; import { Metadata } from "next"; export const metadata: Metadata = { diff --git a/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx b/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx index 8e5296e288..1468ed030d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx @@ -2,9 +2,9 @@ export const revalidate = REVALIDATION_INTERVAL; import Navigation from "@/app/(app)/environments/[environmentId]/Navigation"; import { IS_FORMBRICKS_CLOUD, REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment"; -import { getProducts } from "@formbricks/lib/services/product"; -import { getTeamByEnvironmentId, getTeamsByUserId } from "@formbricks/lib/services/team"; +import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service"; +import { getProducts } from "@formbricks/lib/product/service"; +import { getTeamByEnvironmentId, getTeamsByUserId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui"; import type { Session } from "next-auth"; diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts index 60ce19b887..ed9c7fc822 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts @@ -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/membership/service"; +import { createProduct } from "@formbricks/lib/product/service"; +import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +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 { - 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 { + 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) { @@ -164,7 +76,12 @@ export async function duplicateSurveyAction(environmentId: string, surveyId: str surveyClosedMessage: existingSurvey.surveyClosedMessage ? JSON.parse(JSON.stringify(existingSurvey.surveyClosedMessage)) : prismaClient.JsonNull, - + singleUse: existingSurvey.singleUse + ? JSON.parse(JSON.stringify(existingSurvey.singleUse)) + : prismaClient.JsonNull, + productOverwrites: existingSurvey.productOverwrites + ? JSON.parse(JSON.stringify(existingSurvey.productOverwrites)) + : prismaClient.JsonNull, verifyEmail: existingSurvey.verifyEmail ? JSON.parse(JSON.stringify(existingSurvey.verifyEmail)) : prismaClient.JsonNull, @@ -178,6 +95,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, @@ -295,6 +230,8 @@ export async function copyToOtherEnvironmentAction( }, }, surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull, + singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull, + productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull, verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull, }, }); @@ -302,12 +239,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; }; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts index 1cf2c6c2aa..08e403420f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts @@ -1,7 +1,7 @@ "use server"; -import { getSpreadSheets } from "@formbricks/lib/services/googleSheet"; -import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/services/integrations"; +import { getSpreadSheets } from "@formbricks/lib/googleSheet/service"; +import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/integration/service"; import { TGoogleSheetIntegration } from "@formbricks/types/v1/integrations"; export async function upsertIntegrationAction( diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx index 6fea3e8ea3..2d554b6e7f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx @@ -1,8 +1,8 @@ import GoogleSheetWrapper from "@/app/(app)/environments/[environmentId]/integrations/google-sheets/GoogleSheetWrapper"; 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 { getSpreadSheets } from "@formbricks/lib/googleSheet/service"; +import { getIntegrations } from "@formbricks/lib/integration/service"; +import { getSurveys } from "@formbricks/lib/survey/service"; import { TGoogleSheetIntegration, TGoogleSpreadsheet } from "@formbricks/types/v1/integrations"; import { GOOGLE_SHEETS_CLIENT_ID, @@ -10,7 +10,7 @@ import { GOOGLE_SHEETS_CLIENT_SECRET, GOOGLE_SHEETS_REDIRECT_URL, } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function GoogleSheet({ params }) { const enabled = !!(GOOGLE_SHEETS_CLIENT_ID && GOOGLE_SHEETS_CLIENT_SECRET && GOOGLE_SHEETS_REDIRECT_URL); diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index 90e8d14a35..d0f47dc020 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -6,9 +6,9 @@ import n8nLogo from "@/images/n8n.png"; import MakeLogo from "@/images/make-small.png"; import { Card } from "@formbricks/ui"; import Image from "next/image"; -import { getCountOfWebhooksBasedOnSource } from "@formbricks/lib/services/webhook"; -import { getEnvironment } from "@formbricks/lib/services/environment"; -import { getIntegrations } from "@formbricks/lib/services/integrations"; +import { getCountOfWebhooksBasedOnSource } from "@formbricks/lib/webhook/service"; +import { getEnvironment } from "@formbricks/lib/environment/service"; +import { getIntegrations } from "@formbricks/lib/integration/service"; export default async function IntegrationsPage({ params }) { const environmentId = params.environmentId; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts index 402e6303f0..e62860046c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { createWebhook, deleteWebhook, updateWebhook } from "@formbricks/lib/services/webhook"; +import { createWebhook, deleteWebhook, updateWebhook } from "@formbricks/lib/webhook/service"; import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks"; export const createWebhookAction = async ( diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx index 41ed71d2b8..62552e4570 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx @@ -4,10 +4,10 @@ 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 { getWebhooks } from "@formbricks/lib/services/webhook"; +import { getSurveys } from "@formbricks/lib/survey/service"; +import { getWebhooks } from "@formbricks/lib/webhook/service"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function CustomWebhookPage({ params }) { const [webhooksUnsorted, surveys, environment] = await Promise.all([ diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx index 402320a1ec..c0ee191534 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx @@ -1,6 +1,6 @@ import ActivityTimeline from "@/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivityTimeline"; -import { getActivityTimeline } from "@formbricks/lib/services/activity"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getActivityTimeline } from "@formbricks/lib/activity/service"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function ActivitySection({ environmentId, diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx index 3c4da368f8..68e1657130 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx @@ -3,9 +3,9 @@ export const revalidate = REVALIDATION_INTERVAL; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; import { capitalizeFirstLetter } from "@/lib/utils"; -import { getPerson } from "@formbricks/lib/services/person"; +import { getPerson } from "@formbricks/lib/person/service"; import { getResponsesByPersonId } from "@formbricks/lib/response/service"; -import { getSessionCount } from "@formbricks/lib/services/session"; +import { getSessionCount } from "@formbricks/lib/session/service"; export default async function AttributesSection({ personId }: { personId: string }) { const person = await getPerson(personId); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection.tsx index 60f73bced7..969d094116 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponsesFeed.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponsesFeed.tsx index 243a5bb899..2e73a97846 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponsesFeed.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponsesFeed.tsx @@ -3,6 +3,7 @@ import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller"; import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator"; import { TResponseWithSurvey } from "@formbricks/types/v1/responses"; import Link from "next/link"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui"; import { TEnvironment } from "@formbricks/types/v1/environment"; export default function ResponseFeed({ @@ -63,6 +64,7 @@ export default function ResponseFeed({ /> +
{response.survey.questions.map((question) => (
@@ -75,6 +77,18 @@ export default function ResponseFeed({
))}
+
+ + + +

{response.singleUseId}

+
+ +

Single Use Id

+
+
+
+
diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx index a7ccdb7d4e..5cfa9dcf2d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx @@ -1,6 +1,6 @@ import GoBackButton from "@/components/shared/GoBackButton"; import { DeletePersonButton } from "./DeletePersonButton"; -import { getPerson } from "@formbricks/lib/services/person"; +import { getPerson } from "@formbricks/lib/person/service"; interface HeadingSectionProps { environmentId: string; diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts index e108d8ed65..7ebeea9a32 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { deletePerson } from "@formbricks/lib/services/person"; +import { deletePerson } from "@formbricks/lib/person/service"; export const deletePersonAction = async (personId: string) => { await deletePerson(personId); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx index 9bb2fbf8fd..9d8e2df966 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx @@ -5,7 +5,7 @@ import AttributesSection from "@/app/(app)/environments/[environmentId]/people/[ import ResponseSection from "@/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection"; import HeadingSection from "@/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function PersonPage({ params }) { const environment = await getEnvironment(params.environmentId); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx index 18e234457d..bc48cd39d9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx @@ -3,8 +3,8 @@ export const revalidate = REVALIDATION_INTERVAL; import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller"; import { truncateMiddle } from "@/lib/utils"; import { PEOPLE_PER_PAGE, REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/services/environment"; -import { getPeople, getPeopleCount } from "@formbricks/lib/services/person"; +import { getEnvironment } from "@formbricks/lib/environment/service"; +import { getPeople, getPeopleCount } from "@formbricks/lib/person/service"; import { TPerson } from "@formbricks/types/v1/people"; import { Pagination, PersonAvatar } from "@formbricks/ui"; import Link from "next/link"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx index 0ab082d63c..26531ae960 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx @@ -1,7 +1,7 @@ import EditApiKeys from "./EditApiKeys"; -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getApiKeys } from "@formbricks/lib/apiKey/service"; -import { getEnvironments } from "@formbricks/lib/services/environment"; +import { getEnvironments } from "@formbricks/lib/environment/service"; export default async function ApiKeyList({ environmentId, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx index 74d15b10cf..8d405f1f5a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx @@ -5,7 +5,7 @@ import SettingsCard from "../SettingsCard"; import SettingsTitle from "../SettingsTitle"; import ApiKeyList from "./ApiKeyList"; import EnvironmentNotice from "@/components/shared/EnvironmentNotice"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function ProfileSettingsPage({ params }) { const environment = await getEnvironment(params.environmentId); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx index d71594bf91..748debd6e6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx @@ -4,7 +4,7 @@ import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; -import { getTeamByEnvironmentId } from "@formbricks/lib/services/team"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { getServerSession } from "next-auth"; import { notFound } from "next/navigation"; import SettingsTitle from "../SettingsTitle"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx index 08561f7430..fcf7351fd5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx @@ -1,8 +1,8 @@ import { Metadata } from "next"; import SettingsNavbar from "./SettingsNavbar"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getTeamByEnvironmentId } from "@formbricks/lib/services/team"; -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; export const metadata: Metadata = { title: "Settings", diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx index f14b4529df..348fef9813 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx @@ -2,11 +2,11 @@ import { cn } from "@formbricks/lib/cn"; import { Button, Label, RadioGroup, RadioGroupItem } from "@formbricks/ui"; -import { useState } from "react"; -import toast from "react-hot-toast"; import { getPlacementStyle } from "@/lib/preview"; import { PlacementType } from "@formbricks/types/js"; import { TProduct, TProductUpdateInput } from "@formbricks/types/v1/product"; +import { useState } from "react"; +import toast from "react-hot-toast"; import { updateProductAction } from "./actions"; const placements = [ @@ -35,7 +35,9 @@ export function EditPlacement({ product }: EditPlacementProps) { darkOverlay: overlay === "darkOverlay", clickOutsideClose: clickOutside === "allow", }; + await updateProductAction(product.id, inputProduct); + toast.success("Placement updated successfully."); } catch (error) { toast.error(`Error: ${error.message}`); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts index 46da36abac..64dec47677 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { updateProduct } from "@formbricks/lib/services/product"; +import { updateProduct } from "@formbricks/lib/product/service"; import { TProductUpdateInput } from "@formbricks/types/v1/product"; export async function updateProductAction(productId: string, inputProduct: Partial) { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx index 8c725ae937..1a8d3e59c5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx @@ -1,6 +1,6 @@ export const revalidate = REVALIDATION_INTERVAL; -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; import SettingsCard from "../SettingsCard"; import SettingsTitle from "../SettingsTitle"; @@ -15,6 +15,7 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro if (!product) { throw new Error("Product not found"); } + return (
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx index 682fac982c..006f0490f8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx @@ -1,8 +1,8 @@ import { TTeam } from "@formbricks/types/v1/teams"; import React from "react"; import MembersInfo from "@/app/(app)/environments/[environmentId]/settings/members/EditMemberships/MembersInfo"; -import { getMembersByTeamId } from "@formbricks/lib/services/membership"; -import { getInvitesByTeamId } from "@formbricks/lib/services/invite"; +import { getMembersByTeamId } from "@formbricks/lib/membership/service"; +import { getInvitesByTeamId } from "@formbricks/lib/invite/service"; import { TMembership } from "@formbricks/types/v1/memberships"; type EditMembershipsProps = { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditTeamName.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditTeamName.tsx index 87320cb6ab..dad6c6800c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditTeamName.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditTeamName.tsx @@ -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 = async (data) => { try { setIsUpdatingTeam(true); - await updateTeamAction(team.id, data); + await updateTeamNameAction(team.id, data.name); setIsUpdatingTeam(false); toast.success("Team name updated successfully."); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts index d101e9fc00..a5086ba087 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts @@ -9,24 +9,33 @@ import { inviteUser, resendInvite, updateInvite, -} from "@formbricks/lib/services/invite"; +} from "@formbricks/lib/invite/service"; import { deleteMembership, getMembershipsByUserId, getMembershipByUserIdTeamId, transferOwnership, updateMembership, -} from "@formbricks/lib/services/membership"; -import { deleteTeam, updateTeam } from "@formbricks/lib/services/team"; +} from "@formbricks/lib/membership/service"; +import { deleteTeam, updateTeam } from "@formbricks/lib/team/service"; 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 ( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx index 60255162ec..4039652774 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx @@ -1,7 +1,7 @@ import TeamActions from "@/app/(app)/environments/[environmentId]/settings/members/EditMemberships/TeamActions"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; -import { getMembershipsByUserId, getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership"; -import { getTeamByEnvironmentId } from "@formbricks/lib/services/team"; +import { getMembershipsByUserId, getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { Skeleton } from "@formbricks/ui"; import { getServerSession } from "next-auth"; import { Suspense } from "react"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx index 538441f6cf..b785cbd545 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx @@ -1,10 +1,10 @@ -import { getProducts } from "@formbricks/lib/services/product"; -import { getTeamByEnvironmentId } from "@formbricks/lib/services/team"; +import { getProducts } from "@formbricks/lib/product/service"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { TProduct } from "@formbricks/types/v1/product"; import DeleteProductRender from "@/app/(app)/environments/[environmentId]/settings/product/DeleteProductRender"; import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership"; +import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; type DeleteProductProps = { environmentId: string; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts index 300928c2f0..cffaa22192 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts @@ -1,13 +1,13 @@ "use server"; -import { deleteProduct, getProducts, updateProduct } from "@formbricks/lib/services/product"; +import { deleteProduct, getProducts, updateProduct } from "@formbricks/lib/product/service"; import { TProduct, TProductUpdateInput } from "@formbricks/types/v1/product"; import { getServerSession } from "next-auth"; import { AuthenticationError, AuthorizationError, ResourceNotFoundError } from "@formbricks/types/v1/errors"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; import { TEnvironment } from "@formbricks/types/v1/environment"; -import { getTeamByEnvironmentId } from "@formbricks/lib/services/team"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; export const updateProductAction = async ( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx index 0af1b64da5..97fb952f55 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx @@ -1,4 +1,4 @@ -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import SettingsCard from "../SettingsCard"; import SettingsTitle from "../SettingsTitle"; @@ -6,7 +6,7 @@ import SettingsTitle from "../SettingsTitle"; import EditProductName from "./EditProductName"; import EditWaitingTime from "./EditWaitingTime"; import DeleteProduct from "./DeleteProduct"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; export default async function ProfileSettingsPage({ params }: { params: { environmentId: string } }) { const [, product] = await Promise.all([ diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts index c1cd181fbc..c5fb462456 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts @@ -1,7 +1,7 @@ "use server"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; -import { updateProfile, deleteProfile } from "@formbricks/lib/services/profile"; +import { updateProfile, deleteProfile } from "@formbricks/lib/profile/service"; import { TProfileUpdateInput } from "@formbricks/types/v1/profile"; import { getServerSession } from "next-auth"; import { AuthorizationError } from "@formbricks/types/v1/errors"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx index f3811fc6af..5dbaa31056 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx @@ -8,7 +8,7 @@ import SettingsTitle from "../SettingsTitle"; import { DeleteAccount } from "./DeleteAccount"; import { EditName } from "./EditName"; import { EditAvatar } from "./EditAvatar"; -import { getProfile } from "@formbricks/lib/services/profile"; +import { getProfile } from "@formbricks/lib/profile/service"; export default async function ProfileSettingsPage() { const session = await getServerSession(authOptions); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts index 196f115cd1..5c64a603ad 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { updateEnvironment } from "@formbricks/lib/services/environment"; +import { updateEnvironment } from "@formbricks/lib/environment/service"; import { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment"; export async function updateEnvironmentAction( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx index 904069d470..5141a529be 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx @@ -4,8 +4,8 @@ import { updateEnvironmentAction } from "@/app/(app)/environments/[environmentId import EnvironmentNotice from "@/components/shared/EnvironmentNotice"; import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getActionsByEnvironmentId } from "@formbricks/lib/services/actions"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getActionsByEnvironmentId } from "@formbricks/lib/action/service"; +import { getEnvironment } from "@formbricks/lib/environment/service"; import { ErrorComponent } from "@formbricks/ui"; import SettingsCard from "../SettingsCard"; import SettingsTitle from "../SettingsTitle"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx index b113d9f238..1bac4c3cf8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx @@ -1,8 +1,8 @@ import EditTagsWrapper from "./EditTagsWrapper"; import SettingsTitle from "../SettingsTitle"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getEnvironment } from "@formbricks/lib/environment/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; -import { getTagsOnResponsesCount } from "@formbricks/lib/services/tagOnResponse"; +import { getTagsOnResponsesCount } from "@formbricks/lib/tagOnResponse/service"; export default async function MembersSettingsPage({ params }) { const environment = await getEnvironment(params.environmentId); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx index fd9243d063..55ca0a10a3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx @@ -11,20 +11,23 @@ import { ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline"; import { ComputerDesktopIcon, DevicePhoneMobileIcon } from "@heroicons/react/24/solid"; import { useEffect, useRef, useState } from "react"; +type TPreviewType = "modal" | "fullwidth" | "email"; + interface PreviewSurveyProps { survey: TSurvey | Survey; setActiveQuestionId: (id: string | null) => void; activeQuestionId?: string | null; - previewType?: "modal" | "fullwidth" | "email"; + previewType?: TPreviewType; product: TProduct; environment: TEnvironment; } + let surveyNameTemp; export default function PreviewSurvey({ - survey, setActiveQuestionId, activeQuestionId, + survey, previewType, product, environment, @@ -34,6 +37,18 @@ export default function PreviewSurvey({ const [previewMode, setPreviewMode] = useState("desktop"); const ContentRef = useRef(null); + const { productOverwrites } = survey || {}; + + const { + brandColor: surveyBrandColor, + highlightBorderColor: surveyHighlightBorderColor, + placement: surveyPlacement, + } = productOverwrites || {}; + + const brandColor = surveyBrandColor || product.brandColor; + const placement = surveyPlacement || product.placement; + const highlightBorderColor = surveyHighlightBorderColor || product.highlightBorderColor; + useEffect(() => { // close modal if there are no questions left if (survey.type === "web" && !survey.thankYouCard.enabled) { @@ -52,6 +67,8 @@ export default function PreviewSurvey({ resetQuestionProgress(); surveyNameTemp = survey.name; } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [survey]); function resetQuestionProgress() { @@ -94,12 +111,12 @@ export default function PreviewSurvey({ {previewType === "modal" ? ( Preview Survey @@ -165,8 +171,11 @@ export default function SurveyDropDownMenu({ - {showLinkModal && ( + {showLinkModal && isSingleUse && singleUseIds ? ( + + ) : ( void; + singleUseIds: string[]; +} + +export default function LinkSingleUseSurveyModal({ + survey, + open, + setOpen, + singleUseIds, +}: LinkSingleUseSurveyModalProps) { + const defaultSurveyUrl = `${window.location.protocol}//${window.location.host}/s/${survey.id}`; + const [selectedSingleUseIds, setSelectedSingleIds] = useState([]); + + const linkTextRef = useRef(null); + const router = useRouter(); + + const handleLinkOnClick = (index: number) => { + setSelectedSingleIds([...selectedSingleUseIds, index]); + const surveyUrl = `${defaultSurveyUrl}?suId=${singleUseIds[index]}`; + navigator.clipboard.writeText(surveyUrl); + toast.success("URL copied to clipboard!"); + }; + + return ( + + +
+
+
+

Your survey is ready!

+
+

+ Here are 5 single use links to let people answer your survey: +

+
+ {singleUseIds.map((singleUseId, index) => { + const isSelected = selectedSingleUseIds.includes(index); + return ( +
{ + if (!isSelected) { + handleLinkOnClick(index); + } + }}> + {truncateMiddle(`${defaultSurveyUrl}?suId=${singleUseId}`, 48)} + {isSelected ? ( + + ) : ( + + )} +
+ ); + })} +
+
+
+ + + +
+
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage.tsx index 973743c7ad..4df9c80059 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage.tsx @@ -6,15 +6,23 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import toast from "react-hot-toast"; import LinkSurveyModal from "./LinkSurveyModal"; +import LinkSingleUseSurveyModal from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/LinkSingleUseSurveyModal"; import { TEnvironment } from "@formbricks/types/v1/environment"; interface SummaryMetadataProps { environment: TEnvironment; survey: TSurvey; surveyBaseUrl: string; + singleUseIds?: string[]; } -export default function SuccessMessage({ environment, survey, surveyBaseUrl }: SummaryMetadataProps) { +export default function SuccessMessage({ + environment, + survey, + surveyBaseUrl, + singleUseIds, +}: SummaryMetadataProps) { + const isSingleUse = survey.singleUse?.enabled ?? false; const searchParams = useSearchParams(); const [showLinkModal, setShowLinkModal] = useState(false); const [confetti, setConfetti] = useState(false); @@ -45,7 +53,14 @@ export default function SuccessMessage({ environment, survey, surveyBaseUrl }: S return ( <> - {showLinkModal && ( + {showLinkModal && isSingleUse && singleUseIds ? ( + + ) : ( { @@ -56,6 +58,7 @@ const SummaryPage = ({ survey={survey} surveyId={surveyId} surveyBaseUrl={surveyBaseUrl} + singleUseIds={singleUseIds} product={product} /> { + return Array(5) + .fill(null) + .map(() => { + return generateSurveySingleUseId(isEncrypted); + }); +}; export default async function Page({ params }) { const session = await getServerSession(authOptions); if (!session) { throw new Error("Unauthorized"); } + const [{ responses, survey }, environment] = await Promise.all([ getAnalysisData(params.surveyId, params.environmentId), getEnvironment(params.environmentId), ]); + const isSingleUseSurvey = survey.singleUse?.enabled ?? false; + + let singleUseIds: string[] | undefined = undefined; + if (isSingleUseSurvey) { + singleUseIds = generateSingleUseIds(survey.singleUse?.isEncrypted ?? false); + } + if (!environment) { throw new Error("Environment not found"); } @@ -38,6 +55,7 @@ export default async function Page({ params }) { survey={survey} surveyId={params.surveyId} surveyBaseUrl={SURVEY_BASE_URL} + singleUseIds={isSingleUseSurvey ? singleUseIds : undefined} product={product} environmentTags={tags} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/SummaryHeader.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/SummaryHeader.tsx index a0964cd9ea..0416b6b497 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/SummaryHeader.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/SummaryHeader.tsx @@ -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; @@ -31,8 +31,16 @@ interface SummaryHeaderProps { survey: TSurvey; surveyBaseUrl: string; product: TProduct; + singleUseIds?: string[]; } -const SummaryHeader = ({ surveyId, environment, survey, surveyBaseUrl, product }: SummaryHeaderProps) => { +const SummaryHeader = ({ + surveyId, + environment, + survey, + surveyBaseUrl, + product, + singleUseIds, +}: SummaryHeaderProps) => { const router = useRouter(); const isCloseOnDateEnabled = survey.closeOnDate !== null; @@ -46,7 +54,9 @@ const SummaryHeader = ({ surveyId, environment, survey, surveyBaseUrl, product } {product.name}
- {survey.type === "link" && } + {survey.type === "link" && ( + + )} {(environment?.widgetSetupCompleted || survey.type === "link") && survey?.status !== "draft" ? ( ) : null} @@ -72,6 +82,7 @@ const SummaryHeader = ({ surveyId, environment, survey, surveyBaseUrl, product } className="flex w-full justify-center p-1" survey={survey} surveyBaseUrl={surveyBaseUrl} + singleUseIds={singleUseIds} /> @@ -97,7 +108,7 @@ const SummaryHeader = ({ surveyId, environment, survey, surveyBaseUrl, product } 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" @@ -149,7 +160,12 @@ const SummaryHeader = ({ surveyId, environment, survey, surveyBaseUrl, product }
- + ); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx index 386d43fb88..c5b8b20f28 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx @@ -20,6 +20,7 @@ import { import { QuestionMarkCircleIcon, TrashIcon } from "@heroicons/react/24/solid"; import { ChevronDown, SplitIcon } from "lucide-react"; import { useMemo } from "react"; +import { toast } from "react-hot-toast"; import { BsArrowDown, BsArrowReturnRight } from "react-icons/bs"; interface LogicEditorProps { @@ -141,6 +142,19 @@ export default function LogicEditor({ }; const addLogic = () => { + if (question.logic && question.logic?.length >= 0) { + const hasUndefinedLogic = question.logic.some( + (logic) => + logic.condition === undefined && logic.value === undefined && logic.destination === undefined + ); + if (hasUndefinedLogic) { + toast("Please fill current logic jumps first.", { + icon: "🤓", + }); + return; + } + } + const newLogic: TSurveyLogic[] = !question.logic ? [] : question.logic; newLogic.push({ condition: undefined, diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx index e8efe2bbd0..30e121b28e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx @@ -33,6 +33,7 @@ export default function MultipleChoiceMultiForm({ const [isNew, setIsNew] = useState(true); const [showSubheader, setShowSubheader] = useState(!!question.subheader); const questionRef = useRef(null); + const [isInvalidValue, setIsInvalidValue] = useState(null); const shuffleOptionsTypes = { none: { @@ -76,6 +77,24 @@ export default function MultipleChoiceMultiForm({ updateQuestion(questionIdx, { choices: newChoices, logic: newLogic }); }; + const findDuplicateLabel = () => { + for (let i = 0; i < question.choices.length; i++) { + for (let j = i + 1; j < question.choices.length; j++) { + if (question.choices[i].label.trim() === question.choices[j].label.trim()) { + return question.choices[i].label.trim(); // Return the duplicate label + } + } + } + return null; + }; + + const findEmptyLabel = () => { + for (let i = 0; i < question.choices.length; i++) { + if (question.choices[i].label.trim() === "") return true; + } + return false; + }; + const addChoice = (choiceIdx?: number) => { setIsNew(false); // This question is no longer new. let newChoices = !question.choices ? [] : question.choices; @@ -112,6 +131,9 @@ export default function MultipleChoiceMultiForm({ const newChoices = !question.choices ? [] : question.choices.filter((_, idx) => idx !== choiceIdx); const choiceValue = question.choices[choiceIdx].label; + if (isInvalidValue === choiceValue) { + setIsInvalidValue(null); + } let newLogic: any[] = []; question.logic?.forEach((logic) => { let newL: string | string[] | undefined = logic.value; @@ -198,7 +220,20 @@ export default function MultipleChoiceMultiForm({ className={cn(choice.id === "other" && "border-dashed")} placeholder={choice.id === "other" ? "Other" : `Option ${choiceIdx + 1}`} onChange={(e) => updateChoice(choiceIdx, { label: e.target.value })} - isInvalid={isInValid && choice.label.trim() === ""} + onBlur={() => { + const duplicateLabel = findDuplicateLabel(); + if (duplicateLabel) { + setIsInvalidValue(duplicateLabel); + } else if (findEmptyLabel()) { + setIsInvalidValue(""); + } else { + setIsInvalidValue(null); + } + }} + isInvalid={ + (isInvalidValue === "" && choice.label.trim() === "") || + (isInvalidValue !== null && choice.label.trim() === isInvalidValue.trim()) + } /> {question.choices && question.choices.length > 2 && ( (null); const [isNew, setIsNew] = useState(true); const [showSubheader, setShowSubheader] = useState(!!question.subheader); + const [isInvalidValue, setIsInvalidValue] = useState(null); const questionRef = useRef(null); const shuffleOptionsTypes = { @@ -52,6 +53,24 @@ export default function MultipleChoiceSingleForm({ }, }; + const findDuplicateLabel = () => { + for (let i = 0; i < question.choices.length; i++) { + for (let j = i + 1; j < question.choices.length; j++) { + if (question.choices[i].label.trim() === question.choices[j].label.trim()) { + return question.choices[i].label.trim(); // Return the duplicate label + } + } + } + return null; + }; + + const findEmptyLabel = () => { + for (let i = 0; i < question.choices.length; i++) { + if (question.choices[i].label.trim() === "") return true; + } + return false; + }; + const updateChoice = (choiceIdx: number, updatedAttributes: { label: string }) => { const newLabel = updatedAttributes.label; const oldLabel = question.choices[choiceIdx].label; @@ -112,6 +131,9 @@ export default function MultipleChoiceSingleForm({ const newChoices = !question.choices ? [] : question.choices.filter((_, idx) => idx !== choiceIdx); const choiceValue = question.choices[choiceIdx].label; + if (isInvalidValue === choiceValue) { + setIsInvalidValue(null); + } let newLogic: any[] = []; question.logic?.forEach((logic) => { let newL: string | string[] | undefined = logic.value; @@ -197,8 +219,21 @@ export default function MultipleChoiceSingleForm({ value={choice.label} className={cn(choice.id === "other" && "border-dashed")} placeholder={choice.id === "other" ? "Other" : `Option ${choiceIdx + 1}`} + onBlur={() => { + const duplicateLabel = findDuplicateLabel(); + if (duplicateLabel) { + setIsInvalidValue(duplicateLabel); + } else if (findEmptyLabel()) { + setIsInvalidValue(""); + } else { + setIsInvalidValue(null); + } + }} onChange={(e) => updateChoice(choiceIdx, { label: e.target.value })} - isInvalid={isInValid && choice.label.trim() === ""} + isInvalid={ + (isInvalidValue === "" && choice.label.trim() === "") || + (isInvalidValue !== null && choice.label.trim() === isInvalidValue.trim()) + } /> {question.choices && question.choices.length > 2 && ( void; + setOverlay: (overlay: string) => void; + overlay: string; + setClickOutside: (clickOutside: boolean) => void; + clickOutside: boolean; +}; + +export default function Placement({ + setCurrentPlacement, + currentPlacement, + setOverlay, + overlay, + setClickOutside, + clickOutside, +}: TPlacementProps) { + return ( + <> +
+ setCurrentPlacement(e as PlacementType)} value={currentPlacement}> + {placements.map((placement) => ( +
+ + +
+ ))} +
+
+
+
+
+ {currentPlacement === "center" && ( + <> +
+ + setOverlay(overlay)} + value={overlay} + className="flex space-x-4"> +
+ + +
+
+ + +
+
+
+
+ + setClickOutside(value === "allow")} + value={clickOutside ? "allow" : "disallow"} + className="flex space-x-4"> +
+ + +
+
+ + +
+
+
+ + )} + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx index 143f6ebbff..7b2b177bf1 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx @@ -42,7 +42,15 @@ interface QuestionCardProps { isInValid: boolean; } -export function BackButtonInput({ value, onChange }) { +export function BackButtonInput({ + value, + onChange, + className, +}: { + value: string | undefined; + onChange: (e: any) => void; + className?: string; +}) { return (
@@ -53,6 +61,7 @@ export function BackButtonInput({ value, onChange }) { value={value} placeholder="Back" onChange={onChange} + className={className} />
@@ -235,9 +244,24 @@ export default function QuestionCard({ updateQuestion(questionIdx, { buttonLabel: e.target.value })} + onChange={(e) => { + const trimmedValue = e.target.value.trim(); // Remove spaces from the start and end + const hasInternalSpaces = /\S\s\S/.test(trimmedValue); // Test if there are spaces between words + + if ( + !trimmedValue.includes(" ") && + (trimmedValue === "" || hasInternalSpaces || !/\s/.test(trimmedValue)) + ) { + updateQuestion(questionIdx, { backButtonLabel: trimmedValue }); + } + }} /> @@ -245,6 +269,11 @@ export default function QuestionCard({ updateQuestion(questionIdx, { backButtonLabel: e.target.value })} + className={cn( + isInValid && + question.backButtonLabel?.trim() === "" && + "border border-red-600 focus:border-red-600" + )} /> )} @@ -255,6 +284,11 @@ export default function QuestionCard({ updateQuestion(questionIdx, { backButtonLabel: e.target.value })} + className={cn( + isInValid && + question.backButtonLabel?.trim() === "" && + "border border-red-600 focus:border-red-600" + )} /> )} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx index 1d81e55082..23c05f558b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/ResponseOptionsCard.tsx @@ -1,7 +1,17 @@ "use client"; import { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys"; -import { AdvancedOptionToggle, DatePicker, Input, Label } from "@formbricks/ui"; +import { + AdvancedOptionToggle, + DatePicker, + Input, + Label, + Switch, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@formbricks/ui"; import { CheckCircleIcon } from "@heroicons/react/24/solid"; import * as Collapsible from "@radix-ui/react-collapsible"; import { useEffect, useState } from "react"; @@ -10,9 +20,14 @@ import toast from "react-hot-toast"; interface ResponseOptionsCardProps { localSurvey: TSurveyWithAnalytics; setLocalSurvey: (survey: TSurveyWithAnalytics) => void; + isEncryptionKeySet: boolean; } -export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: ResponseOptionsCardProps) { +export default function ResponseOptionsCard({ + localSurvey, + setLocalSurvey, + isEncryptionKeySet, +}: ResponseOptionsCardProps) { const [open, setOpen] = useState(false); const autoComplete = localSurvey.autoComplete !== null; const [redirectToggle, setRedirectToggle] = useState(false); @@ -27,6 +42,12 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res subheading: "This free & open-source survey has been closed", }); + const [singleUseMessage, setSingleUseMessage] = useState({ + heading: "The survey has already been answered.", + subheading: "You can only use this link once.", + }); + + const [singleUseEncryption, setSingleUseEncryption] = useState(isEncryptionKeySet); const [verifyEmailSurveyDetails, setVerifyEmailSurveyDetails] = useState({ name: "", subheading: "", @@ -104,6 +125,53 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res setLocalSurvey({ ...localSurvey, surveyClosedMessage: message }); }; + const handleSingleUseSurveyToggle = () => { + if (!localSurvey.singleUse?.enabled) { + setLocalSurvey({ + ...localSurvey, + singleUse: { enabled: true, ...singleUseMessage, isEncrypted: singleUseEncryption }, + }); + } else { + setLocalSurvey({ ...localSurvey, singleUse: { enabled: false, isEncrypted: false } }); + } + }; + + const handleSingleUseSurveyMessageChange = ({ + heading, + subheading, + }: { + heading?: string; + subheading?: string; + }) => { + const message = { + heading: heading ?? singleUseMessage.heading, + subheading: subheading ?? singleUseMessage.subheading, + }; + + const localSurveySingleUseEnabled = localSurvey.singleUse?.enabled ?? false; + setSingleUseMessage(message); + setLocalSurvey({ + ...localSurvey, + singleUse: { enabled: localSurveySingleUseEnabled, ...message, isEncrypted: singleUseEncryption }, + }); + }; + + const hangleSingleUseEncryptionToggle = () => { + if (!singleUseEncryption) { + setSingleUseEncryption(true); + setLocalSurvey({ + ...localSurvey, + singleUse: { enabled: true, ...singleUseMessage, isEncrypted: true }, + }); + } else { + setSingleUseEncryption(false); + setLocalSurvey({ + ...localSurvey, + singleUse: { enabled: true, ...singleUseMessage, isEncrypted: false }, + }); + } + }; + const handleVerifyEmailSurveyDetailsChange = ({ name, subheading, @@ -134,6 +202,14 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res setSurveyClosedMessageToggle(true); } + if (localSurvey.singleUse?.enabled) { + setSingleUseMessage({ + heading: localSurvey.singleUse.heading ?? singleUseMessage.heading, + subheading: localSurvey.singleUse.subheading ?? singleUseMessage.subheading, + }); + setSingleUseEncryption(localSurvey.singleUse.isEncrypted); + } + if (localSurvey.verifyEmail) { setVerifyEmailSurveyDetails({ name: localSurvey.verifyEmail.name!, @@ -302,6 +378,81 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res + {/* Single User Survey Options */} + +
+
+
+ +
+
    +
  • + Blocks survey if the survey URL has no Single Use Id (suId). +
  • +
  • + Blocks survey if a submission with the Single Use Id (suId) in the URL exists already. +
  • +
+ + handleSingleUseSurveyMessageChange({ heading: e.target.value })} + /> + + + handleSingleUseSurveyMessageChange({ subheading: e.target.value })} + /> + +
+ + + +
+ + +
+
+ {!isEncryptionKeySet && ( + +

+ FORMBRICKS_ENCRYPTION_KEY needs to be set to enable this feature. +

+
+ )} +
+
+
+
+
+
+ + {/* Verify Email Section */} void; actionClasses: TActionClass[]; attributeClasses: TAttributeClass[]; + isEncryptionKeySet: boolean; } export default function SettingsView({ @@ -22,6 +24,7 @@ export default function SettingsView({ setLocalSurvey, actionClasses, attributeClasses, + isEncryptionKeySet, }: SettingsViewProps) { return (
@@ -41,13 +44,19 @@ export default function SettingsView({ actionClasses={actionClasses} /> - + + +
); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx new file mode 100644 index 0000000000..1cb9d8f9d6 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx @@ -0,0 +1,215 @@ +"use client"; + +import Placement from "./Placement"; +import { PlacementType } from "@formbricks/types/js"; +import { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys"; +import { ColorPicker, Label, Switch } from "@formbricks/ui"; +import { CheckCircleIcon } from "@heroicons/react/24/solid"; +import * as Collapsible from "@radix-ui/react-collapsible"; +import { useState } from "react"; + +interface StylingCardProps { + localSurvey: TSurveyWithAnalytics; + setLocalSurvey: React.Dispatch>; +} + +export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCardProps) { + const [open, setOpen] = useState(false); + + const { type, productOverwrites } = localSurvey; + const { brandColor, clickOutside, darkOverlay, placement, highlightBorderColor } = productOverwrites ?? {}; + + const togglePlacement = () => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + placement: !!placement ? null : "bottomRight", + }, + }); + }; + + const toggleBrandColor = () => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + brandColor: !!brandColor ? null : "#64748b", + }, + }); + }; + + const toggleHighlightBorderColor = () => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + highlightBorderColor: !!highlightBorderColor ? null : "#64748b", + }, + }); + }; + + const handleColorChange = (color: string) => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + brandColor: color, + }, + }); + }; + + const handleBorderColorChange = (color: string) => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + highlightBorderColor: color, + }, + }); + }; + + const handlePlacementChange = (placement: PlacementType) => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + placement, + }, + }); + }; + + const handleOverlay = (overlayType: string) => { + const darkOverlay = overlayType === "dark"; + + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + darkOverlay, + }, + }); + }; + + const handleClickOutside = (clickOutside: boolean) => { + setLocalSurvey({ + ...localSurvey, + productOverwrites: { + ...localSurvey.productOverwrites, + clickOutside, + }, + }); + }; + + return ( + + +
+
+ +
+
+

Styling

+

Overwrite global styling settings

+
+
+
+ +
+
+ {/* Brand Color */} +
+
+ + +
+ {brandColor && ( +
+
+ + +
+
+ )} +
+ {/* positioning */} + {type !== "link" && ( +
+
+ + +
+ {placement && ( +
+
+
+ +
+
+
+ )} +
+ )} + {/* Highlight border */} + {type !== "link" && ( +
+
+ + +
+ {!!highlightBorderColor && ( +
+
+ +

Show highlight border

+
+ {!!highlightBorderColor && ( +
+ + +
+ )} +
+ )} +
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx index 6f3d1f29d9..69171d4e36 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx @@ -20,6 +20,7 @@ interface SurveyEditorProps { environment: TEnvironment; actionClasses: TActionClass[]; attributeClasses: TAttributeClass[]; + isEncryptionKeySet: boolean; } export default function SurveyEditor({ @@ -28,6 +29,7 @@ export default function SurveyEditor({ environment, actionClasses, attributeClasses, + isEncryptionKeySet, }: SurveyEditorProps): JSX.Element { const [activeView, setActiveView] = useState<"questions" | "settings">("questions"); const [activeQuestionId, setActiveQuestionId] = useState(null); @@ -49,6 +51,8 @@ export default function SurveyEditor({ if (localSurvey?.questions?.length && localSurvey.questions.length > 0) { setActiveQuestionId(localSurvey.questions[0].id); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [localSurvey?.type]); if (!localSurvey) { @@ -88,6 +92,7 @@ export default function SurveyEditor({ setLocalSurvey={setLocalSurvey} actionClasses={actionClasses} attributeClasses={attributeClasses} + isEncryptionKeySet={isEncryptionKeySet} /> )} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyMenuBar.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyMenuBar.tsx index 5fe3abef71..3291268377 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyMenuBar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyMenuBar.tsx @@ -3,6 +3,7 @@ import AlertDialog from "@/components/shared/AlertDialog"; import DeleteDialog from "@/components/shared/DeleteDialog"; import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown"; +import { QuestionType } from "@formbricks/types/questions"; import type { Survey } from "@formbricks/types/surveys"; import { TEnvironment } from "@formbricks/types/v1/environment"; import { TProduct } from "@formbricks/types/v1/product"; @@ -14,7 +15,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; @@ -97,6 +98,14 @@ export default function SurveyMenuBar({ }; const validateSurvey = (survey) => { + const existingLogicConditions = new Set(); + const existingQuestionIds = new Set(); + + if (survey.questions.length === 0) { + toast.error("Please add at least one question"); + return; + } + faultyQuestions = []; for (let index = 0; index < survey.questions.length; index++) { const question = survey.questions[index]; @@ -109,7 +118,67 @@ export default function SurveyMenuBar({ // if there are any faulty questions, the user won't be allowed to save the survey if (faultyQuestions.length > 0) { setInvalidQuestions(faultyQuestions); - toast.error("Please fill required fields"); + toast.error("Please fill all required fields."); + return false; + } + + for (const question of survey.questions) { + if (existingQuestionIds.has(question.id)) { + toast.error("There are 2 identical question IDs. Please update one."); + return false; + } + existingQuestionIds.add(question.id); + + if ( + question.type === QuestionType.MultipleChoiceSingle || + question.type === QuestionType.MultipleChoiceMulti + ) { + const haveSameChoices = + question.choices.some((element) => element.label.trim() === "") || + question.choices.some((element, index) => + question.choices + .slice(index + 1) + .some((nextElement) => nextElement.label.trim() === element.label.trim()) + ); + + if (haveSameChoices) { + toast.error("You have two identical choices."); + return false; + } + } + + for (const logic of question.logic || []) { + const validFields = ["condition", "destination", "value"].filter( + (field) => logic[field] !== undefined + ).length; + + if (validFields < 2) { + setInvalidQuestions([question.id]); + toast.error("Incomplete logic jumps detected: Please fill or delete them."); + return false; + } + + if (question.required && logic.condition === "skipped") { + toast.error("You have a missing logic condition. Please update or delete it."); + return false; + } + + const thisLogic = `${logic.condition}-${logic.value}`; + if (existingLogicConditions.has(thisLogic)) { + setInvalidQuestions([question.id]); + toast.error("You have 2 competing logic conditons. Please update or delete one."); + return false; + } + existingLogicConditions.add(thisLogic); + } + } + + if ( + survey.redirectUrl && + !survey.redirectUrl.includes("https://") && + !survey.redirectUrl.includes("http://") + ) { + toast.error("Please enter a valid URL for redirecting respondents."); return false; } @@ -128,6 +197,10 @@ export default function SurveyMenuBar({ }; const saveSurveyAction = async (shouldNavigateBack = false) => { + if (localSurvey.questions.length === 0) { + toast.error("Please add at least one question."); + return; + } setIsMutatingSurvey(true); // Create a copy of localSurvey with isDraft removed from every question const strippedSurvey: TSurvey = { @@ -139,11 +212,12 @@ export default function SurveyMenuBar({ }; if (!validateSurvey(localSurvey)) { + setIsMutatingSurvey(false); return; } try { - await surveyMutateAction({ ...strippedSurvey }); + await updateSurveyAction({ ...strippedSurvey }); router.refresh(); setIsMutatingSurvey(false); toast.success("Changes saved."); @@ -155,6 +229,7 @@ export default function SurveyMenuBar({ } else { router.push(`/environments/${environment.id}/surveys`); } + router.refresh(); } } catch (e) { console.error(e); @@ -239,9 +314,10 @@ export default function SurveyMenuBar({ onClick={async () => { setIsMutatingSurvey(true); if (!validateSurvey(localSurvey)) { + setIsMutatingSurvey(false); 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`); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/UpdateQuestionId.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/UpdateQuestionId.tsx index 1b18a11d26..08650618e8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/UpdateQuestionId.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/UpdateQuestionId.tsx @@ -7,6 +7,9 @@ import toast from "react-hot-toast"; export default function UpdateQuestionId({ localSurvey, question, questionIdx, updateQuestion }) { const [currentValue, setCurrentValue] = useState(question.id); const [prevValue, setPrevValue] = useState(question.id); + const [isInputInvalid, setIsInputInvalid] = useState( + currentValue.trim() === "" || currentValue.includes(" ") + ); const saveAction = () => { // return early if the input value was not changed @@ -14,28 +17,22 @@ export default function UpdateQuestionId({ localSurvey, question, questionIdx, u return; } - // check if id is unique const questionIds = localSurvey.questions.map((q) => q.id); if (questionIds.includes(currentValue)) { + setIsInputInvalid(true); toast.error("IDs have to be unique per survey."); - setCurrentValue(question.id); - return; - } - - // check if id contains any spaces - if (currentValue.trim() === "" || currentValue.includes(" ")) { - toast.error("ID should not contain space."); - setCurrentValue(question.id); - return; + } else if (currentValue.trim() === "" || currentValue.includes(" ")) { + setIsInputInvalid(true); + toast.error("ID should not be empty."); + } else { + setIsInputInvalid(false); + toast.success("Question ID updated."); } updateQuestion(questionIdx, { id: currentValue }); - toast.success("Question ID updated."); setPrevValue(currentValue); // after successful update, set current value as previous value }; - const isInputInvalid = currentValue.trim() === "" || currentValue.includes(" "); - return (
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Validation.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Validation.ts index a2830b8f50..09eb880cf7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Validation.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Validation.ts @@ -1,6 +1,7 @@ // extend this object in order to add more validation rules import { + TSurveyConsentQuestion, TSurveyMultipleChoiceMultiQuestion, TSurveyMultipleChoiceSingleQuestion, TSurveyQuestion, @@ -13,8 +14,16 @@ const validationRules = { multipleChoiceSingle: (question: TSurveyMultipleChoiceSingleQuestion) => { return !question.choices.some((element) => element.label.trim() === ""); }, + consent: (question: TSurveyConsentQuestion) => { + return question.label.trim() !== ""; + }, defaultValidation: (question: TSurveyQuestion) => { - return question.headline.trim() !== ""; + console.log(question); + return ( + question.headline.trim() !== "" && + question.buttonLabel?.trim() !== "" && + question.backButtonLabel?.trim() !== "" + ); }, }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts index ebd4ce8307..97a0304f2e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts @@ -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 { + 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 { 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); -} +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx index 1223a5a437..4080ecece9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx @@ -1,12 +1,12 @@ export const revalidate = REVALIDATION_INTERVAL; import React from "react"; -import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; +import { FORMBRICKS_ENCRYPTION_KEY, REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; import SurveyEditor from "./SurveyEditor"; -import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey"; -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getEnvironment } from "@formbricks/lib/environment/service"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; -import { getAttributeClasses } from "@formbricks/lib/services/attributeClass"; +import { getAttributeClasses } from "@formbricks/lib/attributeClass/service"; import { ErrorComponent } from "@formbricks/ui"; export default async function SurveysEditPage({ params }) { @@ -17,6 +17,7 @@ export default async function SurveysEditPage({ params }) { getActionClasses(params.environmentId), getAttributeClasses(params.environmentId), ]); + const isEncryptionKeySet = !!FORMBRICKS_ENCRYPTION_KEY; if (!survey || !environment || !actionClasses || !attributeClasses || !product) { return ; } @@ -29,6 +30,7 @@ export default async function SurveysEditPage({ params }) { environment={environment} actionClasses={actionClasses} attributeClasses={attributeClasses} + isEncryptionKeySet={isEncryptionKeySet} /> ); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts index bd3454703c..31240985e5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts @@ -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) { + 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); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx index 377d4a90e2..b010102b8a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx @@ -4,8 +4,8 @@ import { updateEnvironmentAction } from "@/app/(app)/environments/[environmentId import ContentWrapper from "@/components/shared/ContentWrapper"; import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getActionsByEnvironmentId } from "@formbricks/lib/services/actions"; -import { getEnvironment } from "@formbricks/lib/services/environment"; +import { getActionsByEnvironmentId } from "@formbricks/lib/action/service"; +import { getEnvironment } from "@formbricks/lib/environment/service"; import { Metadata } from "next"; import SurveysList from "./SurveyList"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateList.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateList.tsx index 96bfdac84d..832287750f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateList.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateList.tsx @@ -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; const survey = await createSurveyAction(environmentId, augmentedTemplate); router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/actions.ts index bd3454703c..33cb980756 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/actions.ts @@ -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) { + 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); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx index 9e4c312f3d..9d374f7822 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx @@ -1,6 +1,6 @@ import TemplateContainerWithPreview from "./TemplateContainer"; -import { getEnvironment } from "@formbricks/lib/services/environment"; -import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; +import { getEnvironment } from "@formbricks/lib/environment/service"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; export default async function SurveyTemplatesPage({ params }) { const environmentId = params.environmentId; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts index 656893783f..b32e373d4e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts @@ -2062,4 +2062,6 @@ export const minimalSurvey: TSurvey = { surveyClosedMessage: { enabled: false, }, + productOverwrites: null, + singleUse: null, }; diff --git a/apps/web/app/(app)/onboarding/actions.ts b/apps/web/app/(app)/onboarding/actions.ts index 89537f0007..6608688625 100644 --- a/apps/web/app/(app)/onboarding/actions.ts +++ b/apps/web/app/(app)/onboarding/actions.ts @@ -1,8 +1,8 @@ "use server"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; -import { updateProduct } from "@formbricks/lib/services/product"; -import { updateProfile } from "@formbricks/lib/services/profile"; +import { updateProduct } from "@formbricks/lib/product/service"; +import { updateProfile } from "@formbricks/lib/profile/service"; import { TProductUpdateInput } from "@formbricks/types/v1/product"; import { TProfileUpdateInput } from "@formbricks/types/v1/profile"; import { getServerSession } from "next-auth"; diff --git a/apps/web/app/(app)/onboarding/components/Objective.tsx b/apps/web/app/(app)/onboarding/components/Objective.tsx index c69bd36c85..d0b406604e 100644 --- a/apps/web/app/(app)/onboarding/components/Objective.tsx +++ b/apps/web/app/(app)/onboarding/components/Objective.tsx @@ -41,7 +41,11 @@ const Objective: React.FC = ({ 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) { diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index 0997cd6f38..bf9d39247a 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -1,23 +1,31 @@ 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/environment/service"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getProfile } from "@formbricks/lib/profile/service"; 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); - const profile = await getProfile(session?.user.id!); + if (!session) { + throw new Error("No session found"); + } + const userId = session?.user.id; + const environment = await getFirstEnvironmentByUserId(userId); + + if (!environment) { + throw new Error("No environment found for user"); + } + + const profile = await getProfile(userId); const product = await getProductByEnvironmentId(environment?.id!); if (!environment || !profile || !product) { - return ; + throw new Error("Failed to get environment, profile, or product"); } - return ; + return ; } diff --git a/apps/web/app/(redirects)/products/[productId]/route.ts b/apps/web/app/(redirects)/products/[productId]/route.ts index 1ff1bc6506..730cddadd8 100644 --- a/apps/web/app/(redirects)/products/[productId]/route.ts +++ b/apps/web/app/(redirects)/products/[productId]/route.ts @@ -1,7 +1,7 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; import { hasTeamAccess } from "@/lib/api/apiHelper"; -import { getEnvironments } from "@formbricks/lib/services/environment"; -import { getProduct } from "@formbricks/lib/services/product"; +import { getEnvironments } from "@formbricks/lib/environment/service"; +import { getProduct } from "@formbricks/lib/product/service"; import { AuthenticationError, AuthorizationError } from "@formbricks/types/v1/errors"; import { getServerSession } from "next-auth"; import { notFound, redirect } from "next/navigation"; diff --git a/apps/web/app/(redirects)/teams/[teamId]/route.ts b/apps/web/app/(redirects)/teams/[teamId]/route.ts index 7b67901dfe..d392b2582c 100644 --- a/apps/web/app/(redirects)/teams/[teamId]/route.ts +++ b/apps/web/app/(redirects)/teams/[teamId]/route.ts @@ -1,7 +1,7 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; import { hasTeamAccess } from "@/lib/api/apiHelper"; -import { getEnvironments } from "@formbricks/lib/services/environment"; -import { getProducts } from "@formbricks/lib/services/product"; +import { getEnvironments } from "@formbricks/lib/environment/service"; +import { getProducts } from "@formbricks/lib/product/service"; import { AuthenticationError, AuthorizationError } from "@formbricks/types/v1/errors"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; diff --git a/apps/web/app/api/auth/[...nextauth]/authOptions.ts b/apps/web/app/api/auth/[...nextauth]/authOptions.ts index 5dbdefac8b..29e66a9ca7 100644 --- a/apps/web/app/api/auth/[...nextauth]/authOptions.ts +++ b/apps/web/app/api/auth/[...nextauth]/authOptions.ts @@ -3,7 +3,7 @@ import { verifyPassword } from "@/lib/auth"; import { prisma } from "@formbricks/database"; import { EMAIL_VERIFICATION_DISABLED, INTERNAL_SECRET, WEBAPP_URL } from "@formbricks/lib/constants"; import { verifyToken } from "@formbricks/lib/jwt"; -import { getProfileByEmail } from "@formbricks/lib/services/profile"; +import { getProfileByEmail } from "@formbricks/lib/profile/service"; import type { IdentityProvider } from "@prisma/client"; import type { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; diff --git a/apps/web/app/api/integration/integrations.ts b/apps/web/app/api/integration/integrations.ts index 3ffbc383d7..09ba00fb17 100644 --- a/apps/web/app/api/integration/integrations.ts +++ b/apps/web/app/api/integration/integrations.ts @@ -1,5 +1,5 @@ -import { writeData } from "@formbricks/lib/services/googleSheet"; -import { getSurvey } from "@formbricks/lib/services/survey"; +import { writeData } from "@formbricks/lib/googleSheet/service"; +import { getSurvey } from "@formbricks/lib/survey/service"; import { TGoogleSheetIntegration, TIntegration } from "@formbricks/types/v1/integrations"; import { TPipelineInput } from "@formbricks/types/v1/pipelines"; diff --git a/apps/web/app/api/v1/client/displays/[displayId]/responded/route.ts b/apps/web/app/api/v1/client/displays/[displayId]/responded/route.ts index 8901832fc4..c9d6710216 100644 --- a/apps/web/app/api/v1/client/displays/[displayId]/responded/route.ts +++ b/apps/web/app/api/v1/client/displays/[displayId]/responded/route.ts @@ -1,5 +1,5 @@ import { responses } from "@/lib/api/response"; -import { markDisplayResponded } from "@formbricks/lib/services/displays"; +import { markDisplayResponded } from "@formbricks/lib/display/service"; import { NextResponse } from "next/server"; export async function OPTIONS(): Promise { diff --git a/apps/web/app/api/v1/client/displays/route.ts b/apps/web/app/api/v1/client/displays/route.ts index e51d6639af..d70800e546 100644 --- a/apps/web/app/api/v1/client/displays/route.ts +++ b/apps/web/app/api/v1/client/displays/route.ts @@ -2,9 +2,9 @@ import { responses } from "@/lib/api/response"; 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 { getTeamDetails } from "@formbricks/lib/services/teamDetails"; +import { createDisplay } from "@formbricks/lib/display/service"; +import { getSurvey } from "@formbricks/lib/survey/service"; +import { getTeamDetails } from "@formbricks/lib/teamDetail/service"; import { TDisplay, ZDisplayInput } from "@formbricks/types/v1/displays"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/api/v1/client/responses/[responseId]/route.ts b/apps/web/app/api/v1/client/responses/[responseId]/route.ts index aba81b2fb4..59edb5b4cb 100644 --- a/apps/web/app/api/v1/client/responses/[responseId]/route.ts +++ b/apps/web/app/api/v1/client/responses/[responseId]/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/client/responses/route.ts b/apps/web/app/api/v1/client/responses/route.ts index aac5cbbabc..72595bbde8 100644 --- a/apps/web/app/api/v1/client/responses/route.ts +++ b/apps/web/app/api/v1/client/responses/route.ts @@ -3,9 +3,9 @@ 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 { getTeamDetails } from "@formbricks/lib/teamDetail/service"; import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/v1/responses"; import { NextResponse } from "next/server"; import { UAParser } from "ua-parser-js"; diff --git a/apps/web/app/api/v1/js/actions/route.ts b/apps/web/app/api/v1/js/actions/route.ts index 883f95260c..f5fcdc1ff2 100644 --- a/apps/web/app/api/v1/js/actions/route.ts +++ b/apps/web/app/api/v1/js/actions/route.ts @@ -1,6 +1,6 @@ import { responses } from "@/lib/api/response"; import { transformErrorToDetails } from "@/lib/api/validator"; -import { createAction } from "@formbricks/lib/services/actions"; +import { createAction } from "@formbricks/lib/action/service"; import { ZJsActionInput } from "@formbricks/types/v1/js"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts b/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts index 10cafc9191..208692f88d 100644 --- a/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts +++ b/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts @@ -1,8 +1,8 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync"; import { responses } from "@/lib/api/response"; import { transformErrorToDetails } from "@/lib/api/validator"; -import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/services/attributeClass"; -import { getPersonCached, updatePersonAttribute } from "@formbricks/lib/services/person"; +import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/attributeClass/service"; +import { getPersonCached, updatePersonAttribute } from "@formbricks/lib/person/service"; import { ZJsPeopleAttributeInput } from "@formbricks/types/v1/js"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts b/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts index 2d2197ae70..0b8fa446c1 100644 --- a/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts +++ b/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts @@ -2,7 +2,7 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync"; import { responses } from "@/lib/api/response"; import { transformErrorToDetails } from "@/lib/api/validator"; import { prisma } from "@formbricks/database"; -import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/services/person"; +import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/person/service"; import { ZJsPeopleUserIdInput } from "@formbricks/types/v1/js"; import { revalidateTag } from "next/cache"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/api/v1/js/surveys.ts b/apps/web/app/api/v1/js/surveys.ts index 21095d928a..bd0a77f446 100644 --- a/apps/web/app/api/v1/js/surveys.ts +++ b/apps/web/app/api/v1/js/surveys.ts @@ -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"; @@ -164,6 +164,7 @@ export const getSurveys = async (environmentId: string, person: TPerson): Promis }) .map((survey) => ({ ...survey, + singleUse: survey.singleUse ? JSON.parse(JSON.stringify(survey.singleUse)) : null, triggers: survey.triggers.map((trigger) => trigger.eventClass), attributeFilters: survey.attributeFilters.map((af) => ({ ...af, diff --git a/apps/web/app/api/v1/js/sync/lib/sync.ts b/apps/web/app/api/v1/js/sync/lib/sync.ts index e865231f6e..a180fa82af 100644 --- a/apps/web/app/api/v1/js/sync/lib/sync.ts +++ b/apps/web/app/api/v1/js/sync/lib/sync.ts @@ -1,10 +1,10 @@ import { getSurveysCached } from "@/app/api/v1/js/surveys"; import { MAU_LIMIT } from "@formbricks/lib/constants"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; -import { getEnvironment } from "@formbricks/lib/services/environment"; -import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/services/person"; -import { getProductByEnvironmentIdCached } from "@formbricks/lib/services/product"; -import { createSession, extendSession, getSessionCached } from "@formbricks/lib/services/session"; +import { getEnvironment } from "@formbricks/lib/environment/service"; +import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/person/service"; +import { getProductByEnvironmentIdCached } from "@formbricks/lib/product/service"; +import { createSession, extendSession, getSessionCached } from "@formbricks/lib/session/service"; import { captureTelemetry } from "@formbricks/lib/telemetry"; import { TEnvironment } from "@formbricks/types/v1/environment"; import { TJsState } from "@formbricks/types/v1/js"; diff --git a/apps/web/app/api/v1/management/attribute-classes/[attributeClassId]/route.ts b/apps/web/app/api/v1/management/attribute-classes/[attributeClassId]/route.ts index f46f0145f9..fe2363263d 100644 --- a/apps/web/app/api/v1/management/attribute-classes/[attributeClassId]/route.ts +++ b/apps/web/app/api/v1/management/attribute-classes/[attributeClassId]/route.ts @@ -5,7 +5,7 @@ import { deleteAttributeClass, getAttributeClass, updatetAttributeClass, -} from "@formbricks/lib/services/attributeClass"; +} from "@formbricks/lib/attributeClass/service"; import { TAttributeClass, ZAttributeClassUpdateInput } from "@formbricks/types/v1/attributeClasses"; import { transformErrorToDetails } from "@/lib/api/validator"; import { authenticateRequest } from "@/app/api/v1/auth"; diff --git a/apps/web/app/api/v1/management/attribute-classes/route.ts b/apps/web/app/api/v1/management/attribute-classes/route.ts index ca0b33f2c6..48a5aeb8d1 100644 --- a/apps/web/app/api/v1/management/attribute-classes/route.ts +++ b/apps/web/app/api/v1/management/attribute-classes/route.ts @@ -4,7 +4,7 @@ import { authenticateRequest } from "@/app/api/v1/auth"; import { NextResponse } from "next/server"; import { transformErrorToDetails } from "@/lib/api/validator"; import { TAttributeClass, ZAttributeClassInput } from "@formbricks/types/v1/attributeClasses"; -import { createAttributeClass, getAttributeClasses } from "@formbricks/lib/services/attributeClass"; +import { createAttributeClass, getAttributeClasses } from "@formbricks/lib/attributeClass/service"; export async function GET(request: Request) { try { diff --git a/apps/web/app/api/v1/management/people/[personId]/route.ts b/apps/web/app/api/v1/management/people/[personId]/route.ts index 420d62f77c..f4511bbaa3 100644 --- a/apps/web/app/api/v1/management/people/[personId]/route.ts +++ b/apps/web/app/api/v1/management/people/[personId]/route.ts @@ -1,6 +1,6 @@ import { authenticateRequest, handleErrorResponse } from "@/app/api/v1/auth"; import { responses } from "@/lib/api/response"; -import { deletePerson, getPerson } from "@formbricks/lib/services/person"; +import { deletePerson, getPerson } from "@formbricks/lib/person/service"; import { TAuthenticationApiKey } from "@formbricks/types/v1/auth"; import { TPerson } from "@formbricks/types/v1/people"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/api/v1/management/people/route.ts b/apps/web/app/api/v1/management/people/route.ts index ff36af3654..7be8d4ed7e 100644 --- a/apps/web/app/api/v1/management/people/route.ts +++ b/apps/web/app/api/v1/management/people/route.ts @@ -1,6 +1,6 @@ import { authenticateRequest } from "@/app/api/v1/auth"; import { responses } from "@/lib/api/response"; -import { getPeople } from "@formbricks/lib/services/person"; +import { getPeople } from "@formbricks/lib/person/service"; import { DatabaseError } from "@formbricks/types/v1/errors"; import { TPerson } from "@formbricks/types/v1/people"; diff --git a/apps/web/app/api/v1/management/responses/[responseId]/route.ts b/apps/web/app/api/v1/management/responses/[responseId]/route.ts index 7941a82eea..4158e05bc0 100644 --- a/apps/web/app/api/v1/management/responses/[responseId]/route.ts +++ b/apps/web/app/api/v1/management/responses/[responseId]/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/management/surveys/[surveyId]/route.ts b/apps/web/app/api/v1/management/surveys/[surveyId]/route.ts index c25e2ec9a1..cc096f9caa 100644 --- a/apps/web/app/api/v1/management/surveys/[surveyId]/route.ts +++ b/apps/web/app/api/v1/management/surveys/[surveyId]/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/management/surveys/route.ts b/apps/web/app/api/v1/management/surveys/route.ts index 1ced6ec5d9..4a7d3eb939 100644 --- a/apps/web/app/api/v1/management/surveys/route.ts +++ b/apps/web/app/api/v1/management/surveys/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts b/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts index f1b2b818af..ca2b18b0f4 100644 --- a/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts +++ b/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts @@ -1,5 +1,5 @@ import { INTERNAL_SECRET } from "@formbricks/lib/constants"; -import { createDemoProduct } from "@formbricks/lib/services/team"; +import { createDemoProduct } from "@formbricks/lib/team/service"; import { NextResponse } from "next/server"; import { headers } from "next/headers"; import { responses } from "@/lib/api/response"; diff --git a/apps/web/app/api/v1/users/route.ts b/apps/web/app/api/v1/users/route.ts index 16fefd3f2c..bcb482e079 100644 --- a/apps/web/app/api/v1/users/route.ts +++ b/apps/web/app/api/v1/users/route.ts @@ -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/invite/service"; +import { createMembership } from "@formbricks/lib/membership/service"; +import { createProduct } from "@formbricks/lib/product/service"; +import { createProfile } from "@formbricks/lib/profile/service"; +import { createTeam } from "@formbricks/lib/team/service"; 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( diff --git a/apps/web/app/api/v1/webhooks/[webhookId]/route.ts b/apps/web/app/api/v1/webhooks/[webhookId]/route.ts index f0d51dcba9..7616554406 100644 --- a/apps/web/app/api/v1/webhooks/[webhookId]/route.ts +++ b/apps/web/app/api/v1/webhooks/[webhookId]/route.ts @@ -1,6 +1,6 @@ import { responses } from "@/lib/api/response"; import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service"; -import { deleteWebhook, getWebhook } from "@formbricks/lib/services/webhook"; +import { deleteWebhook, getWebhook } from "@formbricks/lib/webhook/service"; import { headers } from "next/headers"; export async function GET(_: Request, { params }: { params: { webhookId: string } }) { diff --git a/apps/web/app/api/v1/webhooks/route.ts b/apps/web/app/api/v1/webhooks/route.ts index ae1fd52d05..cd9924c4ea 100644 --- a/apps/web/app/api/v1/webhooks/route.ts +++ b/apps/web/app/api/v1/webhooks/route.ts @@ -2,7 +2,7 @@ import { responses } from "@/lib/api/response"; import { transformErrorToDetails } from "@/lib/api/validator"; import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service"; import { DatabaseError, InvalidInputError } from "@formbricks/types/v1/errors"; -import { createWebhook, getWebhooks } from "@formbricks/lib/services/webhook"; +import { createWebhook, getWebhooks } from "@formbricks/lib/webhook/service"; import { ZWebhookInput } from "@formbricks/types/v1/webhooks"; import { headers } from "next/headers"; import { NextResponse } from "next/server"; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 30ac126219..9e23d1606d 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -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/environment/service"; 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); } diff --git a/apps/web/app/s/[surveyId]/LinkSurvey.tsx b/apps/web/app/s/[surveyId]/LinkSurvey.tsx index 3ea44c6f74..a42d618f2f 100644 --- a/apps/web/app/s/[surveyId]/LinkSurvey.tsx +++ b/apps/web/app/s/[surveyId]/LinkSurvey.tsx @@ -12,7 +12,8 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import VerifyEmail from "@/app/s/[surveyId]/VerifyEmail"; import { getPrefillResponseData } from "@/app/s/[surveyId]/prefilling"; -import { TResponseData } from "@formbricks/types/v1/responses"; +import { TResponse, TResponseData } from "@formbricks/types/v1/responses"; +import SurveyLinkUsed from "@/app/s/[surveyId]/SurveyLinkUsed"; interface LinkSurveyProps { survey: TSurvey; @@ -20,6 +21,8 @@ interface LinkSurveyProps { personId?: string; emailVerificationStatus?: string; prefillAnswer?: string; + singleUseId?: string; + singleUseResponse?: TResponse; webAppUrl: string; } @@ -29,16 +32,22 @@ export default function LinkSurvey({ personId, emailVerificationStatus, prefillAnswer, + singleUseId, + singleUseResponse, webAppUrl, }: LinkSurveyProps) { + const responseId = singleUseResponse?.id; const searchParams = useSearchParams(); const isPreview = searchParams?.get("preview") === "true"; - const [surveyState, setSurveyState] = useState(new SurveyState(survey.id)); + // pass in the responseId if the survey is a single use survey, ensures survey state is updated with the responseId + const [surveyState, setSurveyState] = useState(new SurveyState(survey.id, singleUseId, responseId)); const [activeQuestionId, setActiveQuestionId] = useState(survey.questions[0].id); const prefillResponseData: TResponseData | undefined = prefillAnswer ? getPrefillResponseData(survey.questions[0], survey, prefillAnswer) : undefined; + const brandColor = survey.productOverwrites?.brandColor || product.brandColor; + const responseQueue = useMemo( () => new ResponseQueue( @@ -56,6 +65,13 @@ export default function LinkSurvey({ [personId, webAppUrl] ); const [autoFocus, setAutofocus] = useState(false); + const hasFinishedSingleUseResponse = useMemo(() => { + if (singleUseResponse && singleUseResponse.finished) { + return true; + } + return false; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Not in an iframe, enable autofocus on input fields. useEffect(() => { @@ -68,6 +84,10 @@ export default function LinkSurvey({ responseQueue.updateSurveyState(surveyState); }, [responseQueue, surveyState]); + if (!surveyState.isResponseFinished() && hasFinishedSingleUseResponse) { + return ; + } + if (emailVerificationStatus && emailVerificationStatus !== "verified") { if (emailVerificationStatus === "fishy") { return ; @@ -92,7 +112,7 @@ export default function LinkSurvey({ )} { if (!isPreview) { diff --git a/apps/web/app/s/[surveyId]/SurveyInactive.tsx b/apps/web/app/s/[surveyId]/SurveyInactive.tsx index 1796b10665..233fca2be8 100644 --- a/apps/web/app/s/[surveyId]/SurveyInactive.tsx +++ b/apps/web/app/s/[surveyId]/SurveyInactive.tsx @@ -1,6 +1,6 @@ import { TSurveyClosedMessage } from "@formbricks/types/v1/surveys"; import { Button } from "@formbricks/ui"; -import { CheckCircleIcon, PauseCircleIcon } from "@heroicons/react/24/solid"; +import { CheckCircleIcon, PauseCircleIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/solid"; import Image from "next/image"; import Link from "next/link"; import footerLogo from "./footerlogo.svg"; @@ -9,17 +9,19 @@ const SurveyInactive = ({ status, surveyClosedMessage, }: { - status: "paused" | "completed"; + status: "paused" | "completed" | "link invalid"; surveyClosedMessage?: TSurveyClosedMessage | null; }) => { const icons = { paused: , completed: , + "link invalid": , }; const descriptions = { paused: "This free & open-source survey is temporarily paused.", completed: "This free & open-source survey has been closed.", + "link invalid": "This survey can only be taken by invitation.", }; return ( @@ -31,12 +33,11 @@ const SurveyInactive = ({ {status === "completed" && surveyClosedMessage ? surveyClosedMessage.heading : `Survey ${status}.`}

- {" "} {status === "completed" && surveyClosedMessage ? surveyClosedMessage.subheading : descriptions[status]}

- {!(status === "completed" && surveyClosedMessage) && ( + {!(status === "completed" && surveyClosedMessage) && status !== "link invalid" && ( diff --git a/apps/web/app/s/[surveyId]/SurveyLinkUsed.tsx b/apps/web/app/s/[surveyId]/SurveyLinkUsed.tsx new file mode 100644 index 0000000000..c9f694622b --- /dev/null +++ b/apps/web/app/s/[surveyId]/SurveyLinkUsed.tsx @@ -0,0 +1,35 @@ +import { SurveySingleUse } from "@formbricks/types/surveys"; +import { CheckCircleIcon } from "@heroicons/react/24/solid"; +import Image from "next/image"; +import Link from "next/link"; +import footerLogo from "./footerlogo.svg"; + +type SurveyLinkUsedProps = { + singleUseMessage: Omit | null; +}; + +const SurveyLinkUsed = ({ singleUseMessage }: SurveyLinkUsedProps) => { + const defaultHeading = "The survey has already been answered."; + const defaultSubheading = "You can only use this link once."; + return ( +
+
+
+ +

+ {!!singleUseMessage?.heading ? singleUseMessage?.heading : defaultHeading} +

+

+ {!!singleUseMessage?.subheading ? singleUseMessage?.subheading : defaultSubheading} +

+
+
+ + Brand logo + +
+
+ ); +}; + +export default SurveyLinkUsed; diff --git a/apps/web/app/s/[surveyId]/page.tsx b/apps/web/app/s/[surveyId]/page.tsx index c9fba5ec42..7eb4bc1475 100644 --- a/apps/web/app/s/[surveyId]/page.tsx +++ b/apps/web/app/s/[surveyId]/page.tsx @@ -3,15 +3,32 @@ export const revalidate = REVALIDATION_INTERVAL; import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey"; 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 { getOrCreatePersonByUserId } from "@formbricks/lib/person/service"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getSurvey } from "@formbricks/lib/survey/service"; import { getEmailVerificationStatus } from "./helpers"; import { checkValidity } from "@/app/s/[surveyId]/prefilling"; import { notFound } from "next/navigation"; +import { getResponseBySingleUseId } from "@formbricks/lib/response/service"; +import { TResponse } from "@formbricks/types/v1/responses"; +import { validateSurveySingleUseId } from "@/lib/singleUseSurveys"; -export default async function LinkSurveyPage({ params, searchParams }) { +interface LinkSurveyPageProps { + params: { + surveyId: string; + }; + searchParams: { + suId?: string; + userId?: string; + verify?: string; + }; +} + +export default async function LinkSurveyPage({ params, searchParams }: LinkSurveyPageProps) { const survey = await getSurvey(params.surveyId); + const suId = searchParams.suId; + const isSingleUseSurvey = survey?.singleUse?.enabled; + const isSingleUseSurveyEncrypted = survey?.singleUse?.isEncrypted; if (!survey || survey.type !== "link" || survey.status === "draft") { notFound(); @@ -30,14 +47,41 @@ export default async function LinkSurveyPage({ params, searchParams }) { ); } + let singleUseId: string | undefined = undefined; + if (isSingleUseSurvey) { + // check if the single use id is present for single use surveys + if (!suId) { + return ; + } + + // if encryption is enabled, validate the single use id + let validatedSingleUseId: string | undefined = undefined; + if (isSingleUseSurveyEncrypted) { + validatedSingleUseId = validateSurveySingleUseId(suId); + if (!validatedSingleUseId) { + return ; + } + } + // if encryption is disabled, use the suId as is + singleUseId = validatedSingleUseId ?? suId; + } + + let singleUseResponse: TResponse | undefined = undefined; + if (isSingleUseSurvey) { + singleUseResponse = (await getResponseBySingleUseId(survey.id, singleUseId)) ?? undefined; + } + // verify email: Check if the survey requires email verification - let emailVerificationStatus; + let emailVerificationStatus: string | undefined = undefined; if (survey.verifyEmail) { const token = searchParams && Object.keys(searchParams).length !== 0 && searchParams.hasOwnProperty("verify") ? searchParams.verify : undefined; - emailVerificationStatus = await getEmailVerificationStatus(survey.id, token); + + if (token) { + emailVerificationStatus = await getEmailVerificationStatus(survey.id, token); + } } // get product and person @@ -59,6 +103,8 @@ export default async function LinkSurveyPage({ params, searchParams }) { personId={person?.id} emailVerificationStatus={emailVerificationStatus} prefillAnswer={isPrefilledAnswerValid ? prefillAnswer : null} + singleUseId={isSingleUseSurvey ? singleUseId : undefined} + singleUseResponse={singleUseResponse ? singleUseResponse : undefined} webAppUrl={WEBAPP_URL} /> ); diff --git a/apps/web/components/environments/SecondNavBar.tsx b/apps/web/components/environments/SecondNavBar.tsx index ec553fcf10..c0c9751a5c 100644 --- a/apps/web/components/environments/SecondNavBar.tsx +++ b/apps/web/components/environments/SecondNavBar.tsx @@ -1,8 +1,8 @@ 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 { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getSurvey } from "@formbricks/lib/survey/service"; interface SecondNavbarProps { tabs: { id: string; label: string; href: string; icon?: React.ReactNode }[]; diff --git a/apps/web/components/shared/EnvironmentNotice.tsx b/apps/web/components/shared/EnvironmentNotice.tsx index 0212673a65..e61d68d7a0 100644 --- a/apps/web/components/shared/EnvironmentNotice.tsx +++ b/apps/web/components/shared/EnvironmentNotice.tsx @@ -1,4 +1,4 @@ -import { getEnvironments } from "@formbricks/lib/services/environment"; +import { getEnvironments } from "@formbricks/lib/environment/service"; import { TEnvironment } from "@formbricks/types/v1/environment"; import { LightBulbIcon } from "@heroicons/react/24/outline"; import { headers } from "next/headers"; diff --git a/apps/web/components/shared/SurveyStatusDropdown.tsx b/apps/web/components/shared/SurveyStatusDropdown.tsx index 63e508b6c5..74941fa02a 100644 --- a/apps/web/components/shared/SurveyStatusDropdown.tsx +++ b/apps/web/components/shared/SurveyStatusDropdown.tsx @@ -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" diff --git a/apps/web/components/team/CreateTeamModal.tsx b/apps/web/components/team/CreateTeamModal.tsx index 750f5c6eeb..b6d48ddf81 100644 --- a/apps/web/components/team/CreateTeamModal.tsx +++ b/apps/web/components/team/CreateTeamModal.tsx @@ -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}`); diff --git a/apps/web/env.mjs b/apps/web/env.mjs index e944c85b9b..4264843f21 100644 --- a/apps/web/env.mjs +++ b/apps/web/env.mjs @@ -70,6 +70,7 @@ export const env = createEnv({ NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional(), NEXT_PUBLIC_POSTHOG_API_HOST: z.string().optional(), NEXT_PUBLIC_SENTRY_DSN: z.string().optional(), + FORMBRICKS_ENCRYPTION_KEY: z.string().length(24).or(z.string().length(0)).optional(), }, /* * Due to how Next.js bundles environment variables on Edge and Client, @@ -114,6 +115,7 @@ export const env = createEnv({ IS_FORMBRICKS_CLOUD: process.env.IS_FORMBRICKS_CLOUD, NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY, NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST, + FORMBRICKS_ENCRYPTION_KEY: process.env.FORMBRICKS_ENCRYPTION_KEY, VERCEL_URL: process.env.VERCEL_URL, SURVEY_BASE_URL: process.env.SURVEY_BASE_URL, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, diff --git a/apps/web/lib/populate.ts b/apps/web/lib/populate.ts deleted file mode 100644 index e36684173d..0000000000 --- a/apps/web/lib/populate.ts +++ /dev/null @@ -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 }, - ], - }, -}; diff --git a/apps/web/lib/singleUseSurveys.ts b/apps/web/lib/singleUseSurveys.ts new file mode 100644 index 0000000000..dff1e89a94 --- /dev/null +++ b/apps/web/lib/singleUseSurveys.ts @@ -0,0 +1,33 @@ +import { FORMBRICKS_ENCRYPTION_KEY } from "@formbricks/lib/constants"; +import { decryptAES128, encryptAES128 } from "@formbricks/lib/crypto"; +import cuid2 from "@paralleldrive/cuid2"; + +// generate encrypted single use id for the survey +export const generateSurveySingleUseId = (isEncrypted: boolean): string => { + const cuid = cuid2.createId(); + if (!isEncrypted) { + return cuid; + } + if (!FORMBRICKS_ENCRYPTION_KEY) { + throw new Error("FORMBRICKS_ENCRYPTION_KEY is not defined"); + } + const encryptedCuid = encryptAES128(FORMBRICKS_ENCRYPTION_KEY, cuid); + return encryptedCuid; +}; + +// validate the survey single use id +export const validateSurveySingleUseId = (surveySingleUseId: string): string | undefined => { + if (!FORMBRICKS_ENCRYPTION_KEY) { + throw new Error("FORMBRICKS_ENCRYPTION_KEY is not defined"); + } + try { + const decryptedCuid = decryptAES128(FORMBRICKS_ENCRYPTION_KEY!, surveySingleUseId); + if (cuid2.isCuid(decryptedCuid)) { + return decryptedCuid; + } else { + return undefined; + } + } catch (error) { + return undefined; + } +}; diff --git a/apps/web/pages/api/v1/client/environments/[environmentId]/responses/[responseId]/index.ts b/apps/web/pages/api/v1/client/environments/[environmentId]/responses/[responseId]/index.ts index 56b184d45c..8f73511b48 100644 --- a/apps/web/pages/api/v1/client/environments/[environmentId]/responses/[responseId]/index.ts +++ b/apps/web/pages/api/v1/client/environments/[environmentId]/responses/[responseId]/index.ts @@ -64,6 +64,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) data: true, meta: true, personAttributes: true, + singleUseId: true, person: { select: { id: true, diff --git a/apps/web/pages/api/v1/client/environments/[environmentId]/responses/index.ts b/apps/web/pages/api/v1/client/environments/[environmentId]/responses/index.ts index 6cbdbbd020..0245f53184 100644 --- a/apps/web/pages/api/v1/client/environments/[environmentId]/responses/index.ts +++ b/apps/web/pages/api/v1/client/environments/[environmentId]/responses/index.ts @@ -109,6 +109,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) data: true, meta: true, personAttributes: true, + singleUseId: true, person: { select: { id: true, diff --git a/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts index d261973c77..cd7888f969 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts @@ -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/product/service"; 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); diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts index dead5819ef..24df961675 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts @@ -146,6 +146,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) }, }, surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull, + singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull, + productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull, verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull, }, }); diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts index 36b10ad539..858a08e29e 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts @@ -66,6 +66,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) }, }, surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull, + singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull, + productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull, verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull, }, }); diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/index.ts index 02fde3133d..f5650ff72c 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/index.ts @@ -98,6 +98,10 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) body.surveyClosedMessage = prismaClient.JsonNull; } + if (!body.singleUse) { + body.singleUse = prismaClient.JsonNull; + } + if (!body.verifyEmail) { body.verifyEmail = prismaClient.JsonNull; } @@ -230,6 +234,10 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) data.surveyClosedMessage = prismaClient.JsonNull; } + if (data.singleUse === null) { + data.singleUse = prismaClient.JsonNull; + } + if (data.verifyEmail === null) { data.verifyEmail = prismaClient.JsonNull; } diff --git a/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png new file mode 100644 index 0000000000..4265c54486 Binary files /dev/null and b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png differ diff --git a/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png new file mode 100644 index 0000000000..44471ed920 Binary files /dev/null and b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png differ diff --git a/packages/database/jsonTypes.ts b/packages/database/jsonTypes.ts index 15cfe1f09c..f751e931ab 100644 --- a/packages/database/jsonTypes.ts +++ b/packages/database/jsonTypes.ts @@ -3,7 +3,9 @@ import { TIntegrationConfig } from "@formbricks/types/v1/integrations"; import { TResponsePersonAttributes, TResponseData, TResponseMeta } from "@formbricks/types/v1/responses"; import { TSurveyClosedMessage, + TSurveyProductOverwrites, TSurveyQuestions, + TSurveySingleUse, TSurveyThankYouCard, TSurveyVerifyEmail, } from "@formbricks/types/v1/surveys"; @@ -19,7 +21,9 @@ declare global { export type ResponsePersonAttributes = TResponsePersonAttributes; export type SurveyQuestions = TSurveyQuestions; export type SurveyThankYouCard = TSurveyThankYouCard; + export type SurveyProductOverwrites = TSurveyProductOverwrites; export type SurveyClosedMessage = TSurveyClosedMessage; + export type SurveySingleUse = TSurveySingleUse; export type SurveyVerifyEmail = TSurveyVerifyEmail; export type UserNotificationSettings = TUserNotificationSettings; } diff --git a/packages/database/migrations/20231003113835_add_single_use_id_to_survey/migration.sql b/packages/database/migrations/20231003113835_add_single_use_id_to_survey/migration.sql new file mode 100644 index 0000000000..f73c2ae2aa --- /dev/null +++ b/packages/database/migrations/20231003113835_add_single_use_id_to_survey/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - A unique constraint covering the columns `[surveyId,singleUseId]` on the table `Response` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "Response" ADD COLUMN "singleUseId" TEXT; + +-- AlterTable +ALTER TABLE "Survey" ADD COLUMN "singleUse" JSONB DEFAULT '{"enabled": false, "isEncrypted": true}'; + +-- CreateIndex +CREATE UNIQUE INDEX "Response_surveyId_singleUseId_key" ON "Response"("surveyId", "singleUseId"); diff --git a/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql b/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql new file mode 100644 index 0000000000..c4f6a4c876 --- /dev/null +++ b/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Survey" ADD COLUMN "productOverwrites" JSONB; diff --git a/packages/database/package.json b/packages/database/package.json index 441f505e79..61f20cc9e4 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -36,7 +36,7 @@ "prisma": "^5.3.1", "prisma-dbml-generator": "^0.10.0", "prisma-json-types-generator": "^3.0.1", - "zod": "^3.22.2", + "zod": "^3.22.3", "zod-prisma": "^0.5.4" } } diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 0b2305104a..48825e745d 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -113,6 +113,10 @@ model Response { /// @zod.custom(imports.ZResponsePersonAttributes) /// [ResponsePersonAttributes] personAttributes Json? + // singleUseId, used to prevent multiple responses + singleUseId String? + + @@unique([surveyId, singleUseId]) } model ResponseNote { @@ -245,9 +249,18 @@ model Survey { /// @zod.custom(imports.ZSurveyClosedMessage) /// [SurveyClosedMessage] surveyClosedMessage Json? + /// @zod.custom(imports.ZSurveySingleUse) + /// [SurveySingleUse] + + /// @zod.custom(imports.ZSurveyProductOverwrites) + /// [SurveyProductOverwrites] + productOverwrites Json? + /// @zod.custom(imports.ZSurveySingleUse) + /// [SurveySingleUse] + singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}") /// @zod.custom(imports.ZSurveyVerifyEmail) /// [SurveyVerifyEmail] - verifyEmail Json? + verifyEmail Json? } model Event { diff --git a/packages/database/zod-utils.ts b/packages/database/zod-utils.ts index efdd0bdcba..9106c3f1bb 100644 --- a/packages/database/zod-utils.ts +++ b/packages/database/zod-utils.ts @@ -10,7 +10,9 @@ export { ZSurveyQuestions, ZSurveyThankYouCard, ZSurveyClosedMessage, + ZSurveyProductOverwrites, ZSurveyVerifyEmail, + ZSurveySingleUse, } from "@formbricks/types/v1/surveys"; export { ZUserNotificationSettings } from "@formbricks/types/v1/users"; diff --git a/packages/js/src/lib/widget.ts b/packages/js/src/lib/widget.ts index ade5122649..59be662251 100644 --- a/packages/js/src/lib/widget.ts +++ b/packages/js/src/lib/widget.ts @@ -42,15 +42,22 @@ export const renderWidget = (survey: TSurvey) => { surveyState ); + const productOverwrites = survey.productOverwrites ?? {}; + const brandColor = productOverwrites.brandColor ?? product.brandColor; + const highlightBorderColor = productOverwrites.highlightBorderColor ?? product.highlightBorderColor; + const clickOutside = productOverwrites.clickOutside ?? product.clickOutsideClose; + const darkOverlay = productOverwrites.darkOverlay ?? product.darkOverlay; + const placement = productOverwrites.placement ?? product.placement; + setTimeout(() => { renderSurveyModal({ survey: survey, - brandColor: product.brandColor, + brandColor, formbricksSignature: product.formbricksSignature, - clickOutside: product.clickOutsideClose, - darkOverlay: product.darkOverlay, - highlightBorderColor: product.highlightBorderColor, - placement: product.placement, + clickOutside, + darkOverlay, + highlightBorderColor, + placement, onDisplay: async () => { const { id } = await createDisplay( { diff --git a/packages/lib/services/actions.ts b/packages/lib/action/service.ts similarity index 95% rename from packages/lib/services/actions.ts rename to packages/lib/action/service.ts index ffa8b2475a..018aa0bedc 100644 --- a/packages/lib/services/actions.ts +++ b/packages/lib/action/service.ts @@ -2,7 +2,7 @@ import "server-only"; import z from "zod"; import { prisma } from "@formbricks/database"; -import { DatabaseError } from "@formbricks/types/v1/errors"; +import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors"; import { TAction } from "@formbricks/types/v1/actions"; import { ZId } from "@formbricks/types/v1/environment"; import { Prisma } from "@prisma/client"; @@ -12,7 +12,7 @@ import { TJsActionInput } from "@formbricks/types/v1/js"; import { revalidateTag } from "next/cache"; import { EventType } from "@prisma/client"; import { getActionClassCacheTag, getActionClassCached } from "../actionClass/service"; -import { getSessionCached } from "../services/session"; +import { getSessionCached } from "../session/service"; export const getActionsByEnvironmentId = cache( async (environmentId: string, limit?: number): Promise => { @@ -65,7 +65,7 @@ export const createAction = async (data: TJsActionInput) => { const session = await getSessionCached(sessionId); if (!session) { - throw new Error("Session not found"); + throw new ResourceNotFoundError("Session", sessionId); } const actionClass = await getActionClassCached(name, environmentId); diff --git a/packages/lib/services/activity.tsx b/packages/lib/activity/service.ts similarity index 94% rename from packages/lib/services/activity.tsx rename to packages/lib/activity/service.ts index 55ba044ffd..c9215812f6 100644 --- a/packages/lib/services/activity.tsx +++ b/packages/lib/activity/service.ts @@ -5,6 +5,7 @@ import { TActivityFeedItem } from "@formbricks/types/v1/activity"; import { validateInputs } from "../utils/validate"; import { ZId } from "@formbricks/types/v1/environment"; import { cache } from "react"; +import { ResourceNotFoundError } from "@formbricks/types/v1/errors"; export const getActivityTimeline = cache(async (personId: string): Promise => { validateInputs([personId, ZId]); @@ -34,8 +35,9 @@ export const getActivityTimeline = cache(async (personId: string): Promise createHash("sha256").update(key).digest("hex"); + +// create an aes128 encryption function +export const encryptAES128 = (encryptionKey: string, data: string): string => { + const cipher = createCipheriv("aes-128-ecb", Buffer.from(encryptionKey, "base64"), ""); + let encrypted = cipher.update(data, "utf-8", "hex"); + encrypted += cipher.final("hex"); + return encrypted; +}; +// create an aes128 decryption function +export const decryptAES128 = (encryptionKey: string, data: string): string => { + const cipher = createDecipheriv("aes-128-ecb", Buffer.from(encryptionKey, "base64"), ""); + let decrypted = cipher.update(data, "hex", "utf-8"); + decrypted += cipher.final("utf-8"); + return decrypted; +}; diff --git a/packages/lib/services/displays.ts b/packages/lib/display/service.ts similarity index 98% rename from packages/lib/services/displays.ts rename to packages/lib/display/service.ts index 5cbe1983d9..9d4114f36c 100644 --- a/packages/lib/services/displays.ts +++ b/packages/lib/display/service.ts @@ -13,7 +13,7 @@ import { Prisma } from "@prisma/client"; import { revalidateTag } from "next/cache"; import { cache } from "react"; import { validateInputs } from "../utils/validate"; -import { transformPrismaPerson } from "./person"; +import { transformPrismaPerson } from "../person/service"; const selectDisplay = { id: true, diff --git a/packages/lib/services/environment.ts b/packages/lib/environment/service.ts similarity index 63% rename from packages/lib/services/environment.ts rename to packages/lib/environment/service.ts index 87714c5f19..e104e3c74a 100644 --- a/packages/lib/services/environment.ts +++ b/packages/lib/environment/service.ts @@ -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,73 @@ export const updateEnvironment = async ( } }; -export const getEnvironmentByUser = async (user: any): Promise => { - 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 => { + validateInputs([userId, ZId]); + try { + return 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]; - - return environment; + throw error; } - - 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 +): Promise => { + 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 }, + ], }; diff --git a/packages/lib/services/googleSheet.ts b/packages/lib/googleSheet/service.ts similarity index 93% rename from packages/lib/services/googleSheet.ts rename to packages/lib/googleSheet/service.ts index 1f8900a889..c7daae2b2b 100644 --- a/packages/lib/services/googleSheet.ts +++ b/packages/lib/googleSheet/service.ts @@ -2,7 +2,7 @@ import "server-only"; import { prisma } from "@formbricks/database"; import { Prisma } from "@prisma/client"; -import { DatabaseError } from "@formbricks/types/v1/errors"; +import { DatabaseError, UnknownError } from "@formbricks/types/v1/errors"; import { cache } from "react"; import { TGoogleCredential, @@ -89,8 +89,7 @@ export async function writeData(credentials: TGoogleCredential, spreadsheetId: s }, (err: Error) => { if (err) { - throw new Error(`Error while appending data: ${err.message}`); - } else { + throw new UnknownError(`Error while appending data: ${err.message}`); } } ); @@ -104,8 +103,7 @@ export async function writeData(credentials: TGoogleCredential, spreadsheetId: s }, (err: Error) => { if (err) { - throw new Error(`Error while appending data: ${err.message}`); - } else { + throw new UnknownError(`Error while appending data: ${err.message}`); } } ); diff --git a/packages/lib/services/integrations.ts b/packages/lib/integration/service.ts similarity index 100% rename from packages/lib/services/integrations.ts rename to packages/lib/integration/service.ts diff --git a/packages/lib/services/invite.ts b/packages/lib/invite/service.ts similarity index 100% rename from packages/lib/services/invite.ts rename to packages/lib/invite/service.ts diff --git a/packages/lib/services/membership.ts b/packages/lib/membership/service.ts similarity index 78% rename from packages/lib/services/membership.ts rename to packages/lib/membership/service.ts index c1e2ab07ed..470cc72280 100644 --- a/packages/lib/services/membership.ts +++ b/packages/lib/membership/service.ts @@ -1,7 +1,7 @@ import "server-only"; import { prisma } from "@formbricks/database"; -import { ResourceNotFoundError } from "@formbricks/types/v1/errors"; +import { ResourceNotFoundError, DatabaseError, UnknownError } from "@formbricks/types/v1/errors"; import { TMember, TMembership, TMembershipUpdateInput } from "@formbricks/types/v1/memberships"; import { Prisma } from "@prisma/client"; import { cache } from "react"; @@ -62,6 +62,26 @@ export const getMembershipsByUserId = cache(async (userId: string): Promise +): Promise => { + 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, @@ -82,9 +102,9 @@ export const updateMembership = async ( } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2016") { throw new ResourceNotFoundError("Membership", `userId: ${userId}, teamId: ${teamId}`); - } else { - throw error; // Re-throw any other errors } + + throw error; } }; @@ -128,6 +148,11 @@ export const transferOwnership = async (currentOwnerId: string, newOwnerId: stri }), ]); } catch (error) { - throw new Error("Something went wrong"); + if (error instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseError("Database operation failed"); + } + + const message = error instanceof Error ? error.message : ""; + throw new UnknownError(`Error while transfering ownership: ${message}`); } }; diff --git a/packages/lib/services/person.ts b/packages/lib/person/service.ts similarity index 96% rename from packages/lib/services/person.ts rename to packages/lib/person/service.ts index bc2ca3f203..ae5bb4c0b2 100644 --- a/packages/lib/services/person.ts +++ b/packages/lib/person/service.ts @@ -2,14 +2,14 @@ import "server-only"; import { prisma } from "@formbricks/database"; import { ZId } from "@formbricks/types/v1/environment"; -import { DatabaseError } from "@formbricks/types/v1/errors"; +import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors"; import { TPerson, TPersonUpdateInput } from "@formbricks/types/v1/people"; import { Prisma } from "@prisma/client"; import { revalidateTag, unstable_cache } from "next/cache"; import { cache } from "react"; import { PEOPLE_PER_PAGE } from "../constants"; import { validateInputs } from "../utils/validate"; -import { getAttributeClassByName } from "./attributeClass"; +import { getAttributeClassByName } from "../attributeClass/service"; import { SERVICES_REVALIDATION_INTERVAL } from "../constants"; export const selectPerson = { @@ -250,7 +250,7 @@ export const getOrCreatePersonByUserId = async (userId: string, environmentId: s const userIdAttributeClass = await getAttributeClassByName(environmentId, "userId"); if (!userIdAttributeClass) { - throw new Error("Attribute class not found for the given environmentId"); + throw new ResourceNotFoundError("Attribute class not found for the given environment", environmentId); } const personPrisma = await prisma.person.create({ diff --git a/packages/lib/services/product.ts b/packages/lib/product/service.ts similarity index 74% rename from packages/lib/services/product.ts rename to packages/lib/product/service.ts index 8072f3a88e..73dde70184 100644 --- a/packages/lib/services/product.ts +++ b/packages/lib/product/service.ts @@ -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/service"; 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 => unstable_cache( async () => { @@ -135,6 +105,7 @@ export const updateProduct = async ( inputProduct: Partial ): Promise => { 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 return product; }); -export const createProduct = async (environmentId: string, productName: string): Promise => { - 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 +): Promise => { + 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; }; diff --git a/packages/lib/services/profile.ts b/packages/lib/profile/service.ts similarity index 91% rename from packages/lib/services/profile.ts rename to packages/lib/profile/service.ts index b176dff86f..fd2687f1aa 100644 --- a/packages/lib/services/profile.ts +++ b/packages/lib/profile/service.ts @@ -4,11 +4,16 @@ 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"; -import { deleteTeam } from "./team"; +import { deleteTeam } from "../team/service"; import { z } from "zod"; import { SERVICES_REVALIDATION_INTERVAL } from "../constants"; @@ -149,6 +154,19 @@ const deleteUser = async (userId: string): Promise => { return profile; }; +export const createProfile = async (data: TProfileCreateInput): Promise => { + 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 => { validateInputs([userId, ZId]); diff --git a/packages/lib/response/auth.ts b/packages/lib/response/auth.ts index f4adfe0858..513354f212 100644 --- a/packages/lib/response/auth.ts +++ b/packages/lib/response/auth.ts @@ -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 => await unstable_cache( diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts index 4e7f9a820f..852fa06a3e 100644 --- a/packages/lib/response/service.ts +++ b/packages/lib/response/service.ts @@ -12,8 +12,9 @@ import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/error import { TPerson } from "@formbricks/types/v1/people"; import { TTag } from "@formbricks/types/v1/tags"; import { Prisma } from "@prisma/client"; +import { z } from "zod"; import { cache } from "react"; -import { getPerson, transformPrismaPerson } from "../services/person"; +import { getPerson, transformPrismaPerson } from "../person/service"; import { captureTelemetry } from "../telemetry"; import { validateInputs } from "../utils/validate"; import { ZId } from "@formbricks/types/v1/environment"; @@ -28,6 +29,7 @@ const responseSelection = { data: true, meta: true, personAttributes: true, + singleUseId: true, person: { select: { id: true, @@ -115,6 +117,41 @@ export const getResponsesByPersonId = async (personId: string): Promise => { + validateInputs([surveyId, ZId], [singleUseId, z.string()]); + try { + if (!singleUseId) { + return null; + } + const responsePrisma = await prisma.response.findUnique({ + where: { + surveyId_singleUseId: { surveyId, singleUseId }, + }, + select: responseSelection, + }); + + if (!responsePrisma) { + return null; + } + + const response: TResponse = { + ...responsePrisma, + person: responsePrisma.person ? transformPrismaPerson(responsePrisma.person) : null, + tags: responsePrisma.tags.map((tagPrisma: { tag: TTag }) => tagPrisma.tag), + }; + + return response; + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseError("Database operation failed"); + } + + throw error; + } + } +); + export const createResponse = async (responseInput: Partial): Promise => { validateInputs([responseInput, ZResponseInput.partial()]); captureTelemetry("response created"); @@ -143,6 +180,7 @@ export const createResponse = async (responseInput: Partial): Pr personAttributes: person?.attributes, }), ...(responseInput.meta && ({ meta: responseInput?.meta } as Prisma.JsonObject)), + singleUseId: responseInput.singleUseId, }, select: responseSelection, }); diff --git a/packages/lib/services/responseNote.ts b/packages/lib/responseNote/service.ts similarity index 100% rename from packages/lib/services/responseNote.ts rename to packages/lib/responseNote/service.ts diff --git a/packages/lib/responseQueue.ts b/packages/lib/responseQueue.ts index 6358795787..fa1b5ab929 100644 --- a/packages/lib/responseQueue.ts +++ b/packages/lib/responseQueue.ts @@ -72,7 +72,12 @@ export class ResponseQueue { await updateResponse(responseUpdate, this.surveyState.responseId, this.config.apiHost); } else { const response = await createResponse( - { ...responseUpdate, surveyId: this.surveyState.surveyId, personId: this.config.personId || null }, + { + ...responseUpdate, + surveyId: this.surveyState.surveyId, + personId: this.config.personId || null, + singleUseId: this.surveyState.singleUseId || null, + }, this.config.apiHost ); if (this.surveyState.displayId) { diff --git a/packages/lib/services/session.ts b/packages/lib/session/service.ts similarity index 100% rename from packages/lib/services/session.ts rename to packages/lib/session/service.ts diff --git a/packages/lib/survey/auth.ts b/packages/lib/survey/auth.ts new file mode 100644 index 0000000000..263231af5d --- /dev/null +++ b/packages/lib/survey/auth.ts @@ -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 => + 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 diff --git a/packages/lib/services/survey.ts b/packages/lib/survey/service.ts similarity index 96% rename from packages/lib/services/survey.ts rename to packages/lib/survey/service.ts index 6deb20e1e0..c77b937d6b 100644 --- a/packages/lib/services/survey.ts +++ b/packages/lib/survey/service.ts @@ -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 "../display/service"; 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, @@ -49,7 +42,9 @@ export const selectSurvey = { autoComplete: true, verifyEmail: true, redirectUrl: true, + productOverwrites: true, surveyClosedMessage: true, + singleUse: true, triggers: { select: { eventClass: { @@ -144,7 +139,7 @@ export const getSurveyWithAnalytics = async (surveyId: string): Promise => { throw new ValidationError("Data validation of survey failed"); } }, - [getSurveyCacheKey(surveyId)], + [`surveys-${surveyId}`], { tags: [getSurveyCacheTag(surveyId)], revalidate: 60 * 30, @@ -328,7 +323,7 @@ export const getSurveys = async (environmentId: string): Promise => { throw new ValidationError("Data validation of survey failed"); } }, - [getSurveysCacheKey(environmentId)], + [`environments-${environmentId}-surveys`], { tags: [getSurveysCacheTag(environmentId)], revalidate: 60 * 30, @@ -392,7 +387,7 @@ export const getSurveysWithAnalytics = async (environmentId: string): Promise => { +export const createTeam = async (teamInput: TTeamUpdateInput): Promise => { + try { + const team = await prisma.team.create({ + data: teamInput, + select, + }); + + return team; + } catch (error) { + throw error; + } +}; + +export const updateTeam = async (teamId: string, data: Partial): Promise => { try { const updatedTeam = await prisma.team.update({ where: { @@ -232,7 +245,7 @@ export const createDemoProduct = async (teamId: string) => { // check if updatedEnvironment exists and it has attributeClasses if (!updatedEnvironment || !updatedEnvironment.attributeClasses) { - throw new Error("Attribute classes could not be created"); + throw new ValidationError("Attribute classes could not be created"); } const attributeClasses = updatedEnvironment.attributeClasses; @@ -319,8 +332,12 @@ export const createDemoProduct = async (teamId: string) => { })), }), ]); - } catch (err: any) { - throw new Error(err); + } catch (error: any) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseError("Database operation failed"); + } + + throw error; } // Create a function that creates a survey diff --git a/packages/lib/services/teamDetails.ts b/packages/lib/teamDetail/service.ts similarity index 100% rename from packages/lib/services/teamDetails.ts rename to packages/lib/teamDetail/service.ts diff --git a/packages/lib/services/webhook.ts b/packages/lib/webhook/service.ts similarity index 100% rename from packages/lib/services/webhook.ts rename to packages/lib/webhook/service.ts diff --git a/packages/surveys/package.json b/packages/surveys/package.json index 2f3b8cc94c..4f8301ae7a 100644 --- a/packages/surveys/package.json +++ b/packages/surveys/package.json @@ -20,7 +20,7 @@ "@preact/preset-vite": "^2.5.0", "autoprefixer": "^10.4.16", "eslint-config-formbricks": "workspace:*", - "postcss": "^8.4.30", + "postcss": "^8.4.31", "preact": "^10.17.1", "tailwindcss": "^3.3.3", "terser": "^5.20.0", diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index 0d8a04d8df..73ce4d4d67 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -10,7 +10,7 @@ "@tailwindcss/forms": "^0.5.6", "@tailwindcss/typography": "^0.5.10", "autoprefixer": "^10.4.16", - "postcss": "^8.4.30", + "postcss": "^8.4.31", "tailwindcss": "^3.3.3" } } diff --git a/packages/tailwind-config/tailwind.config.js b/packages/tailwind-config/tailwind.config.js index 27f7e777fa..5e8a4421b1 100644 --- a/packages/tailwind-config/tailwind.config.js +++ b/packages/tailwind-config/tailwind.config.js @@ -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", diff --git a/packages/types/package.json b/packages/types/package.json index 3a3b260aba..4ca6cd7e68 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -11,6 +11,6 @@ "@formbricks/tsconfig": "workspace:*" }, "dependencies": { - "zod": "^3.22.2" + "zod": "^3.22.3" } } diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts index 10913f00d5..8d1b413cdb 100644 --- a/packages/types/surveys.ts +++ b/packages/types/surveys.ts @@ -1,3 +1,4 @@ +import { PlacementType } from "./js"; import { Question } from "./questions"; export interface ThankYouCard { @@ -11,11 +12,25 @@ export interface SurveyClosedMessage { subheading?: string; } +export interface SurveySingleUse { + enabled: boolean; + heading?: string; + subheading?: string; +} + export interface VerifyEmail { name?: string; subheading?: string; } +export interface SurveyProductOverwrites { + brandColor: string; + highlightBorderColor: string | null; + placement: PlacementType; + clickOutside: boolean; + darkOverlay: boolean; +} + export interface Survey { id: string; createdAt: string; @@ -39,7 +54,9 @@ export interface Survey { surveyClosedMessage: SurveyClosedMessage | null; verifyEmail: VerifyEmail | null; closeOnDate: Date | null; + singleUse: SurveySingleUse | null; _count: { responses: number | null } | null; + productOverwrites: SurveyProductOverwrites | null; } export interface AttributeFilter { diff --git a/packages/types/v1/actionClasses.ts b/packages/types/v1/actionClasses.ts index 6d76629c00..5c888d409b 100644 --- a/packages/types/v1/actionClasses.ts +++ b/packages/types/v1/actionClasses.ts @@ -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; + export type TActionClassInput = z.infer; diff --git a/packages/types/v1/attributeClasses.ts b/packages/types/v1/attributeClasses.ts index 2b67849d65..6b492ae669 100644 --- a/packages/types/v1/attributeClasses.ts +++ b/packages/types/v1/attributeClasses.ts @@ -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; + export type TAttributeClassUpdateInput = z.infer; export type TAttributeClassInput = z.infer; diff --git a/packages/types/v1/common.ts b/packages/types/v1/common.ts new file mode 100644 index 0000000000..8ed5e6fbb4 --- /dev/null +++ b/packages/types/v1/common.ts @@ -0,0 +1,4 @@ +import { z } from "zod"; + +export const ZColor = z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/); +export const ZSurveyPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]); diff --git a/packages/types/v1/environment.ts b/packages/types/v1/environment.ts index 3910cfb437..90a31bdb1e 100644 --- a/packages/types/v1/environment.ts +++ b/packages/types/v1/environment.ts @@ -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; + export type TEnvironmentUpdateInput = z.infer; diff --git a/packages/types/v1/errors.ts b/packages/types/v1/errors.ts index e86054c477..1d2e8ee850 100644 --- a/packages/types/v1/errors.ts +++ b/packages/types/v1/errors.ts @@ -22,6 +22,14 @@ class ValidationError extends Error { } } +class UnknownError extends Error { + statusCode = 500; + constructor(message: string) { + super(message); + this.name = "DatabaseError"; + } +} + class DatabaseError extends Error { statusCode = 500; constructor(message: string) { @@ -83,6 +91,7 @@ export { ValidationError, DatabaseError, UniqueConstraintError, + UnknownError, ForeignKeyConstraintError, OperationNotAllowedError, AuthenticationError, diff --git a/packages/types/v1/product.ts b/packages/types/v1/product.ts index c686b612f7..8140414d79 100644 --- a/packages/types/v1/product.ts +++ b/packages/types/v1/product.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { ZEnvironment } from "./environment"; +import { ZColor, ZSurveyPlacement } from "./common"; export const ZProduct = z.object({ id: z.string().cuid2(), @@ -7,14 +8,11 @@ export const ZProduct = z.object({ updatedAt: z.date(), name: z.string(), teamId: z.string(), - brandColor: z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/), - highlightBorderColor: z - .string() - .regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/) - .nullish(), + brandColor: ZColor, + highlightBorderColor: ZColor.nullable(), recontactDays: z.number().int(), formbricksSignature: z.boolean(), - placement: z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]), + placement: ZSurveyPlacement, clickOutsideClose: z.boolean(), darkOverlay: z.boolean(), environments: z.array(ZEnvironment), @@ -26,7 +24,6 @@ export const ZProductUpdateInput = ZProduct.omit({ id: true, createdAt: true, updatedAt: true, - environments: true, }); export type TProductUpdateInput = z.infer; diff --git a/packages/types/v1/profile.ts b/packages/types/v1/profile.ts index 62a1e3be00..0130acc3bb 100644 --- a/packages/types/v1/profile.ts +++ b/packages/types/v1/profile.ts @@ -21,11 +21,21 @@ export const ZProfile = z.object({ export type TProfile = z.infer; 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; + +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; diff --git a/packages/types/v1/responses.ts b/packages/types/v1/responses.ts index 069a9a09b5..9855d3c42c 100644 --- a/packages/types/v1/responses.ts +++ b/packages/types/v1/responses.ts @@ -53,6 +53,7 @@ export const ZResponse = z.object({ notes: z.array(ZResponseNote), tags: z.array(ZTag), meta: ZResponseMeta.nullable(), + singleUseId: z.string().nullable(), }); export type TResponse = z.infer; @@ -60,6 +61,7 @@ export type TResponse = z.infer; export const ZResponseInput = z.object({ surveyId: z.string().cuid2(), personId: z.string().cuid2().nullable(), + singleUseId: z.string().nullable().optional(), finished: z.boolean(), data: ZResponseData, meta: z diff --git a/packages/types/v1/surveys.ts b/packages/types/v1/surveys.ts index 64cae13a7b..a09ad8ae7d 100644 --- a/packages/types/v1/surveys.ts +++ b/packages/types/v1/surveys.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ZActionClass } from "./actionClasses"; import { QuestionType } from "../questions"; +import { ZColor, ZSurveyPlacement } from "./common"; export const ZSurveyThankYouCard = z.object({ enabled: z.boolean(), @@ -8,6 +9,16 @@ export const ZSurveyThankYouCard = z.object({ subheader: z.optional(z.string()), }); +export const ZSurveyProductOverwrites = z.object({ + brandColor: ZColor.nullish(), + highlightBorderColor: ZColor.nullish(), + placement: ZSurveyPlacement.nullish(), + clickOutside: z.boolean().nullish(), + darkOverlay: z.boolean().nullish(), +}); + +export type TSurveyProductOverwrites = z.infer; + export const ZSurveyClosedMessage = z .object({ enabled: z.boolean().optional(), @@ -17,6 +28,17 @@ export const ZSurveyClosedMessage = z .nullable() .optional(); +export const ZSurveySingleUse = z + .object({ + enabled: z.boolean(), + heading: z.optional(z.string()), + subheading: z.optional(z.string()), + isEncrypted: z.boolean(), + }) + .nullable(); + +export type TSurveySingleUse = z.infer; + export const ZSurveyVerifyEmail = z .object({ name: z.optional(z.string()), @@ -258,7 +280,9 @@ export const ZSurvey = z.object({ delay: z.number(), autoComplete: z.number().nullable(), closeOnDate: z.date().nullable(), + productOverwrites: ZSurveyProductOverwrites.nullable(), surveyClosedMessage: ZSurveyClosedMessage.nullable(), + singleUse: ZSurveySingleUse.nullable(), verifyEmail: ZSurveyVerifyEmail.nullable(), }); diff --git a/packages/types/v1/teams.ts b/packages/types/v1/teams.ts index 3eef5b8e07..617058a15a 100644 --- a/packages/types/v1/teams.ts +++ b/packages/types/v1/teams.ts @@ -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; export type TTeam = z.infer; diff --git a/packages/ui/components/Accordion.tsx b/packages/ui/components/Accordion.tsx new file mode 100644 index 0000000000..314965e23b --- /dev/null +++ b/packages/ui/components/Accordion.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props}> + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 7b8fe4c492..c45d6e986b 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -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"; diff --git a/packages/ui/package.json b/packages/ui/package.json index db423f33de..fc19e19002 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,7 +16,7 @@ "@formbricks/tsconfig": "workspace:*", "concurrently": "^8.2.1", "eslint-config-formbricks": "workspace:*", - "postcss": "^8.4.30", + "postcss": "^8.4.31", "react": "18.2.0" }, "dependencies": { @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3460e7f9b1..bf3c7df2e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -28,7 +24,7 @@ importers: version: 3.12.7 turbo: specifier: latest - version: 1.10.13 + version: 1.10.14 apps/demo: dependencies: @@ -119,7 +115,7 @@ importers: version: 8.10.0 autoprefixer: specifier: ^10.4.15 - version: 10.4.16(postcss@8.4.30) + version: 10.4.16(postcss@8.4.31) clsx: specifier: ^2.0.0 version: 2.0.0 @@ -270,7 +266,7 @@ importers: version: 7.72.0(encoding@0.1.13)(next@13.5.3)(react@18.2.0) '@t3-oss/env-nextjs': specifier: ^0.6.1 - version: 0.6.1(typescript@5.2.2)(zod@3.22.2) + version: 0.6.1(typescript@5.2.2)(zod@3.22.3) bcryptjs: specifier: ^2.4.3 version: 2.4.3 @@ -403,11 +399,11 @@ importers: specifier: ^3.0.1 version: 3.0.1(prisma@5.3.1)(typescript@5.2.2) zod: - specifier: ^3.22.2 - version: 3.22.2 + specifier: ^3.22.3 + version: 3.22.3 zod-prisma: specifier: ^0.5.4 - version: 0.5.4(prisma@5.3.1)(zod@3.22.2) + version: 0.5.4(prisma@5.3.1)(zod@3.22.3) packages/ee: dependencies: @@ -441,7 +437,7 @@ importers: version: 9.0.0(eslint@8.50.0) eslint-config-turbo: specifier: latest - version: 1.8.8(eslint@8.50.0) + version: 1.10.14(eslint@8.50.0) eslint-plugin-react: specifier: 7.33.2 version: 7.33.2(eslint@8.50.0) @@ -583,7 +579,7 @@ importers: version: 1.0.18 '@typescript-eslint/parser': specifier: ~6.7 - version: 6.7.3(eslint@8.50.0)(typescript@5.2.2) + version: 6.7.0(eslint@8.50.0)(typescript@5.2.2) eslint-plugin-n8n-nodes-base: specifier: ^1.16.0 version: 1.16.0(eslint@8.50.0)(typescript@5.2.2) @@ -616,13 +612,13 @@ importers: version: 2.5.0(@babel/core@7.23.0)(preact@10.17.1)(vite@4.4.9) autoprefixer: specifier: ^10.4.16 - version: 10.4.16(postcss@8.4.30) + version: 10.4.16(postcss@8.4.31) eslint-config-formbricks: specifier: workspace:* version: link:../eslint-config-formbricks postcss: - specifier: ^8.4.30 - version: 8.4.30 + specifier: ^8.4.31 + version: 8.4.31 preact: specifier: ^10.17.1 version: 10.17.1 @@ -649,10 +645,10 @@ importers: version: 0.5.10(tailwindcss@3.3.3) autoprefixer: specifier: ^10.4.16 - version: 10.4.16(postcss@8.4.30) + version: 10.4.16(postcss@8.4.31) postcss: - specifier: ^8.4.30 - version: 8.4.30 + specifier: ^8.4.31 + version: 8.4.31 tailwindcss: specifier: ^3.3.3 version: 3.3.3 @@ -675,8 +671,8 @@ importers: packages/types: dependencies: zod: - specifier: ^3.22.2 - version: 3.22.2 + specifier: ^3.22.3 + version: 3.22.3 devDependencies: '@formbricks/tsconfig': specifier: workspace:* @@ -711,6 +707,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) @@ -785,8 +784,8 @@ importers: specifier: workspace:* version: link:../eslint-config-formbricks postcss: - specifier: ^8.4.30 - version: 8.4.30 + specifier: ^8.4.31 + version: 8.4.31 react: specifier: 18.2.0 version: 18.2.0 @@ -1001,6 +1000,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/compat-data@7.22.9: + resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/core@7.23.0: resolution: {integrity: sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==} engines: {node: '>=6.9.0'} @@ -1038,6 +1042,16 @@ packages: semver: 6.3.1 dev: true + /@babel/generator@7.22.15: + resolution: {integrity: sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.17 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.18 + jsesc: 2.5.2 + dev: true + /@babel/generator@7.23.0: resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} engines: {node: '>=6.9.0'} @@ -1052,14 +1066,14 @@ packages: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-annotate-as-pure@7.22.5: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-builder-binary-assignment-operator-visitor@7.22.5: @@ -1073,7 +1087,7 @@ packages: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.22.20 + '@babel/compat-data': 7.22.9 '@babel/helper-validator-option': 7.22.15 browserslist: 4.21.10 lru-cache: 5.1.1 @@ -1088,8 +1102,8 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 '@babel/helper-member-expression-to-functions': 7.18.9 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.22.9(@babel/core@7.23.0) @@ -1104,8 +1118,8 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 '@babel/helper-member-expression-to-functions': 7.22.15 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.22.9(@babel/core@7.23.0) @@ -1122,8 +1136,8 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 '@babel/helper-member-expression-to-functions': 7.22.5 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.22.9(@babel/core@7.23.0) @@ -1164,6 +1178,19 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 + dev: true + /@babel/helper-function-name@7.23.0: resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} engines: {node: '>=6.9.0'} @@ -1183,28 +1210,28 @@ packages: resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-member-expression-to-functions@7.22.15: resolution: {integrity: sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-member-expression-to-functions@7.22.5: resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-module-imports@7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-module-imports@7.22.15: @@ -1218,7 +1245,21 @@ packages: resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 + dev: true + + /@babel/helper-module-transforms@7.22.17(@babel/core@7.23.0): + resolution: {integrity: sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.0 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.15 dev: true /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.0): @@ -1239,7 +1280,7 @@ packages: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-plugin-utils@7.22.5: @@ -1255,7 +1296,7 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-wrap-function': 7.22.17 dev: true @@ -1263,12 +1304,12 @@ packages: resolution: {integrity: sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-member-expression-to-functions': 7.22.5 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/template': 7.22.15 - '@babel/traverse': 7.23.0 - '@babel/types': 7.23.0 + '@babel/traverse': 7.22.17 + '@babel/types': 7.22.17 transitivePeerDependencies: - supports-color dev: true @@ -1280,7 +1321,7 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.23.0 - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-member-expression-to-functions': 7.22.15 '@babel/helper-optimise-call-expression': 7.22.5 dev: true @@ -1296,14 +1337,14 @@ packages: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/helper-string-parser@7.22.5: @@ -1311,6 +1352,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-validator-identifier@7.22.15: + resolution: {integrity: sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} @@ -1325,7 +1371,7 @@ packages: resolution: {integrity: sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-function-name': 7.23.0 + '@babel/helper-function-name': 7.22.5 '@babel/template': 7.22.15 '@babel/types': 7.23.0 dev: true @@ -1345,11 +1391,19 @@ packages: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.22.15 chalk: 2.4.2 js-tokens: 4.0.0 dev: true + /@babel/parser@7.22.16: + resolution: {integrity: sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.17 + dev: true + /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} engines: {node: '>=6.0.0'} @@ -1423,7 +1477,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.22.20 + '@babel/compat-data': 7.22.9 '@babel/core': 7.23.0 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 @@ -1685,7 +1739,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.23.0 - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.22.17(@babel/core@7.23.0) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.0) @@ -1755,8 +1809,8 @@ packages: '@babel/core': 7.23.0 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-replace-supers': 7.22.9(@babel/core@7.23.0) @@ -1868,7 +1922,7 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-function-name': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -1921,7 +1975,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.23.0 - '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.0) + '@babel/helper-module-transforms': 7.22.17(@babel/core@7.23.0) '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -1932,7 +1986,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.23.0 - '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.0) + '@babel/helper-module-transforms': 7.22.17(@babel/core@7.23.0) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.22.5 dev: true @@ -1957,9 +2011,9 @@ packages: dependencies: '@babel/core': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.0) + '@babel/helper-module-transforms': 7.22.17(@babel/core@7.23.0) '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.22.15 dev: true /@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.23.0): @@ -1969,7 +2023,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.23.0 - '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.0) + '@babel/helper-module-transforms': 7.22.17(@babel/core@7.23.0) '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -2149,7 +2203,7 @@ packages: '@babel/helper-module-imports': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.0) - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.23.0): @@ -2163,7 +2217,7 @@ packages: '@babel/helper-module-imports': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.0) - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.23.0): @@ -2478,6 +2532,24 @@ packages: '@babel/types': 7.23.0 dev: true + /@babel/traverse@7.22.17: + resolution: {integrity: sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.22.15 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.22.16 + '@babel/types': 7.22.17 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/traverse@7.23.0: resolution: {integrity: sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==} engines: {node: '>=6.9.0'} @@ -2496,6 +2568,15 @@ packages: - supports-color dev: true + /@babel/types@7.22.17: + resolution: {integrity: sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.15 + to-fast-properties: 2.0.0 + dev: true + /@babel/types@7.23.0: resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} engines: {node: '>=6.9.0'} @@ -3140,6 +3221,18 @@ packages: engines: {node: '>=8'} dev: true + /@jest/console@29.6.4: + resolution: {integrity: sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.7.0 + chalk: 4.1.2 + jest-message-util: 29.6.3 + jest-util: 29.6.3 + slash: 3.0.0 + dev: true + /@jest/console@29.7.0: resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3306,6 +3399,16 @@ packages: graceful-fs: 4.2.10 dev: true + /@jest/test-result@29.6.4: + resolution: {integrity: sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.6.4 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + dev: true + /@jest/test-result@29.7.0: resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4377,6 +4480,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: @@ -5685,25 +5815,25 @@ packages: dependencies: defer-to-connect: 1.1.3 - /@t3-oss/env-core@0.6.1(typescript@5.2.2)(zod@3.22.2): + /@t3-oss/env-core@0.6.1(typescript@5.2.2)(zod@3.22.3): resolution: {integrity: sha512-KQD7qEDJtkWIWWmTVjNvk0wnHpkvAQ6CRbUxbWMFNG/fiosBQDQvtRpBNu6USxBscJCoC4z6y7P9MN52/mLOzw==} peerDependencies: typescript: '>=4.7.2' zod: ^3.0.0 dependencies: typescript: 5.2.2 - zod: 3.22.2 + zod: 3.22.3 dev: false - /@t3-oss/env-nextjs@0.6.1(typescript@5.2.2)(zod@3.22.2): + /@t3-oss/env-nextjs@0.6.1(typescript@5.2.2)(zod@3.22.3): resolution: {integrity: sha512-z1dIC++Vxj9kmzX5nSPfcrCSkszy3dTEPC4Ssx7Ap5AqR3c2Qa7S0xf8axn6coy7D/vCXDAAnHYnCMDhtcY3SQ==} peerDependencies: typescript: '>=4.7.2' zod: ^3.0.0 dependencies: - '@t3-oss/env-core': 0.6.1(typescript@5.2.2)(zod@3.22.2) + '@t3-oss/env-core': 0.6.1(typescript@5.2.2)(zod@3.22.3) typescript: 5.2.2 - zod: 3.22.2 + zod: 3.22.3 dev: false /@tailwindcss/forms@0.5.6(tailwindcss@3.3.3): @@ -5758,8 +5888,8 @@ packages: /@types/babel__core@7.20.0: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: - '@babel/parser': 7.23.0 - '@babel/types': 7.23.0 + '@babel/parser': 7.22.16 + '@babel/types': 7.22.17 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.3 @@ -5768,20 +5898,20 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.23.0 - '@babel/types': 7.23.0 + '@babel/parser': 7.22.16 + '@babel/types': 7.22.17 dev: true /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 dev: true /@types/bcryptjs@2.4.4: @@ -5984,7 +6114,7 @@ packages: /@types/mdast@3.0.10: resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} dependencies: - '@types/unist': 3.0.0 + '@types/unist': 2.0.6 dev: false /@types/mdast@4.0.0: @@ -6253,6 +6383,27 @@ packages: - typescript dev: true + /@typescript-eslint/parser@6.7.0(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.7.0 + '@typescript-eslint/types': 6.7.0 + '@typescript-eslint/typescript-estree': 6.7.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.7.0 + debug: 4.3.4 + eslint: 8.50.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -6289,6 +6440,14 @@ packages: '@typescript-eslint/visitor-keys': 6.2.1 dev: true + /@typescript-eslint/scope-manager@6.7.0: + resolution: {integrity: sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.7.0 + '@typescript-eslint/visitor-keys': 6.7.0 + dev: true + /@typescript-eslint/scope-manager@6.7.3: resolution: {integrity: sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -6326,6 +6485,11 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/types@6.7.0: + resolution: {integrity: sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + /@typescript-eslint/types@6.7.3: resolution: {integrity: sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==} engines: {node: ^16.0.0 || >=18.0.0} @@ -6372,6 +6536,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@6.7.0(typescript@5.2.2): + resolution: {integrity: sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.7.0 + '@typescript-eslint/visitor-keys': 6.7.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree@6.7.3(typescript@5.2.2): resolution: {integrity: sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==} engines: {node: ^16.0.0 || >=18.0.0} @@ -6466,6 +6651,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@6.7.0: + resolution: {integrity: sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.7.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@typescript-eslint/visitor-keys@6.7.3: resolution: {integrity: sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -7148,7 +7341,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.22.1 + es-abstract: 1.20.4 get-intrinsic: 1.2.1 is-string: 1.0.7 @@ -7366,7 +7559,7 @@ packages: hasBin: true dev: true - /autoprefixer@10.4.16(postcss@8.4.24): + /autoprefixer@10.4.16(postcss@8.4.31): resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -7374,43 +7567,11 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.10 - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001541 fraction.js: 4.3.6 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /autoprefixer@10.4.16(postcss@8.4.27): - resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.10 - caniuse-lite: 1.0.30001540 - fraction.js: 4.3.6 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.27 - postcss-value-parser: 4.2.0 - dev: true - - /autoprefixer@10.4.16(postcss@8.4.30): - resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.10 - caniuse-lite: 1.0.30001540 - fraction.js: 4.3.6 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 /available-typed-arrays@1.0.5: @@ -7524,7 +7685,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.18.3 dev: true @@ -7596,7 +7757,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.23.0 - '@babel/parser': 7.23.0 + '@babel/parser': 7.22.16 dev: true /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.0): @@ -7931,7 +8092,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001541 electron-to-chromium: 1.4.502 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) @@ -7941,7 +8102,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001541 electron-to-chromium: 1.4.502 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.9) @@ -8189,13 +8350,16 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.21.10 - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001541 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true - /caniuse-lite@1.0.30001540: - resolution: {integrity: sha512-9JL38jscuTJBTcuETxm8QLsFr/F6v0CYYTEU6r5+qSM98P2Q0Hmu0eG1dTG5GBUmywU3UlcVOUSIJYY47rdFSw==} + /caniuse-lite@1.0.30001523: + resolution: {integrity: sha512-I5q5cisATTPZ1mc588Z//pj/Ox80ERYDfR71YnvY7raS/NOk8xXlZcB0sF7JdqaV//kOaa6aus7lRfpdnt1eBA==} + + /caniuse-lite@1.0.30001541: + resolution: {integrity: sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==} /capture-stack-trace@1.0.2: resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} @@ -9169,22 +9333,13 @@ packages: timsort: 0.3.0 dev: true - /css-declaration-sorter@6.3.1(postcss@8.4.24): + /css-declaration-sorter@6.3.1(postcss@8.4.31): resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} engines: {node: ^10 || ^12 || >=14} peerDependencies: postcss: ^8.0.9 dependencies: - postcss: 8.4.24 - dev: true - - /css-declaration-sorter@6.3.1(postcss@8.4.30): - resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /css-in-js-utils@3.1.0: @@ -9199,13 +9354,13 @@ packages: peerDependencies: webpack: ^4.27.0 || ^5.0.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.30) + icss-utils: 5.1.0(postcss@8.4.31) loader-utils: 2.0.4 - postcss: 8.4.30 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.30) - postcss-modules-local-by-default: 4.0.0(postcss@8.4.30) - postcss-modules-scope: 3.0.0(postcss@8.4.30) - postcss-modules-values: 4.0.0(postcss@8.4.30) + postcss: 8.4.31 + postcss-modules-extract-imports: 3.0.0(postcss@8.4.31) + postcss-modules-local-by-default: 4.0.0(postcss@8.4.31) + postcss-modules-scope: 3.0.0(postcss@8.4.31) + postcss-modules-values: 4.0.0(postcss@8.4.31) postcss-value-parser: 4.2.0 schema-utils: 3.1.1 semver: 7.5.4 @@ -9310,80 +9465,42 @@ packages: postcss-unique-selectors: 4.0.1 dev: true - /cssnano-preset-default@5.2.13(postcss@8.4.24): + /cssnano-preset-default@5.2.13(postcss@8.4.31): resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - css-declaration-sorter: 6.3.1(postcss@8.4.24) - cssnano-utils: 3.1.0(postcss@8.4.24) - postcss: 8.4.24 - postcss-calc: 8.2.4(postcss@8.4.24) - postcss-colormin: 5.3.0(postcss@8.4.24) - postcss-convert-values: 5.1.3(postcss@8.4.24) - postcss-discard-comments: 5.1.2(postcss@8.4.24) - postcss-discard-duplicates: 5.1.0(postcss@8.4.24) - postcss-discard-empty: 5.1.1(postcss@8.4.24) - postcss-discard-overridden: 5.1.0(postcss@8.4.24) - postcss-merge-longhand: 5.1.7(postcss@8.4.24) - postcss-merge-rules: 5.1.3(postcss@8.4.24) - postcss-minify-font-values: 5.1.0(postcss@8.4.24) - postcss-minify-gradients: 5.1.1(postcss@8.4.24) - postcss-minify-params: 5.1.4(postcss@8.4.24) - postcss-minify-selectors: 5.2.1(postcss@8.4.24) - postcss-normalize-charset: 5.1.0(postcss@8.4.24) - postcss-normalize-display-values: 5.1.0(postcss@8.4.24) - postcss-normalize-positions: 5.1.1(postcss@8.4.24) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.24) - postcss-normalize-string: 5.1.0(postcss@8.4.24) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.24) - postcss-normalize-unicode: 5.1.1(postcss@8.4.24) - postcss-normalize-url: 5.1.0(postcss@8.4.24) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.24) - postcss-ordered-values: 5.1.3(postcss@8.4.24) - postcss-reduce-initial: 5.1.1(postcss@8.4.24) - postcss-reduce-transforms: 5.1.0(postcss@8.4.24) - postcss-svgo: 5.1.0(postcss@8.4.24) - postcss-unique-selectors: 5.1.1(postcss@8.4.24) - dev: true - - /cssnano-preset-default@5.2.13(postcss@8.4.30): - resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - css-declaration-sorter: 6.3.1(postcss@8.4.30) - cssnano-utils: 3.1.0(postcss@8.4.30) - postcss: 8.4.30 - postcss-calc: 8.2.4(postcss@8.4.30) - postcss-colormin: 5.3.0(postcss@8.4.30) - postcss-convert-values: 5.1.3(postcss@8.4.30) - postcss-discard-comments: 5.1.2(postcss@8.4.30) - postcss-discard-duplicates: 5.1.0(postcss@8.4.30) - postcss-discard-empty: 5.1.1(postcss@8.4.30) - postcss-discard-overridden: 5.1.0(postcss@8.4.30) - postcss-merge-longhand: 5.1.7(postcss@8.4.30) - postcss-merge-rules: 5.1.3(postcss@8.4.30) - postcss-minify-font-values: 5.1.0(postcss@8.4.30) - postcss-minify-gradients: 5.1.1(postcss@8.4.30) - postcss-minify-params: 5.1.4(postcss@8.4.30) - postcss-minify-selectors: 5.2.1(postcss@8.4.30) - postcss-normalize-charset: 5.1.0(postcss@8.4.30) - postcss-normalize-display-values: 5.1.0(postcss@8.4.30) - postcss-normalize-positions: 5.1.1(postcss@8.4.30) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.30) - postcss-normalize-string: 5.1.0(postcss@8.4.30) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.30) - postcss-normalize-unicode: 5.1.1(postcss@8.4.30) - postcss-normalize-url: 5.1.0(postcss@8.4.30) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.30) - postcss-ordered-values: 5.1.3(postcss@8.4.30) - postcss-reduce-initial: 5.1.1(postcss@8.4.30) - postcss-reduce-transforms: 5.1.0(postcss@8.4.30) - postcss-svgo: 5.1.0(postcss@8.4.30) - postcss-unique-selectors: 5.1.1(postcss@8.4.30) + css-declaration-sorter: 6.3.1(postcss@8.4.31) + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 + postcss-calc: 8.2.4(postcss@8.4.31) + postcss-colormin: 5.3.0(postcss@8.4.31) + postcss-convert-values: 5.1.3(postcss@8.4.31) + postcss-discard-comments: 5.1.2(postcss@8.4.31) + postcss-discard-duplicates: 5.1.0(postcss@8.4.31) + postcss-discard-empty: 5.1.1(postcss@8.4.31) + postcss-discard-overridden: 5.1.0(postcss@8.4.31) + postcss-merge-longhand: 5.1.7(postcss@8.4.31) + postcss-merge-rules: 5.1.3(postcss@8.4.31) + postcss-minify-font-values: 5.1.0(postcss@8.4.31) + postcss-minify-gradients: 5.1.1(postcss@8.4.31) + postcss-minify-params: 5.1.4(postcss@8.4.31) + postcss-minify-selectors: 5.2.1(postcss@8.4.31) + postcss-normalize-charset: 5.1.0(postcss@8.4.31) + postcss-normalize-display-values: 5.1.0(postcss@8.4.31) + postcss-normalize-positions: 5.1.1(postcss@8.4.31) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31) + postcss-normalize-string: 5.1.0(postcss@8.4.31) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31) + postcss-normalize-unicode: 5.1.1(postcss@8.4.31) + postcss-normalize-url: 5.1.0(postcss@8.4.31) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.31) + postcss-ordered-values: 5.1.3(postcss@8.4.31) + postcss-reduce-initial: 5.1.1(postcss@8.4.31) + postcss-reduce-transforms: 5.1.0(postcss@8.4.31) + postcss-svgo: 5.1.0(postcss@8.4.31) + postcss-unique-selectors: 5.1.1(postcss@8.4.31) dev: true /cssnano-util-get-arguments@4.0.0: @@ -9408,22 +9525,13 @@ packages: engines: {node: '>=6.9.0'} dev: true - /cssnano-utils@3.1.0(postcss@8.4.24): + /cssnano-utils@3.1.0(postcss@8.4.31): resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - dev: true - - /cssnano-utils@3.1.0(postcss@8.4.30): - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /cssnano@4.1.11: @@ -9436,27 +9544,15 @@ packages: postcss: 7.0.39 dev: true - /cssnano@5.1.14(postcss@8.4.24): + /cssnano@5.1.14(postcss@8.4.31): resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-preset-default: 5.2.13(postcss@8.4.24) + cssnano-preset-default: 5.2.13(postcss@8.4.31) lilconfig: 2.1.0 - postcss: 8.4.24 - yaml: 1.10.2 - dev: true - - /cssnano@5.1.14(postcss@8.4.30): - resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-preset-default: 5.2.13(postcss@8.4.30) - lilconfig: 2.1.0 - postcss: 8.4.30 + postcss: 8.4.31 yaml: 1.10.2 dev: true @@ -10651,7 +10747,7 @@ packages: '@rushstack/eslint-patch': 1.5.0 '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2) eslint: 8.50.0 - eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.28.1)(eslint@8.50.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.5.2)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.50.0) @@ -10697,15 +10793,23 @@ packages: resolution: {integrity: sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==} dev: true - /eslint-config-turbo@1.8.8(eslint@8.50.0): - resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==} + /eslint-config-turbo@1.10.14(eslint@8.50.0): + resolution: {integrity: sha512-ZeB+IcuFXy1OICkLuAplVa0euoYbhK+bMEQd0nH9+Lns18lgZRm33mVz/iSoH9VdUzl/1ZmFmoK+RpZc+8R80A==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.50.0 - eslint-plugin-turbo: 1.8.8(eslint@8.50.0) + eslint-plugin-turbo: 1.10.14(eslint@8.50.0) dev: true + /eslint-import-resolver-node@0.3.6: + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} + dependencies: + debug: 3.2.7 + resolve: 1.22.2 + transitivePeerDependencies: + - supports-color + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -10772,7 +10876,7 @@ packages: '@mdn/browser-compat-data': 5.2.39 ast-metadata-inferer: 0.7.0 browserslist: 4.21.10 - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001523 eslint: 8.50.0 find-up: 5.0.0 lodash.memoize: 4.1.2 @@ -10909,11 +11013,12 @@ packages: semver: 6.3.1 string.prototype.matchall: 4.0.8 - /eslint-plugin-turbo@1.8.8(eslint@8.50.0): - resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==} + /eslint-plugin-turbo@1.10.14(eslint@8.50.0): + resolution: {integrity: sha512-sBdBDnYr9AjT1g4lR3PBkZDonTrMnR4TvuGv5W0OiF7z9az1rI68yj2UHJZvjkwwcGu5mazWA1AfB0oaagpmfg==} peerDependencies: eslint: '>6.6.0' dependencies: + dotenv: 16.0.3 eslint: 8.50.0 dev: true @@ -11206,7 +11311,7 @@ packages: jest-get-type: 29.6.3 jest-matcher-utils: 29.6.4 jest-message-util: 29.6.3 - jest-util: 29.7.0 + jest-util: 29.6.3 dev: true /expect@29.7.0: @@ -12835,22 +12940,13 @@ packages: resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} dev: true - /icss-utils@5.1.0(postcss@8.4.24): + /icss-utils@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.24 - dev: true - - /icss-utils@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /idb@7.1.1: @@ -13664,7 +13760,7 @@ packages: engines: {node: '>=8'} dependencies: '@babel/core': 7.23.0 - '@babel/parser': 7.23.0 + '@babel/parser': 7.22.16 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 @@ -13677,7 +13773,7 @@ packages: engines: {node: '>=10'} dependencies: '@babel/core': 7.23.0 - '@babel/parser': 7.23.0 + '@babel/parser': 7.22.16 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 7.5.4 @@ -13854,7 +13950,7 @@ packages: chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 - pretty-format: 29.7.0 + pretty-format: 29.6.3 dev: true /jest-diff@29.7.0: @@ -13968,7 +14064,7 @@ packages: chalk: 4.1.2 jest-diff: 29.6.4 jest-get-type: 29.6.3 - pretty-format: 29.7.0 + pretty-format: 29.6.3 dev: true /jest-matcher-utils@29.7.0: @@ -13991,7 +14087,7 @@ packages: chalk: 4.1.2 graceful-fs: 4.2.10 micromatch: 4.0.5 - pretty-format: 29.7.0 + pretty-format: 29.6.3 slash: 3.0.0 stack-utils: 2.0.6 dev: true @@ -14090,7 +14186,7 @@ packages: jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-util: 29.7.0 jest-validate: 29.7.0 - resolve: 1.22.6 + resolve: 1.22.2 resolve.exports: 2.0.2 slash: 3.0.0 dev: true @@ -14159,10 +14255,10 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.23.0 - '@babel/generator': 7.23.0 + '@babel/generator': 7.22.15 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.0) '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.23.0) - '@babel/types': 7.23.0 + '@babel/types': 7.22.17 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -14182,6 +14278,18 @@ packages: - supports-color dev: true + /jest-util@29.6.3: + resolution: {integrity: sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.7.0 + chalk: 4.1.2 + ci-info: 3.7.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + dev: true + /jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -14226,13 +14334,13 @@ packages: resolution: {integrity: sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.7.0 + '@jest/test-result': 29.6.4 '@jest/types': 29.6.3 '@types/node': 20.7.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 29.7.0 + jest-util: 29.6.3 string-length: 4.0.2 dev: true @@ -14475,6 +14583,13 @@ packages: hasBin: true dev: true + /json5@1.0.1: + resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + /json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -14852,7 +14967,7 @@ packages: dependencies: big.js: 5.2.2 emojis-list: 3.0.0 - json5: 1.0.2 + json5: 1.0.1 dev: true /loader-utils@2.0.4: @@ -15196,10 +15311,9 @@ packages: unist-util-visit: 4.1.1 dev: false - /mdast-util-find-and-replace@2.2.2: - resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} + /mdast-util-find-and-replace@2.2.1: + resolution: {integrity: sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==} dependencies: - '@types/mdast': 3.0.10 escape-string-regexp: 5.0.0 unist-util-is: 5.1.1 unist-util-visit-parents: 5.1.1 @@ -15224,32 +15338,32 @@ packages: - supports-color dev: false - /mdast-util-gfm-autolink-literal@1.0.3: - resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} + /mdast-util-gfm-autolink-literal@1.0.2: + resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==} dependencies: '@types/mdast': 3.0.10 ccount: 2.0.1 - mdast-util-find-and-replace: 2.2.2 + mdast-util-find-and-replace: 2.2.1 micromark-util-character: 1.1.0 dev: false - /mdast-util-gfm-footnote@1.0.2: - resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} + /mdast-util-gfm-footnote@1.0.1: + resolution: {integrity: sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==} dependencies: '@types/mdast': 3.0.10 mdast-util-to-markdown: 1.3.0 micromark-util-normalize-identifier: 1.0.0 dev: false - /mdast-util-gfm-strikethrough@1.0.3: - resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} + /mdast-util-gfm-strikethrough@1.0.2: + resolution: {integrity: sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==} dependencies: '@types/mdast': 3.0.10 mdast-util-to-markdown: 1.3.0 dev: false - /mdast-util-gfm-table@1.0.7: - resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} + /mdast-util-gfm-table@1.0.6: + resolution: {integrity: sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==} dependencies: '@types/mdast': 3.0.10 markdown-table: 3.0.3 @@ -15259,22 +15373,22 @@ packages: - supports-color dev: false - /mdast-util-gfm-task-list-item@1.0.2: - resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} + /mdast-util-gfm-task-list-item@1.0.1: + resolution: {integrity: sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==} dependencies: '@types/mdast': 3.0.10 mdast-util-to-markdown: 1.3.0 dev: false - /mdast-util-gfm@2.0.2: - resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} + /mdast-util-gfm@2.0.1: + resolution: {integrity: sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==} dependencies: mdast-util-from-markdown: 1.2.0 - mdast-util-gfm-autolink-literal: 1.0.3 - mdast-util-gfm-footnote: 1.0.2 - mdast-util-gfm-strikethrough: 1.0.3 - mdast-util-gfm-table: 1.0.7 - mdast-util-gfm-task-list-item: 1.0.2 + mdast-util-gfm-autolink-literal: 1.0.2 + mdast-util-gfm-footnote: 1.0.1 + mdast-util-gfm-strikethrough: 1.0.2 + mdast-util-gfm-table: 1.0.6 + mdast-util-gfm-task-list-item: 1.0.1 mdast-util-to-markdown: 1.3.0 transitivePeerDependencies: - supports-color @@ -15475,7 +15589,7 @@ packages: '@rollup/plugin-node-resolve': 11.2.1(rollup@2.79.1) '@surma/rollup-plugin-off-main-thread': 2.2.3 asyncro: 3.0.0 - autoprefixer: 10.4.16(postcss@8.4.24) + autoprefixer: 10.4.16(postcss@8.4.31) babel-plugin-macros: 3.1.0 babel-plugin-transform-async-to-promises: 0.8.18 babel-plugin-transform-replace-expressions: 0.2.0(@babel/core@7.23.0) @@ -15487,11 +15601,11 @@ packages: gzip-size: 6.0.0 kleur: 4.1.5 lodash.merge: 4.6.2 - postcss: 8.4.24 + postcss: 8.4.31 pretty-bytes: 5.6.0 rollup: 2.79.1 rollup-plugin-bundle-size: 1.0.3 - rollup-plugin-postcss: 4.0.2(postcss@8.4.24) + rollup-plugin-postcss: 4.0.2(postcss@8.4.31) rollup-plugin-terser: 7.0.2(rollup@2.79.1) rollup-plugin-typescript2: 0.32.1(rollup@2.79.1)(typescript@4.9.5) rollup-plugin-visualizer: 5.9.0(rollup@2.79.1) @@ -15531,17 +15645,18 @@ packages: uvu: 0.5.6 dev: false - /micromark-extension-gfm-autolink-literal@1.0.5: - resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} + /micromark-extension-gfm-autolink-literal@1.0.3: + resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==} dependencies: micromark-util-character: 1.1.0 micromark-util-sanitize-uri: 1.1.0 micromark-util-symbol: 1.0.1 micromark-util-types: 1.0.2 + uvu: 0.5.6 dev: false - /micromark-extension-gfm-footnote@1.1.2: - resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} + /micromark-extension-gfm-footnote@1.0.4: + resolution: {integrity: sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==} dependencies: micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -15553,8 +15668,8 @@ packages: uvu: 0.5.6 dev: false - /micromark-extension-gfm-strikethrough@1.0.7: - resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} + /micromark-extension-gfm-strikethrough@1.0.4: + resolution: {integrity: sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==} dependencies: micromark-util-chunked: 1.0.0 micromark-util-classify-character: 1.0.0 @@ -15564,8 +15679,8 @@ packages: uvu: 0.5.6 dev: false - /micromark-extension-gfm-table@1.0.7: - resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} + /micromark-extension-gfm-table@1.0.5: + resolution: {integrity: sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==} dependencies: micromark-factory-space: 1.0.0 micromark-util-character: 1.1.0 @@ -15574,14 +15689,14 @@ packages: uvu: 0.5.6 dev: false - /micromark-extension-gfm-tagfilter@1.0.2: - resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} + /micromark-extension-gfm-tagfilter@1.0.1: + resolution: {integrity: sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==} dependencies: micromark-util-types: 1.0.2 dev: false - /micromark-extension-gfm-task-list-item@1.0.5: - resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} + /micromark-extension-gfm-task-list-item@1.0.3: + resolution: {integrity: sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==} dependencies: micromark-factory-space: 1.0.0 micromark-util-character: 1.1.0 @@ -15590,15 +15705,15 @@ packages: uvu: 0.5.6 dev: false - /micromark-extension-gfm@2.0.3: - resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} + /micromark-extension-gfm@2.0.1: + resolution: {integrity: sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==} dependencies: - micromark-extension-gfm-autolink-literal: 1.0.5 - micromark-extension-gfm-footnote: 1.1.2 - micromark-extension-gfm-strikethrough: 1.0.7 - micromark-extension-gfm-table: 1.0.7 - micromark-extension-gfm-tagfilter: 1.0.2 - micromark-extension-gfm-task-list-item: 1.0.5 + micromark-extension-gfm-autolink-literal: 1.0.3 + micromark-extension-gfm-footnote: 1.0.4 + micromark-extension-gfm-strikethrough: 1.0.4 + micromark-extension-gfm-table: 1.0.5 + micromark-extension-gfm-tagfilter: 1.0.1 + micromark-extension-gfm-task-list-item: 1.0.3 micromark-util-combine-extensions: 1.0.0 micromark-util-types: 1.0.2 dev: false @@ -16407,7 +16522,7 @@ packages: '@next/env': 13.4.19 '@swc/helpers': 0.5.1 busboy: 1.6.0 - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001541 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -16447,7 +16562,7 @@ packages: '@next/env': 13.5.3 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001540 + caniuse-lite: 1.0.30001523 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -16893,9 +17008,9 @@ packages: peerDependencies: webpack: ^4.0.0 dependencies: - cssnano: 5.1.14(postcss@8.4.30) + cssnano: 5.1.14(postcss@8.4.31) last-call-webpack-plugin: 3.0.0 - postcss: 8.4.30 + postcss: 8.4.31 webpack: 4.46.0 dev: true @@ -17448,22 +17563,12 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-calc@8.2.4(postcss@8.4.24): + /postcss-calc@8.2.4(postcss@8.4.31): resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} peerDependencies: postcss: ^8.2.2 dependencies: - postcss: 8.4.24 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-calc@8.2.4(postcss@8.4.30): - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 dev: true @@ -17479,7 +17584,7 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-colormin@5.3.0(postcss@8.4.24): + /postcss-colormin@5.3.0(postcss@8.4.31): resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -17488,20 +17593,7 @@ packages: browserslist: 4.21.10 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-colormin@5.3.0(postcss@8.4.30): - resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -17513,25 +17605,14 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-convert-values@5.1.3(postcss@8.4.24): + /postcss-convert-values@5.1.3(postcss@8.4.31): resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-convert-values@5.1.3(postcss@8.4.30): - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -17542,22 +17623,13 @@ packages: postcss: 7.0.39 dev: true - /postcss-discard-comments@5.1.2(postcss@8.4.24): + /postcss-discard-comments@5.1.2(postcss@8.4.31): resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - dev: true - - /postcss-discard-comments@5.1.2(postcss@8.4.30): - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /postcss-discard-duplicates@4.0.2: @@ -17567,22 +17639,13 @@ packages: postcss: 7.0.39 dev: true - /postcss-discard-duplicates@5.1.0(postcss@8.4.24): + /postcss-discard-duplicates@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - dev: true - - /postcss-discard-duplicates@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /postcss-discard-empty@4.0.1: @@ -17592,22 +17655,13 @@ packages: postcss: 7.0.39 dev: true - /postcss-discard-empty@5.1.1(postcss@8.4.24): + /postcss-discard-empty@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - dev: true - - /postcss-discard-empty@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /postcss-discard-overridden@4.0.1: @@ -17617,45 +17671,36 @@ packages: postcss: 7.0.39 dev: true - /postcss-discard-overridden@5.1.0(postcss@8.4.24): + /postcss-discard-overridden@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 + postcss: 8.4.31 dev: true - /postcss-discard-overridden@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 - dev: true - - /postcss-import@15.1.0(postcss@8.4.27): + /postcss-import@15.1.0(postcss@8.4.31): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.27 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.6 - /postcss-js@4.0.1(postcss@8.4.27): + /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.27 + postcss: 8.4.31 - /postcss-load-config@3.1.4(postcss@8.4.24): + /postcss-load-config@3.1.4(postcss@8.4.31): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -17668,28 +17713,11 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.24 + postcss: 8.4.31 yaml: 1.10.2 dev: true - /postcss-load-config@3.1.4(postcss@8.4.27): - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.27 - yaml: 1.10.2 - dev: true - - /postcss-load-config@4.0.1(postcss@8.4.27): + /postcss-load-config@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -17702,10 +17730,10 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.27 + postcss: 8.4.31 yaml: 2.3.1 - /postcss-loader@4.3.0(postcss@8.4.27)(webpack@4.46.0): + /postcss-loader@4.3.0(postcss@8.4.31)(webpack@4.46.0): resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -17715,7 +17743,7 @@ packages: cosmiconfig: 7.1.0 klona: 2.0.5 loader-utils: 2.0.4 - postcss: 8.4.27 + postcss: 8.4.31 schema-utils: 3.1.1 semver: 7.5.4 webpack: 4.46.0 @@ -17731,26 +17759,15 @@ packages: stylehacks: 4.0.3 dev: true - /postcss-merge-longhand@5.1.7(postcss@8.4.24): + /postcss-merge-longhand@5.1.7(postcss@8.4.31): resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 + postcss: 8.4.31 postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.24) - dev: true - - /postcss-merge-longhand@5.1.7(postcss@8.4.30): - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.30) + stylehacks: 5.1.1(postcss@8.4.31) dev: true /postcss-merge-rules@4.0.3: @@ -17765,7 +17782,7 @@ packages: vendors: 1.0.4 dev: true - /postcss-merge-rules@5.1.3(postcss@8.4.24): + /postcss-merge-rules@5.1.3(postcss@8.4.31): resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -17773,21 +17790,8 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.24) - postcss: 8.4.24 - postcss-selector-parser: 6.0.11 - dev: true - - /postcss-merge-rules@5.1.3(postcss@8.4.30): - resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.30) - postcss: 8.4.30 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true @@ -17799,23 +17803,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-minify-font-values@5.1.0(postcss@8.4.24): + /postcss-minify-font-values@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-minify-font-values@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -17829,27 +17823,15 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-minify-gradients@5.1.1(postcss@8.4.24): + /postcss-minify-gradients@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.24) - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-minify-gradients@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.30) - postcss: 8.4.30 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -17865,27 +17847,15 @@ packages: uniqs: 2.0.0 dev: true - /postcss-minify-params@5.1.4(postcss@8.4.24): + /postcss-minify-params@5.1.4(postcss@8.4.31): resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - cssnano-utils: 3.1.0(postcss@8.4.24) - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-minify-params@5.1.4(postcss@8.4.30): - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - cssnano-utils: 3.1.0(postcss@8.4.30) - postcss: 8.4.30 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -17899,109 +17869,58 @@ packages: postcss-selector-parser: 3.1.2 dev: true - /postcss-minify-selectors@5.2.1(postcss@8.4.24): + /postcss-minify-selectors@5.2.1(postcss@8.4.31): resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true - /postcss-minify-selectors@5.2.1(postcss@8.4.30): - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 - postcss-selector-parser: 6.0.11 - dev: true - - /postcss-modules-extract-imports@3.0.0(postcss@8.4.24): + /postcss-modules-extract-imports@3.0.0(postcss@8.4.31): resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.24 + postcss: 8.4.31 dev: true - /postcss-modules-extract-imports@3.0.0(postcss@8.4.30): - resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.30 - dev: true - - /postcss-modules-local-by-default@4.0.0(postcss@8.4.24): + /postcss-modules-local-by-default@4.0.0(postcss@8.4.31): resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.24) - postcss: 8.4.24 + icss-utils: 5.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 dev: true - /postcss-modules-local-by-default@4.0.0(postcss@8.4.30): - resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0(postcss@8.4.30) - postcss: 8.4.30 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-modules-scope@3.0.0(postcss@8.4.24): + /postcss-modules-scope@3.0.0(postcss@8.4.31): resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.24 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true - /postcss-modules-scope@3.0.0(postcss@8.4.30): - resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.30 - postcss-selector-parser: 6.0.11 - dev: true - - /postcss-modules-values@4.0.0(postcss@8.4.24): + /postcss-modules-values@4.0.0(postcss@8.4.31): resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.24) - postcss: 8.4.24 + icss-utils: 5.1.0(postcss@8.4.31) + postcss: 8.4.31 dev: true - /postcss-modules-values@4.0.0(postcss@8.4.30): - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0(postcss@8.4.30) - postcss: 8.4.30 - dev: true - - /postcss-modules@4.3.1(postcss@8.4.24): + /postcss-modules@4.3.1(postcss@8.4.31): resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} peerDependencies: postcss: ^8.0.0 @@ -18009,21 +17928,21 @@ packages: generic-names: 4.0.0 icss-replace-symbols: 1.1.0 lodash.camelcase: 4.3.0 - postcss: 8.4.24 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.24) - postcss-modules-local-by-default: 4.0.0(postcss@8.4.24) - postcss-modules-scope: 3.0.0(postcss@8.4.24) - postcss-modules-values: 4.0.0(postcss@8.4.24) + postcss: 8.4.31 + postcss-modules-extract-imports: 3.0.0(postcss@8.4.31) + postcss-modules-local-by-default: 4.0.0(postcss@8.4.31) + postcss-modules-scope: 3.0.0(postcss@8.4.31) + postcss-modules-values: 4.0.0(postcss@8.4.31) string-hash: 1.1.3 dev: true - /postcss-nested@6.0.1(postcss@8.4.27): + /postcss-nested@6.0.1(postcss@8.4.31): resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.27 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 /postcss-normalize-charset@4.0.1: @@ -18033,22 +17952,13 @@ packages: postcss: 7.0.39 dev: true - /postcss-normalize-charset@5.1.0(postcss@8.4.24): + /postcss-normalize-charset@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - dev: true - - /postcss-normalize-charset@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 dev: true /postcss-normalize-display-values@4.0.2: @@ -18060,23 +17970,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-display-values@5.1.0(postcss@8.4.24): + /postcss-normalize-display-values@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-display-values@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18090,23 +17990,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-positions@5.1.1(postcss@8.4.24): + /postcss-normalize-positions@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-positions@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18120,23 +18010,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.24): + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18149,23 +18029,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-string@5.1.0(postcss@8.4.24): + /postcss-normalize-string@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-string@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18178,23 +18048,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.24): + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18207,25 +18067,14 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-unicode@5.1.1(postcss@8.4.24): + /postcss-normalize-unicode@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-unicode@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18239,25 +18088,14 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-url@5.1.0(postcss@8.4.24): + /postcss-normalize-url@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: normalize-url: 6.1.0 - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-url@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - normalize-url: 6.1.0 - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18269,23 +18107,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-normalize-whitespace@5.1.1(postcss@8.4.24): + /postcss-normalize-whitespace@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-normalize-whitespace@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18298,25 +18126,14 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-ordered-values@5.1.3(postcss@8.4.24): + /postcss-ordered-values@5.1.3(postcss@8.4.31): resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0(postcss@8.4.24) - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-ordered-values@5.1.3(postcss@8.4.30): - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 3.1.0(postcss@8.4.30) - postcss: 8.4.30 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18330,7 +18147,7 @@ packages: postcss: 7.0.39 dev: true - /postcss-reduce-initial@5.1.1(postcss@8.4.24): + /postcss-reduce-initial@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -18338,18 +18155,7 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - postcss: 8.4.24 - dev: true - - /postcss-reduce-initial@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - caniuse-api: 3.0.0 - postcss: 8.4.30 + postcss: 8.4.31 dev: true /postcss-reduce-transforms@4.0.2: @@ -18362,23 +18168,13 @@ packages: postcss-value-parser: 3.3.1 dev: true - /postcss-reduce-transforms@5.1.0(postcss@8.4.24): + /postcss-reduce-transforms@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - dev: true - - /postcss-reduce-transforms@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true @@ -18414,24 +18210,13 @@ packages: svgo: 1.3.2 dev: true - /postcss-svgo@5.1.0(postcss@8.4.24): + /postcss-svgo@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - dev: true - - /postcss-svgo@5.1.0(postcss@8.4.30): - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-value-parser: 4.2.0 svgo: 2.8.0 dev: true @@ -18445,23 +18230,13 @@ packages: uniqs: 2.0.0 dev: true - /postcss-unique-selectors@5.1.1(postcss@8.4.24): + /postcss-unique-selectors@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.24 - postcss-selector-parser: 6.0.11 - dev: true - - /postcss-unique-selectors@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.30 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true @@ -18489,25 +18264,8 @@ packages: source-map-js: 1.0.2 dev: false - /postcss@8.4.24: - resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /postcss@8.4.27: - resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - - /postcss@8.4.30: - resolution: {integrity: sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -18564,7 +18322,7 @@ packages: '@prefresh/babel-plugin': 0.4.4 '@prefresh/webpack': 3.3.4(@prefresh/babel-plugin@0.4.4)(preact@10.17.1)(webpack@4.46.0) '@types/webpack': 4.41.33 - autoprefixer: 10.4.16(postcss@8.4.27) + autoprefixer: 10.4.16(postcss@8.4.31) babel-esm-plugin: 0.9.0(webpack@4.46.0) babel-loader: 8.3.0(@babel/core@7.23.0)(webpack@4.46.0) babel-plugin-macros: 3.1.0 @@ -18597,9 +18355,9 @@ packages: optimize-css-assets-webpack-plugin: 6.0.1(webpack@4.46.0) ora: 5.4.1 pnp-webpack-plugin: 1.7.0(typescript@5.2.2) - postcss: 8.4.27 - postcss-load-config: 3.1.4(postcss@8.4.27) - postcss-loader: 4.3.0(postcss@8.4.27)(webpack@4.46.0) + postcss: 8.4.31 + postcss-load-config: 3.1.4(postcss@8.4.31) + postcss-loader: 4.3.0(postcss@8.4.31)(webpack@4.46.0) preact: 10.17.1 preact-render-to-string: 6.2.1(preact@10.17.1) progress-bar-webpack-plugin: 2.1.0(webpack@4.46.0) @@ -19559,7 +19317,7 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.6 + resolve: 1.22.2 dev: true /redent@3.0.0: @@ -19695,8 +19453,8 @@ packages: resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} dependencies: '@types/mdast': 3.0.10 - mdast-util-gfm: 2.0.2 - micromark-extension-gfm: 2.0.3 + mdast-util-gfm: 2.0.1 + micromark-extension-gfm: 2.0.1 unified: 10.1.2 transitivePeerDependencies: - supports-color @@ -20049,7 +19807,7 @@ packages: maxmin: 2.1.0 dev: true - /rollup-plugin-postcss@4.0.2(postcss@8.4.24): + /rollup-plugin-postcss@4.0.2(postcss@8.4.31): resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} engines: {node: '>=10'} peerDependencies: @@ -20057,13 +19815,13 @@ packages: dependencies: chalk: 4.1.2 concat-with-sourcemaps: 1.1.0 - cssnano: 5.1.14(postcss@8.4.24) + cssnano: 5.1.14(postcss@8.4.31) import-cwd: 3.0.0 p-queue: 6.6.2 pify: 5.0.0 - postcss: 8.4.24 - postcss-load-config: 3.1.4(postcss@8.4.24) - postcss-modules: 4.3.1(postcss@8.4.24) + postcss: 8.4.31 + postcss-load-config: 3.1.4(postcss@8.4.31) + postcss-modules: 4.3.1(postcss@8.4.31) promise.series: 0.2.0 resolve: 1.22.2 rollup-pluginutils: 2.8.2 @@ -21304,25 +21062,14 @@ packages: postcss-selector-parser: 3.1.2 dev: true - /stylehacks@5.1.1(postcss@8.4.24): + /stylehacks@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.24 - postcss-selector-parser: 6.0.11 - dev: true - - /stylehacks@5.1.1(postcss@8.4.30): - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.21.10 - postcss: 8.4.30 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true @@ -21463,11 +21210,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.27 - postcss-import: 15.1.0(postcss@8.4.27) - postcss-js: 4.0.1(postcss@8.4.27) - postcss-load-config: 4.0.1(postcss@8.4.27) - postcss-nested: 6.0.1(postcss@8.4.27) + postcss: 8.4.31 + postcss-import: 15.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 4.0.1(postcss@8.4.31) + postcss-nested: 6.0.1(postcss@8.4.31) postcss-selector-parser: 6.0.11 resolve: 1.22.2 sucrase: 3.32.0 @@ -22015,7 +21762,7 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.27) + postcss-load-config: 4.0.1(postcss@8.4.31) resolve-from: 5.0.0 rollup: 3.29.4 source-map: 0.8.0-beta.0 @@ -22071,65 +21818,64 @@ packages: dependencies: safe-buffer: 5.2.1 - /turbo-darwin-64@1.10.13: - resolution: {integrity: sha512-vmngGfa2dlYvX7UFVncsNDMuT4X2KPyPJ2Jj+xvf5nvQnZR/3IeDEGleGVuMi/hRzdinoxwXqgk9flEmAYp0Xw==} + /turbo-darwin-64@1.10.14: + resolution: {integrity: sha512-I8RtFk1b9UILAExPdG/XRgGQz95nmXPE7OiGb6ytjtNIR5/UZBS/xVX/7HYpCdmfriKdVwBKhalCoV4oDvAGEg==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.10.13: - resolution: {integrity: sha512-eMoJC+k7gIS4i2qL6rKmrIQGP6Wr9nN4odzzgHFngLTMimok2cGLK3qbJs5O5F/XAtEeRAmuxeRnzQwTl/iuAw==} + /turbo-darwin-arm64@1.10.14: + resolution: {integrity: sha512-KAdUWryJi/XX7OD0alOuOa0aJ5TLyd4DNIYkHPHYcM6/d7YAovYvxRNwmx9iv6Vx6IkzTnLeTiUB8zy69QkG9Q==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.10.13: - resolution: {integrity: sha512-0CyYmnKTs6kcx7+JRH3nPEqCnzWduM0hj8GP/aodhaIkLNSAGAa+RiYZz6C7IXN+xUVh5rrWTnU2f1SkIy7Gdg==} + /turbo-linux-64@1.10.14: + resolution: {integrity: sha512-BOBzoREC2u4Vgpap/WDxM6wETVqVMRcM8OZw4hWzqCj2bqbQ6L0wxs1LCLWVrghQf93JBQtIGAdFFLyCSBXjWQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.10.13: - resolution: {integrity: sha512-0iBKviSGQQlh2OjZgBsGjkPXoxvRIxrrLLbLObwJo3sOjIH0loGmVIimGS5E323soMfi/o+sidjk2wU1kFfD7Q==} + /turbo-linux-arm64@1.10.14: + resolution: {integrity: sha512-D8T6XxoTdN5D4V5qE2VZG+/lbZX/89BkAEHzXcsSUTRjrwfMepT3d2z8aT6hxv4yu8EDdooZq/2Bn/vjMI32xw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.10.13: - resolution: {integrity: sha512-S5XySRfW2AmnTeY1IT+Jdr6Goq7mxWganVFfrmqU+qqq3Om/nr0GkcUX+KTIo9mPrN0D3p5QViBRzulwB5iuUQ==} + /turbo-windows-64@1.10.14: + resolution: {integrity: sha512-zKNS3c1w4i6432N0cexZ20r/aIhV62g69opUn82FLVs/zk3Ie0GVkSB6h0rqIvMalCp7enIR87LkPSDGz9K4UA==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.10.13: - resolution: {integrity: sha512-nKol6+CyiExJIuoIc3exUQPIBjP9nIq5SkMJgJuxsot2hkgGrafAg/izVDRDrRduQcXj2s8LdtxJHvvnbI8hEQ==} + /turbo-windows-arm64@1.10.14: + resolution: {integrity: sha512-rkBwrTPTxNSOUF7of8eVvvM+BkfkhA2OvpHM94if8tVsU+khrjglilp8MTVPHlyS9byfemPAmFN90oRIPB05BA==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.10.13: - resolution: {integrity: sha512-vOF5IPytgQPIsgGtT0n2uGZizR2N3kKuPIn4b5p5DdeLoI0BV7uNiydT7eSzdkPRpdXNnO8UwS658VaI4+YSzQ==} + /turbo@1.10.14: + resolution: {integrity: sha512-hr9wDNYcsee+vLkCDIm8qTtwhJ6+UAMJc3nIY6+PNgUTtXcQgHxCq8BGoL7gbABvNWv76CNbK5qL4Lp9G3ZYRA==} hasBin: true - requiresBuild: true optionalDependencies: - turbo-darwin-64: 1.10.13 - turbo-darwin-arm64: 1.10.13 - turbo-linux-64: 1.10.13 - turbo-linux-arm64: 1.10.13 - turbo-windows-64: 1.10.13 - turbo-windows-arm64: 1.10.13 + turbo-darwin-64: 1.10.14 + turbo-darwin-arm64: 1.10.14 + turbo-linux-64: 1.10.14 + turbo-linux-arm64: 1.10.14 + turbo-windows-64: 1.10.14 + turbo-windows-arm64: 1.10.14 dev: true /tween-functions@1.2.0: @@ -23002,7 +22748,7 @@ packages: optional: true dependencies: esbuild: 0.18.10 - postcss: 8.4.30 + postcss: 8.4.31 rollup: 3.29.4 terser: 5.20.0 optionalDependencies: @@ -23971,7 +23717,7 @@ packages: readable-stream: 3.6.0 dev: true - /zod-prisma@0.5.4(prisma@5.3.1)(zod@3.22.2): + /zod-prisma@0.5.4(prisma@5.3.1)(zod@3.22.3): resolution: {integrity: sha512-5Ca4Qd1a1jy1T/NqCEpbr0c+EsbjJfJ/7euEHob3zDvtUK2rTuD1Rc/vfzH8q8PtaR2TZbysD88NHmrLwpv3Xg==} engines: {node: '>=14'} hasBin: true @@ -23987,15 +23733,15 @@ packages: parenthesis: 3.1.8 prisma: 5.3.1 ts-morph: 13.0.3 - zod: 3.22.2 + zod: 3.22.3 dev: true /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false - /zod@3.22.2: - resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} + /zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} /zustand@4.4.1(@types/react@18.2.23)(react@18.2.0): resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==} @@ -24020,3 +23766,7 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/turbo.json b/turbo.json index c8e2052132..d192f95f70 100644 --- a/turbo.json +++ b/turbo.json @@ -70,6 +70,7 @@ "NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID", "NEXT_PUBLIC_FORMBRICKS_PMF_FORM_ID", "NEXT_PUBLIC_FORMBRICKS_URL", + "FORMBRICKS_ENCRYPTION_KEY", "IMPRINT_URL", "NEXT_PUBLIC_SENTRY_DSN", "SURVEY_BASE_URL",