feat: adds separate status indicator for both app and website (#2747)

Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
Piyush Gupta
2024-06-17 16:37:52 +05:30
committed by GitHub
parent e19dcdc0c4
commit 8f61ceb4ea
53 changed files with 1568 additions and 1221 deletions

View File

@@ -22,9 +22,7 @@ export const metadata = {
App surveys have 6-10x better conversion rates than emailed out surveys. This tutorial explains how to run an app survey in your web app in 10 to 15 minutes. Lets go!
<Note>
App Surveys are ideal for websites that **have a user authentication** system. If you are looking to run
surveys on your public facing website, head over to the [Website Surveys Quickstart
Guide](/website-surveys/quickstart).
App Surveys are ideal for websites that **have a user authentication** system. If you are looking to run surveys on your public facing website, head over to the [Website Surveys Quickstart Guide](/website-surveys/quickstart).
</Note>
1. **Create a free Formbricks Cloud account**: While you can [self-host](/self-hosting/deployment) Formbricks, but the quickest and easiest way to get started is with the free Cloud plan. Just [sign up here](https://app.formbricks.com/auth/signup) and you'll be guided to our onboarding like below:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -16,7 +16,7 @@ export const metadata = {
### Overview
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. Its available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Surveys and Website Surveys into your projects. In this section, we'll explore how to leverage the SDK specifically for **app** surveys. Its available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
### Install
@@ -26,9 +26,11 @@ The Formbricks JS SDK is a 2-in-1 SDK for seamlessly integrating both App Survey
```js {{ title: 'npm' }}
npm install @formbricks/js
```
```js {{ title: 'yarn' }}
yarn add @formbricks/js
```
```js {{ title: 'pnpm' }}
pnpm add @formbricks/js
```
@@ -51,9 +53,10 @@ import formbricks from "@formbricks/js/app";
formbricks.init({
environmentId: "<your-environment-id>", // required
apiHost: "<your-api-host>", // required
userId: "<user-id>" // required
userId: "<user-id>", // required
});
```
</CodeGroup>
</Col>
@@ -67,15 +70,16 @@ Formbricks JS is a client SDK meant to be run client-side in their browser so ma
```js
if (window !== undefined) {
formbricks.init({
environmentId: "<your-environment-id>",
apiHost: "<your-api-host>",
userId: "<user-id>"
});
formbricks.init({
environmentId: "<your-environment-id>",
apiHost: "<your-api-host>",
userId: "<user-id>",
});
} else {
console.error("Window object not accessible to init Formbricks");
console.error("Window object not accessible to init Formbricks");
}
```
</CodeGroup>
</Col>
@@ -91,6 +95,7 @@ You can set custom attributes for the identified user. This can be helpful for s
```js
formbricks.setAttribute("Plan", "Paid");
```
</CodeGroup>
</Col>
@@ -104,10 +109,10 @@ Track user actions to trigger surveys based on user interactions, such as button
```js
formbricks.track("Clicked on Claim");
```
</CodeGroup>
</Col>
### Logout
To log out and deinitialize Formbricks, use the formbricks.logout() function. This action clears the current initialization configuration and erases stored frontend information, such as the surveys a user has viewed or completed. It's an important step when a user logs out of your application or when you want to reset Formbricks.
@@ -118,6 +123,7 @@ To log out and deinitialize Formbricks, use the formbricks.logout() function. Th
```js
formbricks.logout();
```
</CodeGroup>
</Col>
@@ -133,6 +139,7 @@ Reset the current instance and fetch the latest surveys and state again:
```js
formbricks.reset();
```
</CodeGroup>
</Col>
@@ -140,7 +147,11 @@ formbricks.reset();
Listen for page changes and dynamically show surveys configured via no-code actions in the Formbricks app:
<Note> This is only needed when your framework has a custom routing system and you want to trigger surveys on route changes. For example: NextJs</Note>
<Note>
{" "}
This is only needed when your framework has a custom routing system and you want to trigger surveys on route
changes. For example: NextJs
</Note>
<Col>
<CodeGroup>
@@ -148,6 +159,7 @@ Listen for page changes and dynamically show surveys configured via no-code acti
```js
formbricks.registerRouteChange();
```
</CodeGroup>
</Col>
@@ -167,7 +179,7 @@ In case you dont see your survey right away, here's what you can do. Go throu
### Formbricks Cloud and your app are not connected properly.
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the Setup Checklist in the Settings. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the App connection in the Configuration. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
<MdxImage
src={I1}

View File

@@ -67,8 +67,7 @@ The API requests are authorized with a personal API key. This API key gives you
/>
<Note>
### Store API key safely!
Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
### Store API key safely! Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
</Note>
### Test your API Key
@@ -123,7 +122,8 @@ Hit the below request to verify that you are authenticated with your API Key and
"id": "cll2m30r60003mx0hnemjfckr",
"name": "My Product"
},
"widgetSetupCompleted": false
"appSetupCompleted": false,
"websiteSetupCompleted": false,
}
```
```json {{ title: '401 Not Authenticated' }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,4 +1,5 @@
import { MdxImage } from "@/components/MdxImage";
import I1 from "./images/1-set-up-website-micro-survey-popup.webp";
export const metadata = {
title: "Formbricks Website Survey SDK",
@@ -142,4 +143,25 @@ This activates detailed debug messages in the browser console, providing deeper
---
## Troubleshooting
In case you dont see your survey right away, here's what you can do. Go through these to find the error fast:
### Formbricks Cloud and your website are not connected properly.
Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted instance's URL and go to the Website connection in the Configuration. If the status is still indicated as “Not connected” your app hasn't yet pinged the Formbricks Cloud:
<MdxImage
src={I1}
alt="setup checklist ui of survey popup for website surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
**How to fix it:**
1. Check if your website loads the Formbricks widget correctly.
2. Make sure you have `debug` mode enabled in your integration and you should see the Formbricks debug logs in your browser console while being in your app (right click in the browser, `Inspect`, switch to the console tab). If you dont see them, double check your integration.
---
If you have any questions or need help, feel free to reach out to us on our **[Discord](https://formbricks.com/discord)**

View File

@@ -67,8 +67,7 @@ The API requests are authorized with a personal API key. This API key gives you
/>
<Note>
### Store API key safely! Anyone who has your API key has full control over your account. For security
reasons, you cannot view the API key again.
### Store API key safely! Anyone who has your API key has full control over your account. For security reasons, you cannot view the API key again.
</Note>
### Test your API Key
@@ -123,7 +122,8 @@ Hit the below request to verify that you are authenticated with your API Key and
"id": "cll2m30r60003mx0hnemjfckr",
"name": "My Product"
},
"widgetSetupCompleted": false
"appSetupCompleted": false,
"websiteSetupCompleted": false,
}
```
```json {{ title: '401 Not Authenticated' }}

View File

@@ -22,9 +22,7 @@ export const metadata = {
Website Surveys make it easy for your public website visitors to give you feedback. They are a great way to get feedback from your users, without interrupting their workflow. This quickstart guide will show you how to create your first website survey in under 5 minutes.
<Note>
Website Surveys are ideal for **public facing websites**. If you are looking to run surveys in your app
where you have user identification & want advanced user targeting, head over to the [App Surveys Quickstart
Guide](/app-surveys/quickstart).
Website Surveys are ideal for **public facing websites**. If you are looking to run surveys in your app where you have user identification & want advanced user targeting, head over to the [App Surveys Quickstart Guide](/app-surveys/quickstart).
</Note>
1. **Create a free Formbricks Cloud account**: While you can [self-host](/self-hosting/deployment) Formbricks, but the quickest and easiest way to get started is with the free Cloud plan. Just [sign up here](https://app.formbricks.com/auth/signup) and you'll be guided to our onboarding like below:

View File

@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,118 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://formbricks.com</loc><lastmod>2024-04-08T07:33:12.606Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/author/johannes</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/author/shubham</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/author/sudhanshu</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/best-feedback-app-and-how-to-use-them</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/best-hotjar-alternatives-2024-incl-open-source</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/best-open-source-survey-software-2023</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/best-website-feedback-tools-2024</loc><lastmod>2024-04-08T07:33:12.613Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/experience-management-open-source</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/first-end-to-endcommunity-feature</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/github-accelerator-experience</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/granular-targeting-in-app-surveys</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/how-smart-writers-use-formbricks-open-source-tool-to-measure-the-quality-of-their-newsletter-content</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/inaugural-batch-github-accelerator</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/join-the-formtribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/open-source-forms-will-save-the-world</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/open-source-qualtrics-beats-typeform</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/remove-branding-for-free</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/snoopforms-becomes-formbricks</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/terraform-ecs-aws-self-hosting</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/the-perfect-waitlist-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/understanding-formbricks-self-hosting</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/blog/why-open-source-no-code-is-the-future-of-enterprise-gov-software</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/careers</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/ContributorGrid</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/HallOfFame</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/HeaderTribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/LayoutTribe</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/LevelCard</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/LevelGrid</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/community/Roadmap</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/concierge</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs-feedback</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/feature-chaser</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/feedback-box</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/gdpr</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/gdpr-guide</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/imprint</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/improve-trial-conversion</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/in-app-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/interview-prompt</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/learn-from-churn</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/measure-product-market-fit</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/onboarding-segmentation</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/open-source-form-builder</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/oss-friends</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/pricing</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/privacy-policy</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/terms</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/vs-formspree</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/vs-google-forms</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/vs-ohmyform</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/website-survey</loc><lastmod>2024-04-08T07:33:12.614Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/client/responses</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/action-classes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/attribute-classes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/me</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/people</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/client/overview</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/responses</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/webhooks</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/client/displays</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/cancel-subscription</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/client/people</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/feedback-box</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/feature-chaser</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/docs-feedback</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/improve-trial-cr</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/api-key-setup</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/improve-email-content</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/pmf-survey</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/best-practices/interview-prompt</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/demo</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/how-we-code</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/introduction</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/creating-a-service</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/setup</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/additional-features/multi-language-surveys</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/contributing/troubleshooting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/getting-started/quickstart-in-app-survey</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/faq</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/in-app-surveys/actions</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/client/actions</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/getting-started/framework-guides</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/in-app-surveys/advanced-targeting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/in-app-surveys/user-identification</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/getting-started/troubleshooting</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/in-app-surveys/attributes</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/api/management/surveys</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/make</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/airtable</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/notion</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/zapier</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/data-prefilling</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/wordpress</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/introduction/what-is-formbricks</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/introduction/how-it-works</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/introduction/why-is-it-better</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/single-use-links</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/hidden-fields</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/quickstart</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/source-tracking</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/docker</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/deployment</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/start-at-question</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/link-surveys/user-identification</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/enterprise</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/external-auth-providers</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/migration-guide</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/production</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/self-hosting/license</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/n8n</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://formbricks.com/docs/integrations/google-sheets</loc><lastmod>2024-04-08T07:33:12.615Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
</urlset>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://formbricks.com/sitemap-0.xml</loc></sitemap>
</sitemapindex>

View File

@@ -20,13 +20,13 @@ interface HowToSendCardProps {
export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowToSendCardProps) => {
const [open, setOpen] = useState(false);
const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false);
const [appSetupCompleted, setAppSetupCompleted] = useState(false);
const [websiteSetupCompleted, setWebsiteSetupCompleted] = useState(false);
useEffect(() => {
if (environment && environment.widgetSetupCompleted) {
setWidgetSetupCompleted(true);
} else {
setWidgetSetupCompleted(false);
if (environment) {
setAppSetupCompleted(environment.appSetupCompleted);
setWebsiteSetupCompleted(environment.websiteSetupCompleted);
}
}, [environment]);
@@ -76,7 +76,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
icon: EarthIcon,
description: "Run targeted surveys on public websites.",
comingSoon: false,
alert: !widgetSetupCompleted,
alert: !websiteSetupCompleted,
},
{
id: "app",
@@ -84,7 +84,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
icon: MonitorIcon,
description: "Embed a survey in your web app to collect responses with user identification.",
comingSoon: false,
alert: !widgetSetupCompleted,
alert: !appSetupCompleted,
},
{
id: "link",
@@ -177,16 +177,16 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
<AlertCircleIcon className="h-5 w-5 text-amber-500" />
<div className=" text-amber-800">
<p className="text-xs font-semibold">
Your app is not yet connected to Formbricks.
Your ${option.id} is not yet connected to Formbricks.
</p>
<p className="text-xs font-normal">
<Link
href={`/environments/${environment.id}/product/setup`}
href={`/environments/${environment.id}/product/${option.id}-connection`}
className="underline hover:text-amber-900"
target="_blank">
Connect Formbricks
</Link>{" "}
and launch surveys in your app or website.
and launch surveys in your {option.id}.
</p>
</div>
</div>

View File

@@ -13,7 +13,8 @@ export const TopControlBar = ({ environment, environments }: SideBarProps) => {
<div className="fixed inset-0 top-0 z-30 flex h-14 w-full items-center justify-end bg-slate-50 px-6">
<div className="shadow-xs z-10">
<div className="flex w-fit space-x-2 py-2">
<WidgetStatusIndicator environment={environment} type="mini" />
<WidgetStatusIndicator environment={environment} size="mini" type="website" />
<WidgetStatusIndicator environment={environment} size="mini" type="app" />
<TopControlButtons
environment={environment}
environments={environments}

View File

@@ -6,29 +6,30 @@ import { Label } from "@formbricks/ui/Label";
interface WidgetStatusIndicatorProps {
environment: TEnvironment;
type: "large" | "mini";
size: "large" | "mini";
type: "app" | "website";
}
export const WidgetStatusIndicator = ({ environment, type }: WidgetStatusIndicatorProps) => {
export const WidgetStatusIndicator = ({ environment, size, type }: WidgetStatusIndicatorProps) => {
const stati = {
notImplemented: {
icon: AlertTriangleIcon,
title: "Connect Formbricks to your app or website.",
subtitle:
"Your app or website is not yet connected with Formbricks. To run in-app surveys follow the setup guide.",
shortText: "Connect Formbricks to your app or website",
title: `Your ${type} is not yet connected.`,
subtitle: `Connect your ${type} with Formbricks to get started. To run ${type === "app" ? "in-app" : "website"} surveys follow the setup guide.`,
shortText: `Connect your ${type} with Formbricks`,
},
running: {
icon: CheckIcon,
title: "Receiving data.",
subtitle: "Your app or website is connected with Formbricks.",
shortText: "Connected",
title: "Receiving data 💃🕺",
subtitle: `Your ${type} is connected with Formbricks.`,
shortText: `${type === "app" ? "App" : "Website"} connected`,
},
};
const setupStatus = type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
let status: "notImplemented" | "running" | "issue";
if (environment.widgetSetupCompleted) {
if (setupStatus) {
status = "running";
} else {
status = "notImplemented";
@@ -36,19 +37,19 @@ export const WidgetStatusIndicator = ({ environment, type }: WidgetStatusIndicat
const currentStatus = stati[status];
if (type === "large") {
if (size === "large") {
return (
<div
className={clsx(
"flex flex-col items-center justify-center space-y-2 rounded-lg py-6 text-center",
status === "notImplemented" && "bg-slate-100",
status === "running" && "bg-green-100"
"flex flex-col items-center justify-center space-y-2 rounded-lg border py-6 text-center",
status === "notImplemented" && "border-slate-200 bg-slate-100",
status === "running" && "border-emerald-200 bg-emerald-100"
)}>
<div
className={clsx(
"flex h-12 w-12 items-center justify-center rounded-full bg-white p-2",
status === "notImplemented" && "text-slate-700",
status === "running" && "text-green-700"
"flex h-12 w-12 items-center justify-center rounded-full border bg-white p-2",
status === "notImplemented" && "border-slate-200 text-slate-700",
status === "running" && "border-emerald-200 text-emerald-700"
)}>
<currentStatus.icon />
</div>
@@ -57,25 +58,27 @@ export const WidgetStatusIndicator = ({ environment, type }: WidgetStatusIndicat
</div>
);
}
if (type === "mini") {
if (size === "mini") {
return (
<Link href={`/environments/${environment.id}/product/setup`}>
<div className="group flex justify-center">
<div className="flex items-center space-x-2 rounded-lg bg-slate-100 p-2">
<Label className="group-hover:cursor-pointer group-hover:underline">
{currentStatus.shortText}
</Label>
<div
className={clsx(
"h-5 w-5 rounded-full",
status === "notImplemented" && "text-amber-600",
status === "running" && "bg-green-100 text-green-700"
)}>
<currentStatus.icon className="h-5 w-5" />
<div className="flex gap-2">
<Link href={`/environments/${environment.id}/product/${type}-connection`}>
<div className="group flex justify-center">
<div className="flex items-center space-x-2 rounded-lg bg-slate-100 p-2">
{status === "running" ? (
<span className="relative flex h-3 w-3">
<span className="animate-ping-slow absolute inline-flex h-full w-full rounded-full bg-emerald-500 opacity-75"></span>
<span className="relative inline-flex h-3 w-3 rounded-full bg-emerald-500"></span>
</span>
) : (
<AlertTriangleIcon className="h-[14px] w-[14px] text-amber-600" />
)}
<Label className="group-hover:cursor-pointer group-hover:underline">
{currentStatus.shortText}
</Label>
</div>
</div>
</div>
</Link>
</Link>
</div>
);
} else {
return null;

View File

@@ -64,19 +64,28 @@ const Page = async ({ params }) => {
const isN8nIntegrationConnected = isIntegrationConnected("n8n");
const isSlackIntegrationConnected = isIntegrationConnected("slack");
const widgetSetupCompleted = environment?.appSetupCompleted || environment?.websiteSetupCompleted;
const bothSetupCompleted = environment?.appSetupCompleted && environment?.websiteSetupCompleted;
const integrationCards = [
{
docsHref: "https://formbricks.com/docs/getting-started/framework-guides#next-js",
docsText: "Docs",
docsNewTab: true,
connectHref: `/environments/${environmentId}/product/setup`,
connectHref: `/environments/${environmentId}/product/app-connection`,
connectText: "Connect",
connectNewTab: false,
label: "Javascript Widget",
description: "Integrate Formbricks into your Webapp",
icon: <Image src={JsLogo} alt="Javascript Logo" />,
connected: environment?.widgetSetupCompleted,
statusText: environment?.widgetSetupCompleted ? "Connected" : "Not Connected",
connected: widgetSetupCompleted,
statusText: bothSetupCompleted
? "app & website connected"
: environment?.appSetupCompleted
? "app Connected"
: environment?.websiteSetupCompleted
? "website connected"
: "Not Connected",
},
{
docsHref: "https://formbricks.com/docs/integrations/zapier",

View File

@@ -38,7 +38,7 @@ const Loading = () => {
},
{
title: "Your EnvironmentId",
description: "This Id uniquely identifies this Formbricks environment.",
description: "This id uniquely identifies this Formbricks environment.",
skeletonLines: [{ classes: "h-6 w-4/6 rounded-full" }],
},
{
@@ -88,10 +88,16 @@ const Loading = () => {
current: pathname?.includes("/api-keys"),
},
{
id: "setup",
label: "Setup Guide",
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/setup"),
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
},
];
@@ -105,13 +111,13 @@ const Loading = () => {
<div
key={navElem.id}
className={cn(
navElem.id === "setup"
navElem.id === "app-connection"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "setup" ? "page" : undefined}>
aria-current={navElem.id === "app-connection" ? "page" : undefined}>
{navElem.label}
</div>
))}

View File

@@ -1,4 +1,6 @@
import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator";
import { EnvironmentIdField } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/EnvironmentIdField";
import { SetupInstructions } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/SetupInstructions";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { getMultiLanguagePermission } from "@formbricks/ee/lib/service";
import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants";
@@ -7,9 +9,7 @@ import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/ser
import { EnvironmentNotice } from "@formbricks/ui/EnvironmentNotice";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
import { SettingsCard } from "../../settings/components/SettingsCard";
import { EnvironmentIdField } from "./components/EnvironmentIdField";
import { SetupInstructions } from "./components/SetupInstructions";
import { SettingsCard } from "../../../settings/components/SettingsCard";
const Page = async ({ params }) => {
const [environment, organization] = await Promise.all([
@@ -32,20 +32,20 @@ const Page = async ({ params }) => {
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation
environmentId={params.environmentId}
activeId="setup"
activeId="app-connection"
isMultiLanguageAllowed={isMultiLanguageAllowed}
/>
</PageHeader>
<div className="space-y-4">
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/product/setup" />
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/product/app-connection" />
<SettingsCard
title="Widget Status"
description="Check if the Formbricks widget is alive and kicking.">
{environment && <WidgetStatusIndicator environment={environment} type="large" />}
title="App Connection Status"
description="Check if your app is successfully connected with Formbricks. Reload page to recheck.">
{environment && <WidgetStatusIndicator environment={environment} size="large" type="app" />}
</SettingsCard>
<SettingsCard
title="Your EnvironmentId"
description="This Id uniquely identifies this Formbricks environment.">
description="This id uniquely identifies this Formbricks environment.">
<EnvironmentIdField environmentId={params.environmentId} />
</SettingsCard>
<SettingsCard
@@ -56,6 +56,7 @@ const Page = async ({ params }) => {
environmentId={params.environmentId}
webAppUrl={WEBAPP_URL}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
type="app"
/>
</SettingsCard>
</div>

View File

@@ -21,12 +21,14 @@ interface SetupInstructionsProps {
environmentId: string;
webAppUrl: string;
isFormbricksCloud: boolean;
type: "app" | "website";
}
export const SetupInstructions = ({
environmentId,
webAppUrl,
isFormbricksCloud,
type,
}: SetupInstructionsProps) => {
const [activeTab, setActiveTab] = useState(tabs[0].id);
@@ -35,23 +37,24 @@ export const SetupInstructions = ({
<TabBar tabs={tabs} activeId={activeTab} setActiveId={setActiveTab} />
<div className="px-6 py-5">
{activeTab === "npm" ? (
<div className="prose prose-slate">
<p className="text-lg font-semibold text-slate-800">Step 1: Install with NPM or Yarn</p>
<div className="prose prose-slate prose-p:my-2 prose-p:text-sm prose-p:text-slate-600 prose-h4:text-slate-800 prose-h4:pt-2">
<h4>Step 1: Install with pnpm, npm or yarn</h4>
<CodeBlock language="sh">pnpm install @formbricks/js</CodeBlock>
<p>or</p>
<CodeBlock language="sh">npm install @formbricks/js</CodeBlock>
<p>or</p>
<CodeBlock language="sh">yarn add @formbricks/js</CodeBlock>
<p className="pt-4 text-lg font-semibold text-slate-800">Step 2: Initialize widget</p>
<h4>Step 2: Initialize widget</h4>
<p>Import Formbricks and initialize the widget in your Component (e.g. App.tsx):</p>
<CodeBlock language="js">{`import formbricks from "@formbricks/js/website";
<CodeBlock language="js">{`import formbricks from "@formbricks/js/${type}";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "${environmentId}",
environmentId: "${environmentId}", ${type === "app" ? `\n userId: "<user-id>",` : ""}
apiHost: "${webAppUrl}",
});
}`}</CodeBlock>
<ul className="list-disc">
<ul className="list-disc text-sm">
<li>
<span className="font-semibold">environmentId:</span> Used to identify the correct
environment: {environmentId} is yours.
@@ -60,20 +63,26 @@ if (typeof window !== "undefined") {
<span className="font-semibold">apiHost:</span> This is the URL of your Formbricks backend.
</li>
</ul>
<p className="text-lg font-semibold text-slate-800">You&apos;re done 🎉</p>
<h4>Step 3: Debug mode</h4>
<p>
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
Switch on the debug mode by appending <i>?formbricksDebug=true</i> to the URL where you load the
Formbricks SDK. Open the browser console to see the logs.{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/developer-docs/app-survey-sdk#debug-mode"
target="_blank">
Read docs.
</Link>{" "}
</p>
<h4>You&apos;re done 🎉</h4>
<p>
Your {type} now communicates with Formbricks - sending events, and loading surveys
automatically!
</p>
<ul className="list-disc text-slate-700">
<ul className="list-disc text-sm text-slate-700">
<li>
<span className="font-semibold">Does your widget work? </span>
<span>Scroll to the top!</span>
</li>
<li>
<span className="font-semibold">
Need a more detailed setup guide for React, Next.js or Vue.js?
</span>{" "}
<span>Need a more detailed setup guide for React, Next.js or Vue.js?</span>{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/website-surveys/quickstart"
@@ -82,22 +91,20 @@ if (typeof window !== "undefined") {
</Link>
</li>
<li>
<span className="font-semibold">Have a problem?</span>{" "}
<span>Not working?</span>{" "}
<Link className="decoration-brand-dark" href="https://formbricks.com/discord" target="_blank">
Join Discord
</Link>{" "}
or{" "}
<Link
className="decoration-brand-dark"
target="_blank"
href="https://github.com/formbricks/formbricks/issues">
Open an issue on GitHub
open an issue on GitHub
</Link>{" "}
or{" "}
<Link className="decoration-brand-dark" href="https://formbricks.com/discord" target="_blank">
join Discord.
</Link>
</li>
<li>
<span className="font-semibold">
Want to learn how to add user attributes, custom events and more?
</span>{" "}
<span>Want to learn how to add user attributes, custom events and more?</span>{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/attributes/why"
@@ -108,22 +115,33 @@ if (typeof window !== "undefined") {
</ul>
</div>
) : activeTab === "html" ? (
<div className="prose prose-slate">
<p className="text-lg font-semibold text-slate-800">Step 1: Copy and paste code</p>
<div className="prose prose-slate prose-p:my-2 prose-p:text-sm prose-p:text-slate-600 prose-h4:text-slate-800 prose-h4:pt-2">
<h4>Step 1: Copy and paste code</h4>
<p>
Insert this code into the <code>{`<head>`}</code> tag of your website:
Insert this code into the <code>{`<head>`}</code> tag of your {type}:
</p>
<CodeBlock language="js">{`<!-- START Formbricks Surveys -->
<script type="text/javascript">
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.5/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="${webAppUrl}/api/packages/${type}";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", ${type === "app" ? `\n userId: "<user-id>",` : ""} apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
</script>
<!-- END Formbricks Surveys -->`}</CodeBlock>
<p className="text-lg font-semibold text-slate-800">You&apos;re done 🎉</p>
<h4>Step 2: Debug mode</h4>
<p>
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
Switch on the debug mode by appending <i>?formbricksDebug=true</i> to the URL where you load the
Formbricks SDK. Open the browser console to see the logs.{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/developer-docs/app-survey-sdk#debug-mode"
target="_blank">
Read docs.
</Link>{" "}
</p>
<ul className="list-disc text-slate-700">
<h4>You&apos;re done 🎉</h4>
<p>
Your {type} now communicates with Formbricks - sending events, and loading surveys
automatically!
</p>
<ul className="list-disc text-sm text-slate-700">
<li>
<span className="font-semibold">Does your widget work? </span>
<span>Scroll to the top!</span>

View File

@@ -0,0 +1,137 @@
"use client";
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@formbricks/lib/cn";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div className="w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 text-left shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-4 ">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div
className={`flex animate-pulse flex-col items-center justify-center space-y-2 rounded-lg bg-slate-200 py-6 text-center ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
const Loading = () => {
const pathname = usePathname();
const cards = [
{
title: "Widget Status",
description: "Check if the Formbricks widget is alive and kicking.",
skeletonLines: [{ classes: " h-44 max-w-full rounded-md" }],
},
{
title: "Your EnvironmentId",
description: "This id uniquely identifies this Formbricks environment.",
skeletonLines: [{ classes: "h-6 w-4/6 rounded-full" }],
},
{
title: "How to setup",
description: "Follow these steps to setup the Formbricks widget within your app",
skeletonLines: [
{ classes: "h-6 w-24 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-6 w-24 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
],
},
];
let navigation = [
{
id: "general",
label: "General",
icon: <UsersIcon className="h-5 w-5" />,
current: pathname?.includes("/general"),
},
{
id: "look",
label: "Look & Feel",
icon: <BrushIcon className="h-5 w-5" />,
current: pathname?.includes("/look"),
},
{
id: "languages",
label: "Survey Languages",
icon: <LanguagesIcon className="h-5 w-5" />,
hidden: true,
current: pathname?.includes("/languages"),
},
{
id: "tags",
label: "Tags",
icon: <TagIcon className="h-5 w-5" />,
current: pathname?.includes("/tags"),
},
{
id: "api-keys",
label: "API Keys",
icon: <KeyIcon className="h-5 w-5" />,
current: pathname?.includes("/api-keys"),
},
{
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
},
];
return (
<div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<div className="grid h-10 w-full grid-cols-[auto,1fr] ">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{navigation.map((navElem) => (
<div
key={navElem.id}
className={cn(
navElem.id === "website-connection"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "website-connection" ? "page" : undefined}>
{navElem.label}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</PageContentWrapper>
</div>
);
};
export default Loading;

View File

@@ -0,0 +1,67 @@
import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator";
import { EnvironmentIdField } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/EnvironmentIdField";
import { SetupInstructions } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/SetupInstructions";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { getMultiLanguagePermission } from "@formbricks/ee/lib/service";
import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { EnvironmentNotice } from "@formbricks/ui/EnvironmentNotice";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
import { SettingsCard } from "../../../settings/components/SettingsCard";
const Page = async ({ params }) => {
const [environment, organization] = await Promise.all([
getEnvironment(params.environmentId),
getOrganizationByEnvironmentId(params.environmentId),
]);
if (!environment) {
throw new Error("Environment not found");
}
if (!organization) {
throw new Error("Organization not found");
}
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
return (
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation
environmentId={params.environmentId}
activeId="website-connection"
isMultiLanguageAllowed={isMultiLanguageAllowed}
/>
</PageHeader>
<div className="space-y-4">
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/product/website-connection" />
<SettingsCard
title="Website Connection Status"
description="Check if your website is successfully connected with Formbricks. Reload page to recheck.">
{environment && <WidgetStatusIndicator environment={environment} size="large" type="website" />}
</SettingsCard>
<SettingsCard
title="Your EnvironmentId"
description="This id uniquely identifies this Formbricks environment.">
<EnvironmentIdField environmentId={params.environmentId} />
</SettingsCard>
<SettingsCard
title="How to setup"
description="Follow these steps to setup the Formbricks widget within your website"
noPadding>
<SetupInstructions
environmentId={params.environmentId}
webAppUrl={WEBAPP_URL}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
type="website"
/>
</SettingsCard>
</div>
</PageContentWrapper>
);
};
export default Page;

View File

@@ -72,10 +72,16 @@ const Loading = () => {
current: pathname?.includes("/api-keys"),
},
{
id: "setup",
label: "Setup Guide",
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/setup"),
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
},
];

View File

@@ -54,11 +54,18 @@ export const ProductConfigNavigation = ({
current: pathname?.includes("/api-keys"),
},
{
id: "setup",
label: "Setup Guide",
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
href: `/environments/${environmentId}/product/setup`,
current: pathname?.includes("/setup"),
href: `/environments/${environmentId}/product/website-connection`,
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
href: `/environments/${environmentId}/product/app-connection`,
current: pathname?.includes("/app-connection"),
},
];

View File

@@ -81,10 +81,16 @@ const Loading = () => {
current: pathname?.includes("/api-keys"),
},
{
id: "setup",
label: "Setup Guide",
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/setup"),
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
},
];

View File

@@ -56,10 +56,16 @@ const Loading = () => {
current: pathname?.includes("/api-keys"),
},
{
id: "setup",
label: "Setup Guide",
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/setup"),
current: pathname?.includes("/website-connection"),
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
},
];

View File

@@ -5,7 +5,6 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
import { SubmitHandler, useForm, useWatch } from "react-hook-form";
import toast from "react-hot-toast";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { TMembershipRole } from "@formbricks/types/memberships";
import { TOrganization } from "@formbricks/types/organizations";

View File

@@ -22,7 +22,7 @@ export const EmptyAppSurveys = ({ environment, surveyType = "app" }: TEmptyAppSu
Connect your {surveyType} with Formbricks to run {surveyType} surveys.
</p>
<Link className="mt-2" href={`/environments/${environment.id}/product/setup`}>
<Link className="mt-2" href={`/environments/${environment.id}/product/${surveyType}-connection`}>
<Button variant="darkCTA" size="sm" className="flex w-[120px] justify-center">
Connect
</Button>

View File

@@ -48,6 +48,9 @@ export const ResponseTimeline = ({
const [isViewer, setIsViewer] = useState(false);
const loadingRef = useRef(null);
const widgetSetupCompleted =
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
useEffect(() => {
const currentLoadingRef = loadingRef.current;
@@ -86,7 +89,7 @@ export const ResponseTimeline = ({
<div className="space-y-4">
{(survey.type === "app" || survey.type === "website") &&
responses.length === 0 &&
!environment.widgetSetupCompleted ? (
!widgetSetupCompleted ? (
<EmptyAppSurveys environment={environment} surveyType={survey.type} />
) : isFetchingFirstPage ? (
<SkeletonLoader type="response" />
@@ -96,6 +99,7 @@ export const ResponseTimeline = ({
environment={environment}
noWidgetRequired={survey.type === "link"}
emptyMessage={totalResponseCount === 0 ? undefined : "No response matches your filter"}
widgetSetupCompleted={widgetSetupCompleted}
/>
) : (
<div>

View File

@@ -22,17 +22,19 @@ export const SuccessMessage = ({ environment, survey, webAppUrl, user }: Summary
const [confetti, setConfetti] = useState(false);
const isAppSurvey = survey.type === "app" || survey.type === "website";
const widgetSetupCompleted =
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
useEffect(() => {
const newSurveyParam = searchParams?.get("success");
if (newSurveyParam && survey && environment) {
setConfetti(true);
toast.success(
isAppSurvey && !environment.widgetSetupCompleted
isAppSurvey && !widgetSetupCompleted
? "Almost there! Install widget to start receiving responses."
: "Congrats! Your survey is live.",
{
icon: isAppSurvey && !environment.widgetSetupCompleted ? "🤏" : "🎉",
icon: isAppSurvey && !widgetSetupCompleted ? "🤏" : "🎉",
duration: 5000,
position: "bottom-right",
}
@@ -45,7 +47,7 @@ export const SuccessMessage = ({ environment, survey, webAppUrl, user }: Summary
url.searchParams.delete("success");
window.history.replaceState({}, "", url.toString());
}
}, [environment, isAppSurvey, searchParams, survey]);
}, [environment, isAppSurvey, searchParams, survey, widgetSetupCompleted]);
return (
<>

View File

@@ -39,11 +39,14 @@ export const SummaryList = ({
totalResponseCount,
attributeClasses,
}: SummaryListProps) => {
const widgetSetupCompleted =
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
return (
<div className="mt-10 space-y-8">
{(survey.type === "app" || survey.type === "website") &&
responseCount === 0 &&
!environment.widgetSetupCompleted ? (
!widgetSetupCompleted ? (
<EmptyAppSurveys environment={environment} surveyType={survey.type} />
) : fetchingSummary ? (
<SkeletonLoader type="summary" />
@@ -53,6 +56,7 @@ export const SummaryList = ({
environment={environment}
noWidgetRequired={survey.type === "link"}
emptyMessage={totalResponseCount === 0 ? undefined : "No response matches your filter"}
widgetSetupCompleted={widgetSetupCompleted}
/>
) : (
summary.map((questionSummary) => {

View File

@@ -25,13 +25,15 @@ export const SurveyAnalysisCTA = ({
user: TUser;
}) => {
const [showShareSurveyModal, setShowShareSurveyModal] = useState(false);
const widgetSetupCompleted =
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
return (
<div className="hidden justify-end gap-x-1.5 sm:flex">
{survey.resultShareKey && (
<Badge text="Results are public" type="warning" size="normal" className="rounded-lg"></Badge>
)}
{(environment.widgetSetupCompleted || survey.type === "link") && survey.status !== "draft" ? (
{(widgetSetupCompleted || survey.type === "link") && survey.status !== "draft" ? (
<SurveyStatusDropdown environment={environment} survey={survey} />
) : null}
{survey.type === "link" && (

View File

@@ -60,7 +60,9 @@ export const SurveyStatusDropdown = ({
<SelectTrigger className="w-[170px] bg-white py-6 md:w-[200px]">
<SelectValue>
<div className="flex items-center">
{(survey.type === "link" || environment.widgetSetupCompleted) && (
{(survey.type === "link" ||
environment.appSetupCompleted ||
environment.websiteSetupCompleted) && (
<SurveyStatusIndicator status={survey.status} />
)}
<span className="ml-2 text-sm text-slate-700">

View File

@@ -76,10 +76,7 @@ const ConnectedState = ({ goToProduct }) => {
const NotConnectedState = ({ environment, webAppUrl, jsPackageVersion, goToOrganizationInvitePage }) => {
return (
<div className="mb-8 w-full max-w-xl space-y-8">
<OnboardingTitle
title="Connect your app or website"
subtitle="It takes just a few minutes to set it up."
/>
<OnboardingTitle title="Connect your website" subtitle="It takes just a few minutes to set it up." />
<div className="flex w-full items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-12 py-3 text-slate-700">
Waiting for your signal...
@@ -123,6 +120,8 @@ export const ConnectWithFormbricks = ({
useVisibilityChange(environment, setLocalEnvironment);
const widgetSetupCompleted = localEnvironment.websiteSetupCompleted;
useEffect(() => {
const fetchLatestEnvironmentOnFirstLoad = async () => {
const refetchedEnvironment = await fetchEnvironment(environment.id);
@@ -132,7 +131,7 @@ export const ConnectWithFormbricks = ({
fetchLatestEnvironmentOnFirstLoad();
}, [environment.id]);
return localEnvironment.widgetSetupCompleted ? (
return widgetSetupCompleted ? (
<ConnectedState
goToProduct={() => {
goToProduct(router);

View File

@@ -26,7 +26,7 @@ export const SetupInstructionsOnboarding = ({
const [activeTab, setActiveId] = useState(tabs[0].id);
const htmlSnippet = `<!-- START Formbricks Surveys -->
<script type="text/javascript">
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.5/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="${webAppUrl}/api/packages/website";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
</script>
<!-- END Formbricks Surveys -->`;

View File

@@ -5,9 +5,9 @@ import {
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { NextRequest, userAgent } from "next/server";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getActionClassByEnvironmentIdAndName, getActionClasses } from "@formbricks/lib/actionClass/service";
import { getAttributes } from "@formbricks/lib/attribute/service";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment, updateEnvironment } from "@formbricks/lib/environment/service";
import {
getMonthlyActiveOrganizationPeopleCount,
@@ -18,7 +18,8 @@ import { createPerson, getIsPersonMonthlyActive, getPersonByUserId } from "@form
import { sendPlanLimitsReachedEventToPosthogWeekly } from "@formbricks/lib/posthogServer";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { getSyncSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service";
import { createSurvey, getSyncSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service";
import { getExampleAppSurveyTemplate } from "@formbricks/lib/templates";
import { isVersionGreaterThanOrEqualTo } from "@formbricks/lib/utils/version";
import { TLegacySurvey } from "@formbricks/types/LegacySurvey";
import { TEnvironment } from "@formbricks/types/environment";
@@ -70,8 +71,14 @@ export const GET = async (
throw new Error("Environment does not exist");
}
if (!environment.widgetSetupCompleted) {
await updateEnvironment(environment.id, { widgetSetupCompleted: true });
if (!environment.appSetupCompleted) {
const exampleTrigger = await getActionClassByEnvironmentIdAndName(environmentId, "New Session");
if (!exampleTrigger) {
throw new Error("Example trigger not found");
}
const firstSurvey = getExampleAppSurveyTemplate(WEBAPP_URL, exampleTrigger);
await createSurvey(environmentId, firstSurvey);
await updateEnvironment(environment.id, { appSetupCompleted: true });
}
// check organization subscriptions

View File

@@ -12,7 +12,7 @@ import { sendPlanLimitsReachedEventToPosthogWeekly } from "@formbricks/lib/posth
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { createSurvey, getSurveys, transformToLegacySurvey } from "@formbricks/lib/survey/service";
import { getExampleSurveyTemplate } from "@formbricks/lib/templates";
import { getExampleWebsiteSurveyTemplate } from "@formbricks/lib/templates";
import { isVersionGreaterThanOrEqualTo } from "@formbricks/lib/utils/version";
import { TLegacySurvey } from "@formbricks/types/LegacySurvey";
import { TJsWebsiteLegacyStateSync, TJsWebsiteStateSync, ZJsWebsiteSyncInput } from "@formbricks/types/js";
@@ -79,14 +79,14 @@ export const GET = async (
}
}
if (!environment?.widgetSetupCompleted) {
if (!environment?.websiteSetupCompleted) {
const exampleTrigger = await getActionClassByEnvironmentIdAndName(environmentId, "New Session");
if (!exampleTrigger) {
throw new Error("Example trigger not found");
}
const firstSurvey = getExampleSurveyTemplate(WEBAPP_URL, exampleTrigger);
const firstSurvey = getExampleWebsiteSurveyTemplate(WEBAPP_URL, exampleTrigger);
await createSurvey(environmentId, firstSurvey);
await updateEnvironment(environment.id, { widgetSetupCompleted: true });
await updateEnvironment(environment.id, { websiteSetupCompleted: true });
}
const [surveys, actionClasses, product] = await Promise.all([

View File

@@ -23,7 +23,8 @@ export const GET = async () => {
name: true,
},
},
widgetSetupCompleted: true,
appSetupCompleted: true,
websiteSetupCompleted: true,
},
},
},

View File

@@ -10,19 +10,25 @@ const Page = () => {
return (
<div className="flex flex-col items-center">
<h2 className="mb-6 text-xl font-medium">Welcome to Formbricks!</h2>
<div className="space-y-4 text-sm text-slate-800">
<div className="mx-auto max-w-sm space-y-4 text-sm leading-6 text-slate-600">
<p>
Formbricks is a versatile open source platform with an Experience Management Suite built on top of
it.
Formbricks is an Experience Management Suite built of the{" "}
<b>fastest growing open source survey platform</b> worldwide.
</p>
<p>
Run targeted surveys on websites, in apps or anywhere online. Gather valuable insights to{" "}
<b>craft irresistible experiences</b> for customers, users and employees.
</p>
<p>
We&apos;re commited to highest degree of data privacy. Self-host to keep{" "}
<b>full control over your data.</b> Always.
</p>
<p>Survey customers, users or employees at any points with a perfectly timed and targeted survey.</p>
<p>Keep full control over your data</p>
</div>
<Button variant="darkCTA" href="/setup/signup" className="mt-6">
Get started
</Button>
<p className="pt-6 text-xs text-slate-500">Made with 🤍 in Kiel, Germany</p>
<p className="pt-6 text-xs text-slate-400">Made with 🤍 in Kiel, Germany</p>
</div>
);
};

View File

@@ -8,7 +8,7 @@ const SetupLayout = ({ children }: { children: React.ReactNode }) => {
<div className="flex h-full w-full items-center justify-center bg-slate-50">
<div
style={{ scrollbarGutter: "stable both-edges" }}
className="flex max-h-[90vh] w-[40rem] flex-col items-center space-y-4 overflow-auto rounded-lg border bg-white p-12 text-center shadow">
className="flex max-h-[90vh] w-[40rem] flex-col items-center space-y-4 overflow-auto rounded-lg border bg-white p-12 text-center shadow-md">
<div className="h-20 w-20 rounded-lg bg-slate-900 p-2">
<FormbricksLogo className="h-full w-full" />
</div>

View File

@@ -83,7 +83,7 @@ export const finishOnboarding = async (page: Page, deleteExampleSurvey: boolean
await page.waitForURL(/\/environments\/[^/]+\/surveys/);
if (deleteExampleSurvey) {
await page.click("#example-survey-survey-actions");
await page.click("#example-website-survey-survey-actions");
await page.getByRole("menuitem", { name: "Delete" }).click();
await page.getByRole("button", { name: "Delete" }).click();
await expect(page.getByText("Survey deleted successfully.")).toBeVisible();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -0,0 +1,91 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const main = async () => {
await prisma.$transaction(
async (tx) => {
const startTime = Date.now();
console.log("Starting data migration...");
// Retrieve all environments with widget setup completed
const environmentsWithWidgetSetupCompleted = await tx.environment.findMany({
where: {
widgetSetupCompleted: true,
},
select: {
id: true,
},
});
console.log(
`Found ${environmentsWithWidgetSetupCompleted.length} environments with widget setup completed.`
);
if (environmentsWithWidgetSetupCompleted.length > 0) {
const environmentIds = environmentsWithWidgetSetupCompleted.map((env) => env.id);
// Fetch survey counts in a single query
const surveyCounts = await tx.survey.groupBy({
by: ["environmentId", "type"],
where: {
environmentId: {
in: environmentIds,
},
displays: {
some: {},
},
type: {
in: ["app", "website"],
},
},
_count: {
_all: true,
},
});
// Create a map of environmentId to survey counts
const surveyCountMap = surveyCounts.reduce(
(acc, survey) => {
if (!acc[survey.environmentId]) {
acc[survey.environmentId] = { website: 0, app: 0, link: 0, web: 0 };
}
acc[survey.environmentId][survey.type] = survey._count._all;
return acc;
},
{} as Record<string, { website: number; app: number; link: number; web: number }>
);
// Update the appSetupCompleted and websiteSetupCompleted flags for each environment
const updatePromises = environmentsWithWidgetSetupCompleted.map((environment) => {
const counts = surveyCountMap[environment.id] || { website: 0, app: 0 };
return tx.environment.update({
where: { id: environment.id },
data: {
appSetupCompleted: counts.app > 0,
websiteSetupCompleted: counts.website > 0,
},
});
});
await Promise.all(updatePromises);
}
const endTime = Date.now();
console.log(`Data migration completed. Total time: ${(endTime - startTime) / 1000}s`);
},
{
timeout: 50000,
}
);
};
main()
.catch((e: Error) => {
console.error("Error during migration: ", e.message);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Environment" ADD COLUMN "appSetupCompleted" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "websiteSetupCompleted" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -36,6 +36,7 @@
"data-migration:pricing-v2": "dotenv -e ../../.env -- ts-node ./data-migrations/20240613070218_pricing_v2/data-migration.ts",
"data-migration:extended-noCodeActions": "ts-node ./data-migrations/20240524053239_extends_no_code_action_schema/data-migration.ts",
"data-migration:v2.1": "pnpm data-migration:extended-noCodeActions",
"data-migration:adds_app_and_website_status_indicator": "ts-node ./data-migrations/20240610055828_adds_app_and_website_status_indicators/data-migration.ts",
"data-migration:product-config": "ts-node ./data-migrations/20240612115151_adds_product_config/data-migration.ts"
},
"dependencies": {

View File

@@ -393,22 +393,24 @@ model Integration {
}
model Environment {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
type EnvironmentType
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String
widgetSetupCompleted Boolean @default(false)
surveys Survey[]
people Person[]
actionClasses ActionClass[]
attributeClasses AttributeClass[]
apiKeys ApiKey[]
webhooks Webhook[]
tags Tag[]
segments Segment[]
integration Integration[]
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
type EnvironmentType
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String
widgetSetupCompleted Boolean @default(false)
appSetupCompleted Boolean @default(false)
websiteSetupCompleted Boolean @default(false)
surveys Survey[]
people Person[]
actionClasses ActionClass[]
attributeClasses AttributeClass[]
apiKeys ApiKey[]
webhooks Webhook[]
tags Tag[]
segments Segment[]
integration Integration[]
@@index([productId])
}

View File

@@ -161,7 +161,8 @@ export const createEnvironment = async (
data: {
type: environmentInput.type || "development",
product: { connect: { id: productId } },
widgetSetupCompleted: environmentInput.widgetSetupCompleted || false,
appSetupCompleted: environmentInput.appSetupCompleted || false,
websiteSetupCompleted: environmentInput.websiteSetupCompleted || false,
actionClasses: {
create: [
{

View File

@@ -2503,23 +2503,23 @@ export const customSurvey = {
},
};
export const getExampleSurveyTemplate = (webAppUrl: string, trigger: TActionClass): TSurveyInput => ({
export const getExampleWebsiteSurveyTemplate = (webAppUrl: string, trigger: TActionClass): TSurveyInput => ({
...customSurvey.preset,
questions: customSurvey.preset.questions.map(
(question) =>
({
...question,
type: TSurveyQuestionTypeEnum.CTA,
headline: { default: "You did it 🎉" },
headline: { default: "Website successfully connected 🎉" },
html: {
default: "You're all set up. Create your own survey to gather exactly the feedback you need :)",
default: "You're all set up. Create your own survey for website visitors 👇",
},
buttonLabel: { default: "Create survey" },
buttonLabel: { default: "Let's do it!" },
buttonExternal: true,
imageUrl: `${webAppUrl}/onboarding/meme.png`,
}) as TSurveyCTAQuestion
),
name: "Example survey",
name: "Example website survey",
type: "website" as TSurveyType,
autoComplete: 2,
triggers: [{ actionClass: trigger }],
@@ -2527,3 +2527,28 @@ export const getExampleSurveyTemplate = (webAppUrl: string, trigger: TActionClas
displayOption: "respondMultiple" as TSurveyDisplayOption,
recontactDays: 0,
});
export const getExampleAppSurveyTemplate = (webAppUrl: string, trigger: TActionClass): TSurveyInput => ({
...customSurvey.preset,
questions: customSurvey.preset.questions.map(
(question) =>
({
...question,
type: TSurveyQuestionTypeEnum.CTA,
headline: { default: "App successfully connected 🥳" },
html: {
default: "You're all set up. Create your own survey for your app users 👇",
},
buttonLabel: { default: "Let's do it!" },
buttonExternal: true,
imageUrl: `${webAppUrl}/onboarding/meme.png`,
}) as TSurveyCTAQuestion
),
name: "Example app survey",
type: "app" as TSurveyType,
autoComplete: 2,
triggers: [{ actionClass: trigger }],
status: "inProgress" as TSurveyStatus,
displayOption: "respondMultiple" as TSurveyDisplayOption,
recontactDays: 0,
});

View File

@@ -6,7 +6,8 @@ export const ZEnvironment = z.object({
updatedAt: z.date(),
type: z.enum(["development", "production"]),
productId: z.string(),
widgetSetupCompleted: z.boolean(),
appSetupCompleted: z.boolean(),
websiteSetupCompleted: z.boolean(),
});
export type TEnvironment = z.infer<typeof ZEnvironment>;
@@ -20,14 +21,16 @@ export type TEnvironmentId = z.infer<typeof ZEnvironmentId>;
export const ZEnvironmentUpdateInput = z.object({
type: z.enum(["development", "production"]),
productId: z.string(),
widgetSetupCompleted: z.boolean(),
appSetupCompleted: z.boolean(),
websiteSetupCompleted: z.boolean(),
});
export const ZId = z.string().cuid2();
export const ZEnvironmentCreateInput = z.object({
type: z.enum(["development", "production"]).optional(),
widgetSetupCompleted: z.boolean().optional(),
appSetupCompleted: z.boolean().optional(),
websiteSetupCompleted: z.boolean().optional(),
});
export type TEnvironmentCreateInput = z.infer<typeof ZEnvironmentCreateInput>;

View File

@@ -9,6 +9,7 @@ type EmptySpaceFillerProps = {
environment: TEnvironment;
noWidgetRequired?: boolean;
emptyMessage?: string;
widgetSetupCompleted?: boolean;
};
export const EmptySpaceFiller = ({
@@ -16,6 +17,7 @@ export const EmptySpaceFiller = ({
environment,
noWidgetRequired,
emptyMessage,
widgetSetupCompleted = false,
}: EmptySpaceFillerProps) => {
if (type === "table") {
return (
@@ -23,17 +25,16 @@ export const EmptySpaceFiller = ({
<div className="w-full space-y-3">
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
<div className="flex h-16 w-full flex-col items-center justify-center rounded-lg bg-slate-50 text-slate-700 transition-all duration-300 ease-in-out hover:bg-slate-100 ">
{!environment.widgetSetupCompleted && !noWidgetRequired && (
{!widgetSetupCompleted && !noWidgetRequired && (
<Link
className="flex w-full items-center justify-center"
href={`/environments/${environment.id}/product/setup`}>
href={`/environments/${environment.id}/product/app-connection`}>
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
Install Formbricks Widget. <strong>Go to Setup Checklist 👉</strong>
</span>
</Link>
)}
{((environment.widgetSetupCompleted || noWidgetRequired) && emptyMessage) ||
"Waiting for a response 🧘‍♂️"}
{((widgetSetupCompleted || noWidgetRequired) && emptyMessage) || "Waiting for a response 🧘‍♂️"}
</div>
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
@@ -52,16 +53,16 @@ export const EmptySpaceFiller = ({
<div className="space-y-4">
<div className="h-12 w-full rounded-full bg-slate-100"></div>
<div className=" flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
{!environment.widgetSetupCompleted && !noWidgetRequired && (
{!widgetSetupCompleted && !noWidgetRequired && (
<Link
className="flex h-full w-full items-center justify-center"
href={`/environments/${environment.id}/product/setup`}>
href={`/environments/${environment.id}/product/app-connection`}>
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
Install Formbricks Widget. <strong>Go to Setup Checklist 👉</strong>
</span>
</Link>
)}
{(environment.widgetSetupCompleted || noWidgetRequired) && (
{(widgetSetupCompleted || noWidgetRequired) && (
<span className="bg-light-background-primary-500 text-center">
{emptyMessage ?? "Waiting for a response"} 🧘
</span>
@@ -83,16 +84,16 @@ export const EmptySpaceFiller = ({
<div className="space-y-4">
<div className="h-12 w-full rounded-full bg-slate-100"></div>
<div className=" flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
{!environment.widgetSetupCompleted && !noWidgetRequired && (
{!widgetSetupCompleted && !noWidgetRequired && (
<Link
className="flex h-full w-full items-center justify-center"
href={`/environments/${environment.id}/product/setup`}>
href={`/environments/${environment.id}/product/app-connection`}>
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
Install Formbricks Widget. <strong>Go to Setup Checklist 👉</strong>
</span>
</Link>
)}
{(environment.widgetSetupCompleted || noWidgetRequired) && (
{(widgetSetupCompleted || noWidgetRequired) && (
<span className="text-center">Tag a submission to find your list of tags here.</span>
)}
</div>
@@ -130,17 +131,17 @@ export const EmptySpaceFiller = ({
</div>
<div className="space-y-4">
<div className="h-12 w-full rounded-full bg-slate-100"></div>
<div className=" flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
{!environment.widgetSetupCompleted && !noWidgetRequired && (
<div className="flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
{!widgetSetupCompleted && !noWidgetRequired && (
<Link
className="flex h-full w-full items-center justify-center"
href={`/environments/${environment.id}/product/setup`}>
href={`/environments/${environment.id}/product/app-connection`}>
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
Install Formbricks Widget. <strong>Go to Setup Checklist 👉</strong>
</span>
</Link>
)}
{(environment.widgetSetupCompleted || noWidgetRequired) && (
{(widgetSetupCompleted || noWidgetRequired) && (
<span className="text-center">Waiting for a response 🧘</span>
)}
</div>

View File

@@ -69,7 +69,10 @@ export const PreviewSurvey = ({
}: PreviewSurveyProps) => {
const [isModalOpen, setIsModalOpen] = useState(true);
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false);
const [appSetupCompleted, setAppSetupCompleted] = useState(false);
const [websiteSetupCompleted, setWebsiteSetupCompleted] = useState(false);
const [previewMode, setPreviewMode] = useState("desktop");
const [previewPosition, setPreviewPosition] = useState("relative");
const ContentRef = useRef<HTMLDivElement | null>(null);
@@ -118,6 +121,8 @@ export const PreviewSurvey = ({
const darkOverlay = surveyDarkOverlay ?? product.darkOverlay;
const clickOutsideClose = surveyClickOutsideClose ?? product.clickOutsideClose;
const widgetSetupCompleted = appSetupCompleted || websiteSetupCompleted;
const styling: TSurveyStyling | TProductStyling = useMemo(() => {
// allow style overwrite is disabled from the product
if (!product.styling.allowStyleOverwrite) {
@@ -184,10 +189,9 @@ export const PreviewSurvey = ({
};
useEffect(() => {
if (environment && environment.widgetSetupCompleted) {
setWidgetSetupCompleted(true);
} else {
setWidgetSetupCompleted(false);
if (environment) {
setAppSetupCompleted(environment.appSetupCompleted);
setWebsiteSetupCompleted(environment.websiteSetupCompleted);
}
}, [environment]);

View File

@@ -149,9 +149,9 @@ export const SingleResponseCardHeader = ({
{pageType === "people" && (
<div className="flex items-center justify-center space-x-2 rounded-full bg-slate-100 p-1 px-2 text-sm text-slate-600">
{(survey.type === "link" || environment.widgetSetupCompleted) && (
<SurveyStatusIndicator status={survey.status} />
)}
{(survey.type === "link" ||
environment.appSetupCompleted ||
environment.websiteSetupCompleted) && <SurveyStatusIndicator status={survey.status} />}
<Link
className="hover:underline"
href={`/environments/${environmentId}/surveys/${survey.id}/summary`}>

View File

@@ -65,7 +65,11 @@ export const TemplateList = ({
const addSurvey = async (activeTemplate: TTemplate) => {
setLoading(true);
const surveyType = environment?.widgetSetupCompleted ? "app" : "link";
const surveyType = environment?.appSetupCompleted
? "app"
: environment?.websiteSetupCompleted
? "website"
: "link";
const augmentedTemplate: TSurveyInput = {
...activeTemplate.preset,
type: surveyType,

1777
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff