fix: merges the app and website js sdk into @formbricks/js (#3314)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
@@ -1,22 +0,0 @@
|
||||
interface SurveySwitchProps {
|
||||
value: "website" | "app";
|
||||
formbricks: any;
|
||||
}
|
||||
|
||||
export const SurveySwitch = ({ value, formbricks }: SurveySwitchProps) => {
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
onChange={(v) => {
|
||||
formbricks.logout();
|
||||
window.location.href = `/${v.target.value}`;
|
||||
}}>
|
||||
<option value="website" className="h-10 px-4 hover:bg-slate-100">
|
||||
Website Surveys
|
||||
</option>
|
||||
<option value="app" className="hover:bg-slate-10 h-10 px-4">
|
||||
App Surveys
|
||||
</option>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,5 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/",
|
||||
destination: "/app",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import { SurveySwitch } from "../../components/SurveySwitch";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
import formbricks from "@formbricks/js";
|
||||
import fbsetup from "../public/fb-setup.png";
|
||||
|
||||
declare const window: any;
|
||||
|
||||
@@ -63,7 +62,6 @@ const AppPage = ({}) => {
|
||||
<div className="min-h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div className="flex flex-col items-center gap-2 sm:flex-row">
|
||||
<SurveySwitch value="app" formbricks={formbricks} />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
Formbricks In-product Survey Demo App
|
||||
@@ -1,143 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import formbricks from "@formbricks/js/website";
|
||||
import { SurveySwitch } from "../../components/SurveySwitch";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
declare const window: any;
|
||||
|
||||
const AppPage = ({}) => {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (darkMode) {
|
||||
document.body.classList.add("dark");
|
||||
} else {
|
||||
document.body.classList.remove("dark");
|
||||
}
|
||||
}, [darkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// enable Formbricks debug mode by adding formbricksDebug=true GET parameter
|
||||
const addFormbricksDebugParam = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has("formbricksDebug")) {
|
||||
urlParams.set("formbricksDebug", "true");
|
||||
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
}
|
||||
};
|
||||
|
||||
addFormbricksDebugParam();
|
||||
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const defaultAttributes = {
|
||||
language: "en",
|
||||
};
|
||||
|
||||
formbricks.init({
|
||||
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
|
||||
apiHost: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
|
||||
attributes: defaultAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
// Connect next.js router to Formbricks
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
const handleRouteChange = formbricks?.registerRouteChange;
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div className="flex flex-col items-center gap-2 sm:flex-row">
|
||||
<SurveySwitch value="website" formbricks={formbricks} />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
Formbricks Website Survey Demo App
|
||||
</h1>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
This app helps you test your app surveys. You can create and test user actions, create and
|
||||
update user attributes, etc.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="mt-2 rounded-lg bg-slate-200 px-6 py-1 dark:bg-slate-700 dark:text-slate-100"
|
||||
onClick={() => setDarkMode(!darkMode)}>
|
||||
{darkMode ? "Toggle Light Mode" : "Toggle Dark Mode"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="my-4 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<div className="rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">1. Setup .env</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
Copy the environment ID of your Formbricks app to the env variable in /apps/demo/.env
|
||||
</p>
|
||||
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded" priority />
|
||||
|
||||
<div className="mt-4 flex-col items-start text-sm text-slate-700 sm:flex sm:items-center sm:text-base dark:text-slate-300">
|
||||
<p className="mb-1 sm:mb-0 sm:mr-2">You're connected with env:</p>
|
||||
<div className="flex items-center">
|
||||
<strong className="w-32 truncate sm:w-auto">
|
||||
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
|
||||
</strong>
|
||||
<span className="relative ml-2 flex h-3 w-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span>
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">2. Widget Logs</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
Look at the logs to understand how the widget works.{" "}
|
||||
<strong className="dark:text-white">Open your browser console</strong> to see the logs.
|
||||
</p>
|
||||
{/* <div className="max-h-[40vh] overflow-y-auto py-4">
|
||||
<LogsContainer />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:grid md:grid-cols-3">
|
||||
<div className="col-span-3 self-start rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Reset person / pull data from Formbricks app
|
||||
</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
On formbricks.reset() the local state will <strong>be deleted</strong> and formbricks gets{" "}
|
||||
<strong>reinitialized</strong>.
|
||||
</p>
|
||||
<button
|
||||
className="my-4 rounded-lg bg-slate-500 px-6 py-3 text-white hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
|
||||
onClick={() => {
|
||||
formbricks.reset();
|
||||
}}>
|
||||
Reset
|
||||
</button>
|
||||
|
||||
<p className="text-xs text-slate-700 dark:text-slate-300">
|
||||
If you made a change in Formbricks app and it does not seem to work, hit 'Reset' and
|
||||
try again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppPage;
|
||||
@@ -9,8 +9,6 @@ export const metadata = {
|
||||
"Dive deep into how actions in Formbricks help products and organizations to engage users at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine user targeting and generate richer, more detailed user insights.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Actions
|
||||
|
||||
Actions are predefined events within your app that prompt Formbricks to display a survey when triggered. These are detected by the Formbricks widget, which then presents the appropriate survey based on your predefined settings.
|
||||
|
||||
@@ -4,8 +4,6 @@ export const metadata = {
|
||||
"Advanced Targeting allows you to show surveys to just the right group of people. You can target surveys based on user attributes, metadata, and other segments. This helps you get more relevant feedback and make data-driven decisions.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Advanced Targeting
|
||||
|
||||
Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, device type, and more instead of spraying and praying. This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code.
|
||||
|
||||
@@ -42,8 +42,8 @@ All you need to do is copy a `<script>` tag to your HTML head, and that’s abou
|
||||
!function(){
|
||||
var apiHost = "https://app.formbricks.com";
|
||||
var environmentId = "<your-environment-id>";
|
||||
var userId = "<your-user-id>";
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/app";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
|
||||
var userId = "<your-user-id>"; //optional
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
```
|
||||
@@ -58,9 +58,6 @@ All you need to do is copy a `<script>` tag to your HTML head, and that’s abou
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example HTML project](https://github.com/formbricks/examples/tree/main/html) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
@@ -91,13 +88,13 @@ Now, update your App.js/ts file to initialise Formbricks.
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,9 +116,6 @@ export default App;
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example ReactJs project](https://github.com/formbricks/examples/tree/main/reactjs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
@@ -164,7 +158,7 @@ yarn add @formbricks/js zod
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
export default function FormbricksProvider() {
|
||||
const pathname = usePathname();
|
||||
@@ -174,7 +168,7 @@ export default function FormbricksProvider() {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -217,13 +211,13 @@ Refer to our [Example NextJS App Directory project](https://github.com/formbrick
|
||||
// other import
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
@@ -256,9 +250,6 @@ Refer to our [Example NextJS Pages Directory project](https://github.com/formbri
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
First we initialize the Formbricks SDK, ensuring that it only runs on the client side.
|
||||
@@ -292,13 +283,13 @@ yarn add @formbricks/js
|
||||
<CodeGroup title="src/formbricks.js">
|
||||
|
||||
```js
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
userId: "<user-id>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
}
|
||||
|
||||
@@ -339,9 +330,6 @@ router.afterEach((to, from) => {
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example VueJs project](https://github.com/formbricks/examples/tree/main/vuejs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
@@ -399,9 +387,6 @@ export default function App() {
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
<Property name="userId" type="string">
|
||||
User ID of the user who has active session.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
---
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 102 KiB |
BIN
apps/docs/app/app-surveys/quickstart/images/I3_1.webp
Normal file
|
After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 149 KiB |
@@ -3,6 +3,7 @@ import { MdxImage } from "@/components/MdxImage";
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
import I3 from "./images/I3.webp";
|
||||
import I3_1 from "./images/I3_1.webp";
|
||||
import I4 from "./images/I4.webp";
|
||||
import I5 from "./images/I5.webp";
|
||||
import I6 from "./images/I6.webp";
|
||||
@@ -15,44 +16,41 @@ export const metadata = {
|
||||
"Formbricks is the easiest way to create and manage app surveys. This quickstart guide will show you how to create your first app survey in under 5 minutes.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Quickstart
|
||||
|
||||
App surveys have 6-10x better conversion rates than emailed surveys. This tutorial explains how to run a survey in both your web app and mobile app (React Native) in just 10 to 15 minutes. Let’s 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).
|
||||
</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:
|
||||
|
||||
<Note>
|
||||
Website & App Surveys have the same integration process. The difference will come when we setup our survey.
|
||||
</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, choose the "Formbricks Surveys" option:
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="Choose website survey from survey type"
|
||||
alt="Choose Formbricks Surveys"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. **Connect your App/Website**: Once you get through a couple of onboarding steps, you’ll be asked to connect your app or website. This is where you’ll find the code snippet for both HTML as well as the npm package which you need to embed in your app:
|
||||
2. **Choose your Product Channel**: On this step, you have to choose between the various channels that you want your product to be created in, you can create both app and link surveys from all the channels, but for the onboarding, please choose between the app surveys or the public website options, upon doing this, you'll be prompted to connect your app / website to formbricks.
|
||||
|
||||
<MdxImage
|
||||
src={I2}
|
||||
alt="Choose between app and website surveys product channels"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Connect your App/Website**: Once you get through a couple of onboarding steps, you’ll be asked to connect your app or website. This is where you’ll find the code snippet for both HTML as well as the npm package which you need to embed in your app:
|
||||
|
||||
<MdxImage
|
||||
src={I3}
|
||||
alt="Code snippet for connecting app with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Paste the code snippet in your app and reload the page. You should now see the Formbricks widget in the lower right corner of your app! The integration is now complete.
|
||||
Paste the code snippet in your app and reload the page. You should now see a message being displayed that your app or website is now connected with formbricks.
|
||||
|
||||
<MdxImage
|
||||
src={I3}
|
||||
src={I3_1}
|
||||
alt="App connected with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
@@ -67,7 +65,7 @@ Onboarding is complete! Now let’s create our first survey as you should see te
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Create your first survey**: To be able to see a survey in your app, you need to create one. We’ll choose one of the templates and head over to the survey settings:
|
||||
4. **Create your first survey**: To be able to see a survey in your app, you need to create one. We’ll choose one of the templates and head over to the survey settings:
|
||||
|
||||
Pick the Survey Type as **App Survey**.
|
||||
|
||||
@@ -78,7 +76,7 @@ Pick the Survey Type as **App Survey**.
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
4. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
|
||||
5. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new user session:
|
||||
|
||||
<MdxImage
|
||||
src={I6}
|
||||
@@ -87,7 +85,7 @@ Pick the Survey Type as **App Survey**.
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once to each user to prevent survey fatigue:
|
||||
6. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once to each user to prevent survey fatigue:
|
||||
|
||||
<Note>
|
||||
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
|
||||
@@ -100,7 +98,7 @@ Pick the Survey Type as **App Survey**.
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. **Publish your survey**: Now hit **Publish** and you’ll be forwarded to the Summary Page. This is where you’ll find the responses to this survey.
|
||||
7. **Publish your survey**: Now hit **Publish** and you’ll be forwarded to the Summary Page. This is where you’ll find the responses to this survey.
|
||||
|
||||
<MdxImage
|
||||
src={I8}
|
||||
@@ -111,7 +109,7 @@ Pick the Survey Type as **App Survey**.
|
||||
|
||||
---
|
||||
|
||||
- We offer framework guides for various frontend tech, head over to the the [App Survey Framework Guides](/app-survey/framework-guides) to get started with your app survey.
|
||||
- Head over to our App Survey SDK documentation to get started with the [App Survey JS SDK](/developer-docs/app-survey-sdk).
|
||||
- We offer framework guides for various frontend tech, head over to the the [App Survey Framework Guides](/app-surveys/framework-guides) to get started with your app survey.
|
||||
- Head over to our JS SDK documentation to get started with the [JS SDK](/developer-docs/js-sdk).
|
||||
|
||||
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!
|
||||
|
||||
@@ -11,8 +11,6 @@ export const metadata = {
|
||||
"Explore how to configure Recontact options in Formbricks to control the frequency of survey exposure to users, ensuring effective feedback collection without compromising user experience.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# Recontact Options
|
||||
|
||||
Recontact options in Formbricks enable you to manage how often and under what conditions a survey is shown to a user. This feature is crucial for balancing effective feedback collection with a positive user experience by preventing survey fatigue.
|
||||
|
||||
@@ -4,19 +4,12 @@ export const metadata = {
|
||||
"Dive into the importance of user identification in surveys. Boost your survey response rates and target the right users with Formbricks.",
|
||||
};
|
||||
|
||||
#### App Surveys
|
||||
|
||||
# User Identification
|
||||
|
||||
User Identification helps you to not only segment your users but also to see more information about the user who responded to a survey. This helps you to target surveys to specific user segments and see more information about the user who responded to a survey.
|
||||
|
||||
### Understanding Identified vs Unidentified Users
|
||||
|
||||
<Note>
|
||||
Formbricks App Surveys can **only be used with identified users**. If you are looking to run surveys on your
|
||||
public-facing website, head over to the [Website Surveys](/website-surveys/quickstart) section.
|
||||
</Note>
|
||||
|
||||
In Formbricks, understanding the distinction between identified and unidentified users is crucial for effective survey segmentation and targeted feedback collection.
|
||||
|
||||
| Feature | Unidentified Users | Identified Users |
|
||||
|
||||
|
Before Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@@ -5,18 +5,18 @@ import I2 from "./images/2-micro-survey-pop-up-in-app.webp";
|
||||
import I3 from "./images/3-survey-logs-in-app-survey-popup.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks App Survey SDK",
|
||||
title: "Formbricks JS SDK",
|
||||
description:
|
||||
"Integrate Formbricks App Surveys into your web apps with the Formbricks JS SDK for App Surveys. Learn how to initialize Formbricks, set attributes, track actions, and troubleshoot common issues.",
|
||||
"Integrate Formbricks App Surveys into your web apps and websites with the Formbricks JS SDK. Learn how to initialize Formbricks, set attributes, track actions, and troubleshoot common issues.",
|
||||
};
|
||||
|
||||
#### Developer Docs
|
||||
|
||||
# SDK: Run Surveys Inside Your Web Apps
|
||||
# SDK: Run Surveys Inside Your Web Apps and Websites
|
||||
|
||||
### 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. It’s available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
The Formbricks JS SDK is a versatile solution for integrating surveys into both web apps and websites. It adapts based on the presence of a `userId`. If a `userId` is provided, the SDK handles authenticated surveys for logged-in users in web apps. If no `userId` is provided, the SDK can still seamlessly run surveys on public-facing websites. The SDK is available on npm [here](https://www.npmjs.com/package/@formbricks/js/).
|
||||
|
||||
### Install
|
||||
|
||||
@@ -42,7 +42,7 @@ pnpm add @formbricks/js
|
||||
|
||||
### Initialize Formbricks
|
||||
|
||||
Initialize the Formbricks JS Client for app surveys where you pass the userId (creates a user if not existing in Formbricks) to attribute & target the user based on their actions.
|
||||
Initialize the Formbricks JS Client for surveys. When used in a web app, pass a `userId` to create and target a specific user. When using it on a website without authentication, simply omit the `userId`.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Initialize Formbricks">
|
||||
@@ -53,7 +53,7 @@ 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>", // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -73,7 +73,7 @@ if (window !== undefined) {
|
||||
formbricks.init({
|
||||
environmentId: "<your-environment-id>",
|
||||
apiHost: "<your-api-host>",
|
||||
userId: "<user-id>",
|
||||
userId: "<user-id>", //optional
|
||||
});
|
||||
} else {
|
||||
console.error("Window object not accessible to init Formbricks");
|
||||
@@ -87,7 +87,7 @@ if (window !== undefined) {
|
||||
|
||||
### Set Attribute
|
||||
|
||||
You can set custom attributes for the identified user. This can be helpful for segmenting users based on specific characteristics or properties. To learn how to set custom user attributes, please check out our [User Attributes Guide](/app-surveys/user-identification).
|
||||
Set custom attributes for the identified user to help segment them based on specific characteristics. This method only works when a `userId` is provided during initialization, allowing for targeted surveys in web apps. To learn how to set custom user attributes, refer to our [User Attributes Guide](/app-surveys/user-identification).
|
||||
|
||||
<Col>
|
||||
<CodeGroup>
|
||||
@@ -14,13 +14,13 @@ Welcome to the Developer Docs section, your comprehensive resource for integrati
|
||||
|
||||
The Formbricks React Native SDK for App Surveys is designed for React Native applications, enabling seamless integration of surveys within your mobile apps. Dive into the documentation to learn how to leverage the SDK for app surveys and engage with your users effectively.
|
||||
|
||||
### [SDK: Web Apps](/developer-docs/app-survey-sdk)
|
||||
### [SDK: Formbricks JS](/developer-docs/js-sdk)
|
||||
|
||||
The Formbricks JS SDK tailored for App Surveys is designed for applications where users are logged in, allowing for targeted and identified interactions within Formbricks. This SDK is particularly useful for advanced user tracking, enabling deeper insights into user behavior. Learn how to seamlessly integrate Formbricks into your applications and harness valuable insights from your logged-in users.
|
||||
The Formbricks JS SDK is a versatile solution for both web apps and public websites. It adapts based on how you provide user information.
|
||||
|
||||
### [SDK: Public Websites](/developer-docs/website-survey-sdk)
|
||||
If a `userId` is provided, the SDK tailors surveys for logged-in users, enabling targeted and identified interactions. This allows for advanced user tracking, providing deeper insights into user behavior within your application.
|
||||
|
||||
The Formbricks JS SDK for Website Surveys is ideal for public-facing websites without user authentication. It's recommended for pages with high traffic and no authentication walls, facilitating quick and efficient survey collection. Dive into the documentation to discover how to deploy surveys on your website and effectively engage with your audience.
|
||||
Alternatively, when no `userId` is supplied, the SDK seamlessly handles surveys for public-facing websites, making it ideal for high-traffic pages without authentication. This enables efficient survey collection without requiring user identification.
|
||||
|
||||
### [SDK: Formbricks API](/developer-docs/api-sdk)
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ export const metadata = {
|
||||
|
||||
Hidden fields are a powerful feature in Formbricks that allows you to add data to a submission without asking the user to type it in. This feature is especially useful when you already have information about a user that you want to use in the analysis of the survey results (e.g. `payment plan` or `email`)
|
||||
|
||||
<Note>Hidden fields are now available in the Formbricks in-app and website surveys as well</Note>
|
||||
|
||||
## How to Add Hidden Fields
|
||||
|
||||
### Enable them in the Survey Builder
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -1,132 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import StepOne from "./images/StepOne.webp";
|
||||
import StepTwo from "./images/StepTwo.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Using Actions in Formbricks | Fine-tuning Session Moments",
|
||||
description:
|
||||
"Dive deep into how actions in Formbricks help products and organizations to engage active sessions at precise moments in their journey. Discover the power of actions, from coding to no-code setups, to refine public facing websites' targeting and generate richer, more detailed insights.",
|
||||
};
|
||||
|
||||
#### Website Surveys
|
||||
|
||||
# Actions
|
||||
|
||||
Actions are triggers based on interactions with your website, allowing you to capture feedback precisely when it's most relevant.
|
||||
|
||||
<Note>
|
||||
These actions operate **independently** as website surveys do not involve user identification. If you have
|
||||
user identification enabled (you’ve initialized Formbricks with a userId), please refer to the App Surveys
|
||||
Action Guide.
|
||||
</Note>
|
||||
|
||||
## **How Do Actions Work?**
|
||||
|
||||
Actions are predefined events within your website that prompt Formbricks to display a survey when triggered. These are detected by the Formbricks widget, which then presents the appropriate survey based on your predefined settings.
|
||||
|
||||
## **Why Are Actions Useful?**
|
||||
|
||||
Actions enable surveys to be displayed based on specific site interactions or behaviours, independent of user identity. This approach is perfect for:
|
||||
|
||||
- **Public-Facing Websites**: Engage visitors on general content pages where user authentication is not present.
|
||||
- **Anonymous User Interactions**: Capture feedback without needing to identify or track individual users, maintaining user privacy and simplicity in data collection.
|
||||
|
||||
Using actions to trigger surveys enhances the relevance of survey content to the visitor's current interaction, potentially increasing engagement and the accuracy of feedback.
|
||||
|
||||
## **Setting Up No-Code Actions**
|
||||
|
||||
Formbricks provides an intuitive No-Code interface for configuring actions, enabling surveys to be triggered by various interactions on your website. To add a No-Code Action:
|
||||
|
||||
1. Visit the Formbricks Dashboard & switch to the Actions tab:
|
||||
|
||||
<MdxImage
|
||||
src={StepOne}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Now click on “Add Action”
|
||||
|
||||
<MdxImage
|
||||
src={StepTwo}
|
||||
alt="Choose a link survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Here are four types of No-Code actions you can set up:
|
||||
|
||||
### **1. Click Action**
|
||||
|
||||
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
|
||||
|
||||
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
|
||||
|
||||
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
|
||||
|
||||
### **2. Page view Action**
|
||||
|
||||
This action is triggered when a user visits a page within your application.
|
||||
|
||||
### **3. Exit Intent Action**
|
||||
|
||||
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
|
||||
|
||||
### **4. 50% Scroll Action**
|
||||
|
||||
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
|
||||
|
||||
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
|
||||
|
||||
<Note>
|
||||
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
|
||||
|
||||
### **URL Match Conditions**
|
||||
|
||||
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
|
||||
- **contains**: Activates when the URL contains the specified substring.
|
||||
- **startsWith**: Fires when the URL starts with the specified string.
|
||||
- **endsWith**: Executes when the URL ends with the specified string.
|
||||
- **notMatch**: Triggers when the URL does not match the specified condition.
|
||||
- **notContains**: Activates when the URL does not contain the specified substring.
|
||||
|
||||
</Note>
|
||||
|
||||
## **Setting Up Code Actions**
|
||||
|
||||
For enhanced customization, actions can also be implemented directly through code:
|
||||
|
||||
1. **Configure the Action**: First, add the action via the Formbricks web interface to link it with the desired survey.
|
||||
2. **Track an Action**: Use the **`formbricks.track()`** function to activate the survey from your website's frontend.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Example of tracking an action">
|
||||
|
||||
```jsx
|
||||
formbricks.track("Action Name");
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
**Example - Tracking a Button Click**:
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Example: Button Click Action">
|
||||
|
||||
```jsx
|
||||
const handleClick = () => {
|
||||
formbricks.track("Button Clicked");
|
||||
};
|
||||
return <button onClick={handleClick}>Click Me</button>;
|
||||
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Using generic actions to trigger surveys on your website provides a straightforward and effective method to engage visitors and gather feedback. This approach is especially valuable for public-facing websites and pages where user authentication barriers are absent, allowing for broad interaction and feedback collection without the complexities of user identification.
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import logoHtml from "@/images/frameworks/html5.svg";
|
||||
import logoNextjs from "@/images/frameworks/nextjs.svg";
|
||||
import logoReactJs from "@/images/frameworks/reactjs.svg";
|
||||
import logoVueJs from "@/images/frameworks/vuejs.svg";
|
||||
import Image from "next/image";
|
||||
|
||||
const libraries = [
|
||||
{
|
||||
href: "#html",
|
||||
name: "HTML",
|
||||
description: "All you need to do is add 3 lines of code to your HTML script and thats it, you're done!",
|
||||
logo: logoHtml,
|
||||
},
|
||||
{
|
||||
href: "#react-js",
|
||||
name: "React.js",
|
||||
description: "Load the our Js library with your environment ID and you're ready to go!",
|
||||
logo: logoReactJs,
|
||||
},
|
||||
{
|
||||
href: "#next-js",
|
||||
name: "Next.js",
|
||||
description:
|
||||
"Natively add us to your NextJs project with support for both App as well as Pages project structure!",
|
||||
logo: logoNextjs,
|
||||
},
|
||||
{
|
||||
href: "#vue-js",
|
||||
name: "Vue.js",
|
||||
description: "Simply add us to your router change and sit back!",
|
||||
logo: logoVueJs,
|
||||
},
|
||||
];
|
||||
|
||||
export const Libraries = () => {
|
||||
return (
|
||||
<div className="my-16 xl:max-w-none">
|
||||
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-slate-900/5 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3 dark:border-white/5">
|
||||
{libraries.map((library) => (
|
||||
<a
|
||||
key={library.name}
|
||||
href={library.href}
|
||||
className="flex flex-row-reverse gap-6 rounded-2xl p-6 transition-all duration-100 ease-in-out hover:cursor-pointer hover:bg-slate-100/50 dark:hover:bg-slate-800/50">
|
||||
<div className="flex-auto">
|
||||
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{library.name}</h3>
|
||||
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">{library.description}</p>
|
||||
<p className="mt-4">
|
||||
<Button href={library.href} variant="text" arrow="right">
|
||||
Read more
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
<Image src={library.logo} alt="" className="h-12 w-12" unoptimized />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,404 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import { Libraries } from "./components/Libraries";
|
||||
|
||||
import ReactApp from "./images/react-in-app-survey-app-popup-form.webp";
|
||||
import WidgetConnected from "./images/widget-connected.webp";
|
||||
import WidgetNotConnected from "./images/widget-not-connected.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Integrate Formbricks Website Surveys: Comprehensive Framework Guide & Integration Tutorial",
|
||||
description:
|
||||
"Master the integration of Formbricks Website Surveys into your application with our detailed guides. From HTML to ReactJS, NextJS, and VueJS, get step-by-step instructions and ensure seamless setup.",
|
||||
};
|
||||
|
||||
#### Website Surveys
|
||||
|
||||
# Framework Guides
|
||||
|
||||
One can integrate Formbricks Website Survey SDK into their app using multiple options! Checkout the options below that we provide! If you are looking
|
||||
for something else, please [join our Discord!](https://formbricks.com/discord) and we would be glad to help.
|
||||
|
||||
Detailed Website Survey SDK documentation can be found [here](/developer-docs/website-survey-sdk).
|
||||
|
||||
<Libraries />
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before getting started, make sure you have:
|
||||
|
||||
1. A **public-facing** web application in your desired framework is set up and running.
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings.
|
||||
|
||||
---
|
||||
|
||||
## HTML
|
||||
|
||||
All you need to do is copy a `<script>` tag to your HTML head, and that’s about it!
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="HTML">
|
||||
```html {{ title: 'index.html' }}
|
||||
<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){
|
||||
var apiHost = "https://app.formbricks.com";
|
||||
var environmentId = "<your-environment-id>";
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/website";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example HTML project](https://github.com/formbricks/examples/tree/main/html) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## ReactJS
|
||||
|
||||
Install the Formbricks SDK using one of the package managers ie `npm`,`pnpm`,`yarn`. Note that zod is required as a peer dependency and must also be installed in your project.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Now, update your App.js/ts file to initialise Formbricks.
|
||||
<Col>
|
||||
<CodeGroup title="src/App.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
});
|
||||
}
|
||||
|
||||
function App() {
|
||||
// your own app
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example ReactJs project](https://github.com/formbricks/examples/tree/main/reactjs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## NextJS
|
||||
|
||||
NextJs projects typically follow two main conventions: the App Directory and the Pages Directory.
|
||||
To ensure smooth integration with the Formbricks SDK, which operates solely on the client side, follow the
|
||||
guidelines for each convention below:
|
||||
|
||||
- App directory: You will have to define a new component in `app/formbricks.tsx` file and call it in your `app/layout.tsx` file.
|
||||
- Pages directory: You will have to visit your `_app.tsx` and just initialise Formbricks there.
|
||||
|
||||
Code snippets for the integration for both conventions are provided to further assist you.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js zod
|
||||
```
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js zod
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
### App Directory
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="app/formbricks.tsx">
|
||||
|
||||
```tsx {{title: 'Typescript'}}
|
||||
"use client";
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
export default function FormbricksProvider() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
formbricks?.registerRouteChange();
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="app/layout.tsx">
|
||||
```tsx {{title: 'Typescript'}}
|
||||
// other imports
|
||||
import FormbricksProvider from "./formbricks";
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<FormbricksProvider />
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
Refer to our [Example NextJS App Directory project](https://github.com/formbricks/examples/tree/main/nextjs-app) for more help!
|
||||
|
||||
### Pages Directory
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="src/pages/_app.tsx">
|
||||
|
||||
```tsx {{ title: 'Typescript' }}
|
||||
// other import
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
});
|
||||
}
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// Connect next.js router to Formbricks
|
||||
const handleRouteChange = formbricks?.registerRouteChange;
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, []);
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
Refer to our [Example NextJS Pages Directory project](https://github.com/formbricks/examples/tree/main/nextjs-pages) for more help!
|
||||
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
First initialize the Formbricks SDK, ensure that it only runs on the client side.
|
||||
To connect the Next.js router to Formbricks and ensure the SDK can keep track of every page change, we are registering the route change event.
|
||||
|
||||
Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
---
|
||||
|
||||
## VueJs
|
||||
|
||||
Integrating the Formbricks SDK with Vue.js is a straightforward process.
|
||||
We will make sure the SDK is only loaded and used on the client side, as it's not intended for server-side usage.
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Install Formbricks JS library">
|
||||
```shell {{ title: 'npm' }}
|
||||
npm install @formbricks/js
|
||||
```
|
||||
|
||||
```shell {{ title: 'pnpm' }}
|
||||
pnpm add @formbricks/js
|
||||
```
|
||||
|
||||
```shell {{ title: 'yarn' }}
|
||||
yarn add @formbricks/js
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="src/formbricks.js">
|
||||
|
||||
```js
|
||||
import formbricks from "@formbricks/js/website";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "<environment-id>",
|
||||
apiHost: "<api-host>",
|
||||
});
|
||||
}
|
||||
|
||||
export default formbricks;
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup title="src/main.js">
|
||||
|
||||
```js
|
||||
// other imports
|
||||
import formbricks from "@/formbricks";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
if (typeof formbricks !== "undefined") {
|
||||
formbricks.registerRouteChange();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
### Required customizations to be made
|
||||
|
||||
<Properties>
|
||||
<Property name="environment-id" type="string">
|
||||
Formbricks Environment ID.
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name="api-host" type="string">
|
||||
URL of the hosted Formbricks instance.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
Refer to our [Example VueJs project](https://github.com/formbricks/examples/tree/main/vuejs) for more help! Now visit the [Validate your Setup](#validate-your-setup) section to verify your setup!
|
||||
|
||||
## Validate your setup
|
||||
|
||||
Once you have completed the steps above, you can validate your setup by checking the **Setup Checklist** in the Settings. Your widget status indicator should go from this:
|
||||
|
||||
<MdxImage
|
||||
src={WidgetNotConnected}
|
||||
alt="Widget isnt connected"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
To this:
|
||||
|
||||
<MdxImage
|
||||
src={WidgetConnected}
|
||||
alt="Widget is connected"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Debugging Formbricks Integration
|
||||
|
||||
Enabling Formbricks debug mode in your browser is a useful troubleshooting step for identifying and resolving complex issues. This section outlines how to activate debug mode, covers common use cases, and provides insights into specific debug log messages.
|
||||
|
||||
### Activate Debug Mode
|
||||
|
||||
To activate Formbricks debug mode:
|
||||
|
||||
1. **Via URL Parameter:**
|
||||
|
||||
- Enable debug mode mode by adding `?formbricksDebug=true` to your application's URL (e.g. `https://example.com?formbricksDebug=true` or `https://example.com?page=123&formbricksDebug=true`). This parameter will enable debugging for the current page.
|
||||
|
||||
2. **View Debug Logs:**
|
||||
|
||||
- Open your browser's developer tools by pressing `F12` or right-clicking and selecting "Inspect."
|
||||
- Navigate to the "Console" tab to view Formbricks debugging information.
|
||||
|
||||
**How to Open Browser Console:**
|
||||
|
||||
- **Google Chrome:** Press `F12` or right-click, select "Inspect," and go to the "Console" tab.
|
||||
- **Firefox:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
|
||||
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
Debug mode is beneficial for scenarios such as:
|
||||
|
||||
- Verifying Formbricks initialization.
|
||||
- Identifying survey trigger issues.
|
||||
- Troubleshooting unexpected behavior.
|
||||
|
||||
### Debug Log Messages
|
||||
|
||||
Debug log messages provide insights into:
|
||||
|
||||
- API calls and responses.
|
||||
- Event tracking, survey triggers and form interactions.
|
||||
- Initialization errors.
|
||||
|
||||
**Can’t figure it out? [Join our Discord!](https://formbricks.com/discord)**
|
||||
|
||||
---
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,115 +0,0 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import I1 from "./images/I1.webp";
|
||||
import I2 from "./images/I2.webp";
|
||||
import I3 from "./images/I3.webp";
|
||||
import I4 from "./images/I4.webp";
|
||||
import I5 from "./images/I5.webp";
|
||||
import I6 from "./images/I6.webp";
|
||||
import I7 from "./images/I7.webp";
|
||||
import I8 from "./images/I8.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Formbricks Quickstart Guide: Website Surveys Made Easier & Faster",
|
||||
description:
|
||||
"Formbricks is the easiest way to create and manage website surveys. This quickstart guide will show you how to create your first website survey in under 5 minutes.",
|
||||
};
|
||||
|
||||
#### Website Surveys
|
||||
|
||||
# Quickstart
|
||||
|
||||
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).
|
||||
</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:
|
||||
|
||||
<Note>
|
||||
Website & App Surveys have the same integration process. The difference will come when we setup our survey.
|
||||
</Note>
|
||||
|
||||
<MdxImage
|
||||
src={I1}
|
||||
alt="Choose website survey from survey type"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl "
|
||||
/>
|
||||
|
||||
2. **Connect your App/Website**: Once you get through a couple of onboarding steps, you’ll be asked to connect your app or website. This is where you’ll find the code snippet for both HTML as well as the npm package which you need to embed in your app:
|
||||
|
||||
<MdxImage
|
||||
src={I2}
|
||||
alt="Code snippet for connecting app with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Paste the code snippet in your app and reload the page. You should now see the Formbricks widget in the lower right corner of your app! The integration is now complete.
|
||||
|
||||
<MdxImage
|
||||
src={I3}
|
||||
alt="App connected with Formbricks"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
Onboarding is complete! Now let’s create our first survey as you should see templates to choose from after clicking on **Next**:
|
||||
|
||||
<MdxImage
|
||||
src={I4}
|
||||
alt="Choose a survey template"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. **Create your first survey**: To be able to see a survey in your app, you need to create one. We’ll choose one of the templates and head over to the survey settings:
|
||||
|
||||
Pick the Survey Type as **Website Survey**.
|
||||
|
||||
<MdxImage
|
||||
src={I5}
|
||||
alt="Survey settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
1. **Set Trigger for the Survey**: Scroll down to Survey Trigger and click on **+ Add action**, choose **New Session**. This will cause this survey to appear when the Formbricks Widget tracks a new session:
|
||||
|
||||
<MdxImage
|
||||
src={I6}
|
||||
alt="Survey trigger settings for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
5. **Set Recontact Options for debugging**: In Recontact Options we choose the following settings, so that we can play around with the survey more easily. By default, each survey will be shown only once for each session to prevent survey fatigue:
|
||||
|
||||
<Note>
|
||||
Please change this setting later on after testing your survey to prevent survey fatigue for your users.
|
||||
</Note>
|
||||
|
||||
<MdxImage
|
||||
src={I7}
|
||||
alt="Recontact options for app survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
6. **Publish your survey**: Now hit **Publish** and you’ll be forwarded to the Summary Page. This is where you’ll find the responses to this survey.
|
||||
|
||||
<MdxImage
|
||||
src={I8}
|
||||
alt="Survey published successfully"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
- We offer framework guides for various frontend tech, head over to the the [Website Survey Framework Guides](/website-survey/framework-guides) to get started with your public facing website surveys.
|
||||
- Head over to our Website Survey SDK documentation to get started with the [Website Survey JS SDK](/developer-docs/website-survey-sdk).
|
||||
|
||||
Still struggling or something not working as expected? [Join our Discord!](https://formbricks.com/discord) and we'd be glad to assist you!
|
||||
@@ -22,58 +22,6 @@ export const navigation: Array<NavGroup> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "App Surveys",
|
||||
links: [
|
||||
{ title: "Quickstart", href: "/app-surveys/quickstart" },
|
||||
{ title: "Framework Guides", href: "/app-surveys/framework-guides" },
|
||||
{
|
||||
title: "Features",
|
||||
children: [
|
||||
{ title: "Identify Users", href: "/app-surveys/user-identification" },
|
||||
{ title: "Actions", href: "/app-surveys/actions" },
|
||||
{ title: "Advanced Targeting", href: "/app-surveys/advanced-targeting" },
|
||||
{ title: "Show Survey to % of users", href: "/global/show-survey-to-percent-of-users" }, // app and website
|
||||
{ title: "Recontact Options", href: "/app-surveys/recontact" },
|
||||
{ title: "Hidden Fields", href: "/global/hidden-fields" }, // global
|
||||
{ title: "Multi Language Surveys", href: "/global/multi-language-surveys" }, // global
|
||||
{ title: "User Metadata", href: "/global/metadata" }, // global
|
||||
{ title: "Custom Styling", href: "/global/overwrite-styling" }, // global
|
||||
{ title: "Conditional Logic", href: "/global/conditional-logic" }, // global
|
||||
{ title: "Start & End Dates", href: "/global/custom-start-end-conditions" }, // global
|
||||
{ title: "Limit submissions", href: "/global/limit-submissions" }, // global
|
||||
{ title: "Recall Functionality", href: "/global/recall" }, // global
|
||||
{ title: "Partial Submissions", href: "/global/partial-submissions" }, // global
|
||||
{ title: "Shareable Dashboards", href: "/global/shareable-dashboards" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Website Surveys",
|
||||
links: [
|
||||
{ title: "Quickstart", href: "/website-surveys/quickstart" },
|
||||
{ title: "Framework Guides", href: "/website-surveys/framework-guides" },
|
||||
{
|
||||
title: "Features",
|
||||
children: [
|
||||
{ title: "Actions", href: "/website-surveys/actions" },
|
||||
{ title: "Show Survey to % of users", href: "/global/show-survey-to-percent-of-users" }, // app and website
|
||||
{ title: "Recontact Options", href: "/app-surveys/recontact" },
|
||||
{ title: "Hidden Fields", href: "/global/hidden-fields" }, // global
|
||||
{ title: "Multi Language Surveys", href: "/global/multi-language-surveys" }, // global
|
||||
{ title: "User Metadata", href: "/global/metadata" }, // global
|
||||
{ title: "Custom Styling", href: "/global/overwrite-styling" }, // global
|
||||
{ title: "Conditional Logic", href: "/global/conditional-logic" }, // global
|
||||
{ title: "Start & End Dates", href: "/global/custom-start-end-conditions" }, // global
|
||||
{ title: "Limit submissions", href: "/global/limit-submissions" }, // global
|
||||
{ title: "Recall Functionality", href: "/global/recall" }, // global
|
||||
{ title: "Partial Submissions", href: "/global/partial-submissions" }, // global
|
||||
{ title: "Shareable Dashboards", href: "/global/shareable-dashboards" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Link Surveys",
|
||||
links: [
|
||||
@@ -104,6 +52,33 @@ export const navigation: Array<NavGroup> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Website & App Surveys",
|
||||
links: [
|
||||
{ title: "Quickstart", href: "/app-surveys/quickstart" },
|
||||
{ title: "Framework Guides", href: "/app-surveys/framework-guides" },
|
||||
{
|
||||
title: "Features",
|
||||
children: [
|
||||
{ title: "Identify Users", href: "/app-surveys/user-identification" },
|
||||
{ title: "Actions", href: "/app-surveys/actions" },
|
||||
{ title: "Advanced Targeting", href: "/app-surveys/advanced-targeting" },
|
||||
{ title: "Show Survey to % of users", href: "/global/show-survey-to-percent-of-users" }, // app and website
|
||||
{ title: "Recontact Options", href: "/app-surveys/recontact" },
|
||||
{ title: "Hidden Fields", href: "/global/hidden-fields" }, // global
|
||||
{ title: "Multi Language Surveys", href: "/global/multi-language-surveys" }, // global
|
||||
{ title: "User Metadata", href: "/global/metadata" }, // global
|
||||
{ title: "Custom Styling", href: "/global/overwrite-styling" }, // global
|
||||
{ title: "Conditional Logic", href: "/global/conditional-logic" }, // global
|
||||
{ title: "Start & End Dates", href: "/global/custom-start-end-conditions" }, // global
|
||||
{ title: "Limit submissions", href: "/global/limit-submissions" }, // global
|
||||
{ title: "Recall Functionality", href: "/global/recall" }, // global
|
||||
{ title: "Partial Submissions", href: "/global/partial-submissions" }, // global
|
||||
{ title: "Shareable Dashboards", href: "/global/shareable-dashboards" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Core Features",
|
||||
links: [
|
||||
@@ -142,8 +117,7 @@ export const navigation: Array<NavGroup> = [
|
||||
{ title: "Zapier", href: "/developer-docs/integrations/zapier" },
|
||||
],
|
||||
},
|
||||
{ title: "SDK: Web Apps", href: "/developer-docs/app-survey-sdk" },
|
||||
{ title: "SDK: Public Websites", href: "/developer-docs/website-survey-sdk" },
|
||||
{ title: "SDK: Formbricks JS", href: "/developer-docs/js-sdk" },
|
||||
{ title: "SDK: React Native", href: "/developer-docs/react-native-in-app-surveys" },
|
||||
{ title: "SDK: Formbricks API", href: "/developer-docs/api-sdk" },
|
||||
{ title: "REST API", href: "/developer-docs/rest-api" },
|
||||
|
||||
@@ -121,6 +121,22 @@ const nextConfig = {
|
||||
destination: "/developer-docs/integrations/:path",
|
||||
permanent: true,
|
||||
},
|
||||
|
||||
{
|
||||
source: "/developer-docs/website-survey-sdk",
|
||||
destination: "/developer-docs/js-sdk",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/website-surveys/quickstart",
|
||||
destination: "/app-surveys/quickstart",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/website-surveys/framework-guides",
|
||||
destination: "/app-surveys/framework-guides",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export const OnboardingSetupInstructions = ({
|
||||
var apiHost = "${webAppUrl}";
|
||||
var environmentId = "${environmentId}";
|
||||
var userId = "testUser";
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/app";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost, userId: userId})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
`;
|
||||
@@ -44,13 +44,13 @@ export const OnboardingSetupInstructions = ({
|
||||
!function(){
|
||||
var apiHost = "${webAppUrl}";
|
||||
var environmentId = "${environmentId}";
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/website";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost})},500)}();
|
||||
var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=apiHost+"/api/packages/js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: environmentId, apiHost: apiHost})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->
|
||||
`;
|
||||
|
||||
const npmSnippetForAppSurveys = `
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
@@ -69,7 +69,7 @@ export const OnboardingSetupInstructions = ({
|
||||
|
||||
const npmSnippetForWebsiteSurveys = `
|
||||
// other imports
|
||||
import formbricks from "@formbricks/js/website";
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
|
||||
@@ -36,9 +36,7 @@ const Page = async ({ params }: ConnectPageProps) => {
|
||||
<ConnectWithFormbricks
|
||||
environment={environment}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
widgetSetupCompleted={
|
||||
channel === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted
|
||||
}
|
||||
widgetSetupCompleted={environment.appSetupCompleted}
|
||||
channel={channel}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -41,7 +41,7 @@ const Page = async ({ params }: ChannelPageProps) => {
|
||||
return (
|
||||
<div className="flex min-h-full min-w-full flex-col items-center justify-center space-y-12">
|
||||
<Header
|
||||
title="Where do you want to survey people?"
|
||||
title="Where do you mainly want to survey people?"
|
||||
subtitle="Run surveys on public websites, in your app, or with shareable links & emails."
|
||||
/>
|
||||
<OnboardingOptionsContainer options={channelOptions} />
|
||||
|
||||
@@ -34,8 +34,8 @@ export const CardStylingSettings = ({
|
||||
setOpen,
|
||||
form,
|
||||
}: CardStylingSettingsProps) => {
|
||||
const isAppSurvey = surveyType === "app" || surveyType === "website";
|
||||
const surveyTypeDerived = isAppSurvey ? "App / Website" : "Link";
|
||||
const isAppSurvey = surveyType === "app";
|
||||
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
|
||||
const isLogoVisible = !!product.logo?.url;
|
||||
|
||||
const linkCardArrangement = form.watch("cardArrangement.linkSurveys") ?? "simple";
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { AlertCircleIcon, BlocksIcon, CheckIcon, EarthIcon, LinkIcon, MonitorIcon } from "lucide-react";
|
||||
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { getDefaultEndingCard } from "@formbricks/lib/templates";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSegment } from "@formbricks/types/segment";
|
||||
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
|
||||
import { Badge } from "@formbricks/ui/components/Badge";
|
||||
@@ -18,25 +17,15 @@ interface HowToSendCardProps {
|
||||
localSurvey: TSurvey;
|
||||
setLocalSurvey: (survey: TSurvey | ((TSurvey: TSurvey) => TSurvey)) => void;
|
||||
environment: TEnvironment;
|
||||
organizationId: string;
|
||||
product: TProduct;
|
||||
}
|
||||
|
||||
export const HowToSendCard = ({
|
||||
localSurvey,
|
||||
setLocalSurvey,
|
||||
environment,
|
||||
product,
|
||||
organizationId,
|
||||
}: HowToSendCardProps) => {
|
||||
export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowToSendCardProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [appSetupCompleted, setAppSetupCompleted] = useState(false);
|
||||
const [websiteSetupCompleted, setWebsiteSetupCompleted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (environment) {
|
||||
setAppSetupCompleted(environment.appSetupCompleted);
|
||||
setWebsiteSetupCompleted(environment.websiteSetupCompleted);
|
||||
}
|
||||
}, [environment]);
|
||||
|
||||
@@ -81,24 +70,6 @@ export const HowToSendCard = ({
|
||||
};
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: "website",
|
||||
name: "Website Survey",
|
||||
icon: EarthIcon,
|
||||
description: "Run targeted surveys on public websites.",
|
||||
comingSoon: false,
|
||||
alert: !websiteSetupCompleted,
|
||||
hide: product.config.channel && product.config.channel !== "website",
|
||||
},
|
||||
{
|
||||
id: "app",
|
||||
name: "App Survey",
|
||||
icon: MonitorIcon,
|
||||
description: "Embed a survey in your web app to collect responses with user identification.",
|
||||
comingSoon: false,
|
||||
alert: !appSetupCompleted,
|
||||
hide: product.config.channel && product.config.channel !== "app",
|
||||
},
|
||||
{
|
||||
id: "link",
|
||||
name: "Link survey",
|
||||
@@ -109,25 +80,15 @@ export const HowToSendCard = ({
|
||||
hide: false,
|
||||
},
|
||||
{
|
||||
id: "headless",
|
||||
name: "Headless Survey",
|
||||
icon: BlocksIcon,
|
||||
description: "Use Formbricks API only and create your own frontend experience.",
|
||||
comingSoon: true,
|
||||
alert: false,
|
||||
hide: false,
|
||||
id: "app",
|
||||
name: "Website & App Survey",
|
||||
icon: MonitorIcon,
|
||||
description: "Embed a survey in your web app or website to collect responses.",
|
||||
comingSoon: false,
|
||||
alert: !appSetupCompleted,
|
||||
},
|
||||
];
|
||||
|
||||
const promotedFeaturesString =
|
||||
product.config.channel === "website"
|
||||
? "app"
|
||||
: product.config.channel === "app"
|
||||
? "website"
|
||||
: product.config.channel === "link"
|
||||
? "app or website"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
@@ -198,13 +159,11 @@ export const HowToSendCard = ({
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 text-xs font-normal text-slate-600">{option.description}</p>
|
||||
{option.alert && (
|
||||
{localSurvey.type === option.id && option.alert && (
|
||||
<div className="mt-2 flex items-center space-x-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2">
|
||||
<AlertCircleIcon className="h-5 w-5 text-amber-500" />
|
||||
<div className="text-amber-800">
|
||||
<p className="text-xs font-semibold">
|
||||
Your {option.id} is not yet connected to Formbricks.
|
||||
</p>
|
||||
<p className="text-xs font-semibold">Formbricks SDK is not connected</p>
|
||||
<p className="text-xs font-normal">
|
||||
<Link
|
||||
href={`/environments/${environment.id}/product/${option.id}-connection`}
|
||||
@@ -212,7 +171,7 @@ export const HowToSendCard = ({
|
||||
target="_blank">
|
||||
Connect Formbricks
|
||||
</Link>{" "}
|
||||
and launch surveys in your {option.id}.
|
||||
and launch surveys in your website or app.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,23 +182,6 @@ export const HowToSendCard = ({
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{promotedFeaturesString && (
|
||||
<div className="mt-2 flex items-center space-x-3 rounded-b-lg border border-slate-200 bg-slate-100 px-4 py-2">
|
||||
🤓
|
||||
<div className="ml-2 text-slate-500">
|
||||
<p className="text-xs">
|
||||
You can also use Formbricks to run {promotedFeaturesString} surveys.{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
href={`/organizations/${organizationId}/products/new/mode`}
|
||||
className="font-medium underline decoration-slate-400 underline-offset-2">
|
||||
Create a new product
|
||||
</Link>{" "}
|
||||
for your {promotedFeaturesString} to use this feature.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TMembershipRole } from "@formbricks/types/memberships";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSegment } from "@formbricks/types/segment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { HowToSendCard } from "./HowToSendCard";
|
||||
@@ -15,7 +14,6 @@ import { WhenToSendCard } from "./WhenToSendCard";
|
||||
|
||||
interface SettingsViewProps {
|
||||
environment: TEnvironment;
|
||||
organizationId: string;
|
||||
localSurvey: TSurvey;
|
||||
setLocalSurvey: (survey: TSurvey) => void;
|
||||
actionClasses: TActionClass[];
|
||||
@@ -25,12 +23,10 @@ interface SettingsViewProps {
|
||||
membershipRole?: TMembershipRole;
|
||||
isUserTargetingAllowed?: boolean;
|
||||
isFormbricksCloud: boolean;
|
||||
product: TProduct;
|
||||
}
|
||||
|
||||
export const SettingsView = ({
|
||||
environment,
|
||||
organizationId,
|
||||
localSurvey,
|
||||
setLocalSurvey,
|
||||
actionClasses,
|
||||
@@ -40,43 +36,38 @@ export const SettingsView = ({
|
||||
membershipRole,
|
||||
isUserTargetingAllowed = false,
|
||||
isFormbricksCloud,
|
||||
product,
|
||||
}: SettingsViewProps) => {
|
||||
const isWebSurvey = localSurvey.type === "website" || localSurvey.type === "app";
|
||||
const isAppSurvey = localSurvey.type === "app";
|
||||
|
||||
return (
|
||||
<div className="mt-12 space-y-3 p-5">
|
||||
<HowToSendCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
environment={environment}
|
||||
product={product}
|
||||
organizationId={organizationId}
|
||||
/>
|
||||
<HowToSendCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} environment={environment} />
|
||||
|
||||
{localSurvey.type === "app" ? (
|
||||
!isUserTargetingAllowed ? (
|
||||
<TargetingCard
|
||||
key={localSurvey.segment?.id}
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
environmentId={environment.id}
|
||||
attributeClasses={attributeClasses}
|
||||
segments={segments}
|
||||
initialSegment={segments.find((segment) => segment.id === localSurvey.segment?.id)}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedTargetingCard
|
||||
key={localSurvey.segment?.id}
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
environmentId={environment.id}
|
||||
attributeClasses={attributeClasses}
|
||||
segments={segments}
|
||||
initialSegment={segments.find((segment) => segment.id === localSurvey.segment?.id)}
|
||||
/>
|
||||
)
|
||||
<div>
|
||||
{!isUserTargetingAllowed ? (
|
||||
<TargetingCard
|
||||
key={localSurvey.segment?.id}
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
environmentId={environment.id}
|
||||
attributeClasses={attributeClasses}
|
||||
segments={segments}
|
||||
initialSegment={segments.find((segment) => segment.id === localSurvey.segment?.id)}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedTargetingCard
|
||||
key={localSurvey.segment?.id}
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
environmentId={environment.id}
|
||||
attributeClasses={attributeClasses}
|
||||
segments={segments}
|
||||
initialSegment={segments.find((segment) => segment.id === localSurvey.segment?.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<WhenToSendCard
|
||||
@@ -99,7 +90,7 @@ export const SettingsView = ({
|
||||
environmentId={environment.id}
|
||||
/>
|
||||
|
||||
{isWebSurvey && (
|
||||
{isAppSurvey && (
|
||||
<SurveyPlacementCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
|
||||
@@ -25,7 +25,6 @@ interface SurveyEditorProps {
|
||||
survey: TSurvey;
|
||||
product: TProduct;
|
||||
environment: TEnvironment;
|
||||
organizationId: string;
|
||||
actionClasses: TActionClass[];
|
||||
attributeClasses: TAttributeClass[];
|
||||
segments: TSegment[];
|
||||
@@ -44,7 +43,6 @@ export const SurveyEditor = ({
|
||||
survey,
|
||||
product,
|
||||
environment,
|
||||
organizationId,
|
||||
actionClasses,
|
||||
attributeClasses,
|
||||
segments,
|
||||
@@ -196,7 +194,6 @@ export const SurveyEditor = ({
|
||||
{activeView === "settings" && (
|
||||
<SettingsView
|
||||
environment={environment}
|
||||
organizationId={organizationId}
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
actionClasses={actionClasses}
|
||||
@@ -206,7 +203,6 @@ export const SurveyEditor = ({
|
||||
membershipRole={membershipRole}
|
||||
isUserTargetingAllowed={isUserTargetingAllowed}
|
||||
isFormbricksCloud={isFormbricksCloud}
|
||||
product={localProduct}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
@@ -217,7 +213,7 @@ export const SurveyEditor = ({
|
||||
questionId={activeQuestionId}
|
||||
product={localProduct}
|
||||
environment={environment}
|
||||
previewType={localSurvey.type === "app" || localSurvey.type === "website" ? "modal" : "fullwidth"}
|
||||
previewType={localSurvey.type === "app" ? "modal" : "fullwidth"}
|
||||
languageCode={selectedLanguageCode}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { isAdvancedSegment } from "@formbricks/lib/segment/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TBaseFilter, TSegment, TSegmentCreateInput, TSegmentUpdateInput } from "@formbricks/types/segment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { Alert, AlertDescription } from "@formbricks/ui/components/Alert";
|
||||
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
|
||||
import { BasicAddFilterModal } from "@formbricks/ui/components/BasicAddFilterModal";
|
||||
import { BasicSegmentEditor } from "@formbricks/ui/components/BasicSegmentEditor";
|
||||
@@ -158,7 +159,7 @@ export const TargetingCard = ({
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="w-full rounded-lg border border-slate-300 bg-white">
|
||||
className="w-full overflow-hidden rounded-lg border border-slate-300 bg-white">
|
||||
<Collapsible.CollapsibleTrigger
|
||||
asChild
|
||||
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50">
|
||||
@@ -437,6 +438,23 @@ export const TargetingCard = ({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Alert className="flex items-center rounded-none bg-slate-50">
|
||||
<AlertDescription className="ml-2">
|
||||
<span className="mr-1 text-slate-600">
|
||||
User targeting is currently only available when{" "}
|
||||
<Link
|
||||
href="https://formbricks.com//docs/app-surveys/user-identification"
|
||||
target="blank"
|
||||
className="underline">
|
||||
identifying users
|
||||
</Link>{" "}
|
||||
with the Formbricks SDK.
|
||||
</span>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
|
||||
@@ -34,9 +34,7 @@ export const WhenToSendCard = ({
|
||||
propActionClasses,
|
||||
membershipRole,
|
||||
}: WhenToSendCardProps) => {
|
||||
const [open, setOpen] = useState(
|
||||
localSurvey.type === "app" || localSurvey.type === "website" ? true : false
|
||||
);
|
||||
const [open, setOpen] = useState(localSurvey.type === "app" ? true : false);
|
||||
const [isAddActionModalOpen, setAddActionModalOpen] = useState(false);
|
||||
const [actionClasses, setActionClasses] = useState<TActionClass[]>(propActionClasses);
|
||||
const [randomizerToggle, setRandomizerToggle] = useState(localSurvey.displayPercentage ? true : false);
|
||||
|
||||
@@ -81,7 +81,6 @@ const Page = async ({ params, searchParams }) => {
|
||||
attributeClasses={attributeClasses}
|
||||
responseCount={responseCount}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
organizationId={organization.id}
|
||||
colors={SURVEY_BG_COLORS}
|
||||
segments={segments}
|
||||
isUserTargetingAllowed={isUserTargetingAllowed}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { formbricksEnabled } from "@/app/lib/formbricks";
|
||||
import type { Session } from "next-auth";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
type UsageAttributesUpdaterProps = {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { PersonSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PersonSecondaryNavigation";
|
||||
import { CircleHelpIcon } from "lucide-react";
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
@@ -22,12 +21,6 @@ const Page = async ({ params }) => {
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
const currentProductChannel = product.config.channel ?? null;
|
||||
|
||||
if (currentProductChannel && currentProductChannel !== "app") {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const HowToAddAttributesButton = (
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { SecondaryNavigation } from "@formbricks/ui/components/SecondaryNavigation";
|
||||
|
||||
interface PersonSecondaryNavigationProps {
|
||||
@@ -13,16 +13,14 @@ export const PersonSecondaryNavigation = async ({
|
||||
environmentId,
|
||||
loading,
|
||||
}: PersonSecondaryNavigationProps) => {
|
||||
let currentProductChannel: TProductConfigChannel = null;
|
||||
let product: TProduct | null = null;
|
||||
|
||||
if (!loading && environmentId) {
|
||||
const product = await getProductByEnvironmentId(environmentId);
|
||||
product = await getProductByEnvironmentId(environmentId);
|
||||
|
||||
if (!product) {
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
currentProductChannel = product.config.channel ?? null;
|
||||
}
|
||||
|
||||
const navigation = [
|
||||
@@ -40,8 +38,6 @@ export const PersonSecondaryNavigation = async ({
|
||||
id: "attributes",
|
||||
label: "Attributes",
|
||||
href: `/environments/${environmentId}/attributes`,
|
||||
// hide attributes tab if it's being used in the loading state or if the product's channel is website or link
|
||||
hidden: loading || !!(currentProductChannel && currentProductChannel !== "app"),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@ import { ActionClassDataRow } from "@/app/(app)/environments/[environmentId]/act
|
||||
import { ActionTableHeading } from "@/app/(app)/environments/[environmentId]/actions/components/ActionTableHeading";
|
||||
import { AddActionModal } from "@/app/(app)/environments/[environmentId]/actions/components/AddActionModal";
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
@@ -15,9 +13,8 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const [actionClasses, product, organization] = await Promise.all([
|
||||
const [actionClasses, organization] = await Promise.all([
|
||||
getActionClasses(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
@@ -25,11 +22,6 @@ const Page = async ({ params }) => {
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
if (currentProductChannel === "link") {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const renderAddActionButton = () => (
|
||||
<AddActionModal environmentId={params.environmentId} actionClasses={actionClasses} />
|
||||
);
|
||||
|
||||
@@ -53,9 +53,6 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
|
||||
|
||||
const isMultiOrgEnabled = features?.isMultiOrgEnabled ?? false;
|
||||
|
||||
const currentProductChannel =
|
||||
products.find((product) => product.id === environment.productId)?.config.channel ?? null;
|
||||
|
||||
let peopleCount = 0;
|
||||
let responseCount = 0;
|
||||
|
||||
@@ -101,7 +98,6 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
|
||||
<TopControlBar
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
currentProductChannel={currentProductChannel}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
/>
|
||||
<div className="mt-14">{children}</div>
|
||||
|
||||
@@ -178,7 +178,6 @@ export const MainNavigation = ({
|
||||
href: `/environments/${environment.id}/actions`,
|
||||
icon: MousePointerClick,
|
||||
isActive: pathname?.includes("/actions") || pathname?.includes("/actions"),
|
||||
isHidden: product?.config.channel === "link",
|
||||
},
|
||||
{
|
||||
name: "Integrations",
|
||||
@@ -195,7 +194,7 @@ export const MainNavigation = ({
|
||||
isHidden: isViewer,
|
||||
},
|
||||
],
|
||||
[environment.id, pathname, product?.config.channel, isViewer]
|
||||
[environment.id, pathname, isViewer]
|
||||
);
|
||||
|
||||
const dropdownNavigation = [
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
import { TopControlButtons } from "@/app/(app)/environments/[environmentId]/components/TopControlButtons";
|
||||
import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TMembershipRole } from "@formbricks/types/memberships";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
|
||||
interface SideBarProps {
|
||||
environment: TEnvironment;
|
||||
environments: TEnvironment[];
|
||||
currentProductChannel: TProductConfigChannel;
|
||||
membershipRole?: TMembershipRole;
|
||||
}
|
||||
|
||||
export const TopControlBar = ({
|
||||
environment,
|
||||
environments,
|
||||
currentProductChannel,
|
||||
membershipRole,
|
||||
}: SideBarProps) => {
|
||||
export const TopControlBar = ({ environment, environments, membershipRole }: SideBarProps) => {
|
||||
return (
|
||||
<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 items-center space-x-2 py-2">
|
||||
{currentProductChannel && currentProductChannel !== "link" && (
|
||||
<WidgetStatusIndicator environment={environment} size="mini" type={currentProductChannel} />
|
||||
)}
|
||||
{!currentProductChannel && (
|
||||
<>
|
||||
<WidgetStatusIndicator environment={environment} size="mini" type="website" />
|
||||
<WidgetStatusIndicator environment={environment} size="mini" type="app" />
|
||||
</>
|
||||
)}
|
||||
<TopControlButtons
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
membershipRole={membershipRole}
|
||||
currentProductChannel={currentProductChannel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
import { EnvironmentSwitch } from "@/app/(app)/environments/[environmentId]/components/EnvironmentSwitch";
|
||||
import { CircleUserIcon, MessageCircleQuestionIcon, PlusIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TMembershipRole } from "@formbricks/types/memberships";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
|
||||
interface TopControlButtonsProps {
|
||||
@@ -14,7 +13,6 @@ interface TopControlButtonsProps {
|
||||
environments: TEnvironment[];
|
||||
isFormbricksCloud: boolean;
|
||||
membershipRole?: TMembershipRole;
|
||||
currentProductChannel: TProductConfigChannel;
|
||||
}
|
||||
|
||||
export const TopControlButtons = ({
|
||||
@@ -22,13 +20,11 @@ export const TopControlButtons = ({
|
||||
environments,
|
||||
isFormbricksCloud,
|
||||
membershipRole,
|
||||
currentProductChannel,
|
||||
}: TopControlButtonsProps) => {
|
||||
const router = useRouter();
|
||||
const showEnvironmentSwitch = currentProductChannel !== "link";
|
||||
return (
|
||||
<div className="z-50 flex items-center space-x-2">
|
||||
{showEnvironmentSwitch && <EnvironmentSwitch environment={environment} environments={environments} />}
|
||||
<EnvironmentSwitch environment={environment} environments={environments} />
|
||||
{isFormbricksCloud && (
|
||||
<Button
|
||||
variant="minimal"
|
||||
|
||||
@@ -1,86 +1,52 @@
|
||||
import clsx from "clsx";
|
||||
import { AlertTriangleIcon, CheckIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
|
||||
interface WidgetStatusIndicatorProps {
|
||||
environment: TEnvironment;
|
||||
size: "large" | "mini";
|
||||
type: "app" | "website";
|
||||
}
|
||||
|
||||
export const WidgetStatusIndicator = ({ environment, size, type }: WidgetStatusIndicatorProps) => {
|
||||
export const WidgetStatusIndicator = ({ environment }: WidgetStatusIndicatorProps) => {
|
||||
const stati = {
|
||||
notImplemented: {
|
||||
icon: AlertTriangleIcon,
|
||||
title: `Your ${type} is not yet connected.`,
|
||||
subtitle: ``,
|
||||
shortText: `Connect your ${type} with Formbricks`,
|
||||
title: `Formbricks SDK is not yet connected.`,
|
||||
subtitle: `Connect your website or app with Formbricks`,
|
||||
},
|
||||
running: {
|
||||
icon: CheckIcon,
|
||||
title: "Receiving data 💃🕺",
|
||||
subtitle: `Your ${type} is connected with Formbricks.`,
|
||||
shortText: `${type === "app" ? "App" : "Website"} connected`,
|
||||
subtitle: `Formbricks SDK is connected`,
|
||||
},
|
||||
};
|
||||
|
||||
const setupStatus = type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
|
||||
let status: "notImplemented" | "running" | "issue";
|
||||
let status: "notImplemented" | "running";
|
||||
|
||||
if (setupStatus) {
|
||||
if (environment.appSetupCompleted) {
|
||||
status = "running";
|
||||
} else {
|
||||
status = "notImplemented";
|
||||
}
|
||||
|
||||
const currentStatus = stati[status];
|
||||
const currentStatus: { icon: React.ExoticComponent; title: string; subtitle: string } = stati[status];
|
||||
|
||||
if (size === "large") {
|
||||
return (
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"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 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"
|
||||
className={cn(
|
||||
"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"
|
||||
)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"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>
|
||||
<p className="text-md font-bold text-slate-800 md:text-xl">{currentStatus.title}</p>
|
||||
<p className="w-2/3 text-balance text-sm text-slate-600">{currentStatus.subtitle}</p>
|
||||
<currentStatus.icon />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (size === "mini") {
|
||||
return (
|
||||
<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>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
<p className="text-md font-bold text-slate-800 md:text-xl">{currentStatus.title}</p>
|
||||
<p className="w-2/3 text-balance text-sm text-slate-600">{currentStatus.subtitle}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ import { getIntegrations } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { getWebhookCountBySource } from "@formbricks/lib/webhook/service";
|
||||
import { TIntegrationType } from "@formbricks/types/integration";
|
||||
import { Card } from "@formbricks/ui/components/Card";
|
||||
@@ -35,7 +34,6 @@ const Page = async ({ params }) => {
|
||||
zapierWebhookCount,
|
||||
makeWebhookCount,
|
||||
n8nwebhookCount,
|
||||
product,
|
||||
] = await Promise.all([
|
||||
getEnvironment(environmentId),
|
||||
getIntegrations(environmentId),
|
||||
@@ -45,7 +43,6 @@ const Page = async ({ params }) => {
|
||||
getWebhookCountBySource(environmentId, "zapier"),
|
||||
getWebhookCountBySource(environmentId, "make"),
|
||||
getWebhookCountBySource(environmentId, "n8n"),
|
||||
getProductByEnvironmentId(environmentId),
|
||||
]);
|
||||
|
||||
const isIntegrationConnected = (type: TIntegrationType) =>
|
||||
@@ -67,11 +64,7 @@ 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 productChannel =
|
||||
product?.config.channel === "website" || !product?.config.channel ? "website" : product?.config.channel;
|
||||
|
||||
const widgetSetupCompleted = !!environment?.appSetupCompleted;
|
||||
const integrationCards = [
|
||||
{
|
||||
docsHref: "https://formbricks.com/docs/integrations/zapier",
|
||||
@@ -199,27 +192,19 @@ const Page = async ({ params }) => {
|
||||
},
|
||||
];
|
||||
|
||||
if (productChannel !== "link") {
|
||||
integrationCards.unshift({
|
||||
docsHref: "https://formbricks.com/docs/getting-started/framework-guides#next-js",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
connectHref: `/environments/${environmentId}/product/${productChannel}-connection`,
|
||||
connectText: "Connect",
|
||||
connectNewTab: false,
|
||||
label: "Javascript Widget",
|
||||
description: "Integrate Formbricks into your Webapp",
|
||||
icon: <Image src={JsLogo} alt="Javascript Logo" />,
|
||||
connected: widgetSetupCompleted,
|
||||
statusText: bothSetupCompleted
|
||||
? "app & website connected"
|
||||
: environment?.appSetupCompleted
|
||||
? "app Connected"
|
||||
: environment?.websiteSetupCompleted
|
||||
? "website connected"
|
||||
: "Not Connected",
|
||||
});
|
||||
}
|
||||
integrationCards.unshift({
|
||||
docsHref: "https://formbricks.com/docs/getting-started/framework-guides#next-js",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
connectHref: `/environments/${environmentId}/product/app-connection`,
|
||||
connectText: "Connect",
|
||||
connectNewTab: false,
|
||||
label: "Javascript SDK",
|
||||
description: "Integrate Formbricks into your Website or App",
|
||||
icon: <Image src={JsLogo} alt="Javascript Logo" />,
|
||||
connected: widgetSetupCompleted,
|
||||
statusText: widgetSetupCompleted ? "App Connected" : "Not Connected",
|
||||
});
|
||||
|
||||
if (isViewer) return <ErrorComponent />;
|
||||
|
||||
|
||||
@@ -2,21 +2,18 @@ import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/
|
||||
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 { notFound } from "next/navigation";
|
||||
import { getMultiLanguagePermission } from "@formbricks/ee/lib/service";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { EnvironmentNotice } from "@formbricks/ui/components/EnvironmentNotice";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SettingsCard } from "../../../settings/components/SettingsCard";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const [environment, product, organization] = await Promise.all([
|
||||
const [environment, organization] = await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
@@ -29,11 +26,6 @@ const Page = async ({ params }) => {
|
||||
}
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
if (currentProductChannel && currentProductChannel !== "app") {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
@@ -42,15 +34,14 @@ const Page = async ({ params }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="app-connection"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
<div className="space-y-4">
|
||||
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/product/app-connection" />
|
||||
<SettingsCard
|
||||
title="App Connection Status"
|
||||
title="Website & 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" />}
|
||||
{environment && <WidgetStatusIndicator environment={environment} />}
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Your EnvironmentId"
|
||||
@@ -61,7 +52,7 @@ const Page = async ({ params }) => {
|
||||
title="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your app"
|
||||
noPadding>
|
||||
<SetupInstructions type="app" environmentId={params.environmentId} webAppUrl={WEBAPP_URL} />
|
||||
<SetupInstructions environmentId={params.environmentId} webAppUrl={WEBAPP_URL} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
</PageContentWrapper>
|
||||
|
||||
@@ -19,11 +19,10 @@ const tabs = [
|
||||
interface SetupInstructionsProps {
|
||||
environmentId: string;
|
||||
webAppUrl: string;
|
||||
type: "app" | "website";
|
||||
}
|
||||
|
||||
export const SetupInstructions = ({ environmentId, webAppUrl, type }: SetupInstructionsProps) => {
|
||||
const [activeTab, setActiveTab] = useState(type === "website" ? tabs[1].id : tabs[0].id);
|
||||
export const SetupInstructions = ({ environmentId, webAppUrl }: SetupInstructionsProps) => {
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -39,14 +38,13 @@ export const SetupInstructions = ({ environmentId, webAppUrl, type }: SetupInstr
|
||||
<CodeBlock language="sh">yarn add @formbricks/js</CodeBlock>
|
||||
<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/${type}";
|
||||
<CodeBlock language="js">{`import formbricks from "@formbricks/js";
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "${environmentId}", ${type === "app" ? `\n userId: "<user-id>",` : ""}
|
||||
environmentId: "${environmentId}",
|
||||
apiHost: "${webAppUrl}",
|
||||
});
|
||||
}`}</CodeBlock>
|
||||
|
||||
<ul className="list-disc text-sm">
|
||||
<li>
|
||||
<span className="font-semibold">environmentId:</span> Used to identify the correct
|
||||
@@ -56,6 +54,17 @@ if (typeof window !== "undefined") {
|
||||
<span className="font-semibold">apiHost:</span> This is the URL of your Formbricks backend.
|
||||
</li>
|
||||
</ul>
|
||||
<span className="text-sm text-slate-600">
|
||||
If you are planning to{" "}
|
||||
<Link
|
||||
href="https://formbricks.com//docs/app-surveys/user-identification"
|
||||
target="blank"
|
||||
className="underline">
|
||||
identifying your users
|
||||
</Link>{" "}
|
||||
you also need to pass a <span className="font-semibold">userId</span> to the{" "}
|
||||
<span className="font-semibold">init</span> function.
|
||||
</span>
|
||||
<h4>Step 3: Debug mode</h4>
|
||||
<p>
|
||||
Switch on the debug mode by appending <i>?formbricksDebug=true</i> to the URL where you load the
|
||||
@@ -69,10 +78,8 @@ if (typeof window !== "undefined") {
|
||||
</p>
|
||||
<h4>You're done 🎉</h4>
|
||||
<p>
|
||||
Your {type} now communicates with Formbricks - sending events, and loading surveys
|
||||
automatically!
|
||||
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
|
||||
</p>
|
||||
|
||||
<ul className="list-disc text-sm text-slate-700">
|
||||
<li>
|
||||
<span>Need a more detailed setup guide for React, Next.js or Vue.js?</span>{" "}
|
||||
@@ -111,11 +118,11 @@ if (typeof window !== "undefined") {
|
||||
<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 {type}:
|
||||
Insert this code into the <code>{`<head>`}</code> tag of your app
|
||||
</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="${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)}();
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="${webAppUrl}/api/packages/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)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->`}</CodeBlock>
|
||||
<h4>Step 2: Debug mode</h4>
|
||||
@@ -131,8 +138,7 @@ if (typeof window !== "undefined") {
|
||||
</p>
|
||||
<h4>You're done 🎉</h4>
|
||||
<p>
|
||||
Your {type} now communicates with Formbricks - sending events, and loading surveys
|
||||
automatically!
|
||||
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
|
||||
</p>
|
||||
<ul className="list-disc text-sm text-slate-700">
|
||||
<li>
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
"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/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/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"),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
id: "app-connection",
|
||||
label: "App Connection",
|
||||
icon: <ListChecksIcon className="h-5 w-5" />,
|
||||
current: pathname?.includes("/app-connection"),
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
@@ -1,71 +0,0 @@
|
||||
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 { notFound } from "next/navigation";
|
||||
import { getMultiLanguagePermission } from "@formbricks/ee/lib/service";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { EnvironmentNotice } from "@formbricks/ui/components/EnvironmentNotice";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SettingsCard } from "../../../settings/components/SettingsCard";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const [environment, product, organization] = await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
getProductByEnvironmentId(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);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
if (currentProductChannel && currentProductChannel !== "website") {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle="Configuration">
|
||||
<ProductConfigNavigation
|
||||
environmentId={params.environmentId}
|
||||
activeId="website-connection"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</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="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your website"
|
||||
noPadding>
|
||||
<SetupInstructions environmentId={params.environmentId} webAppUrl={WEBAPP_URL} type="website" />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Your EnvironmentId"
|
||||
description="This id uniquely identifies this Formbricks environment.">
|
||||
<EnvironmentIdField environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -6,7 +6,6 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { EnvironmentNotice } from "@formbricks/ui/components/EnvironmentNotice";
|
||||
import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
@@ -15,10 +14,9 @@ import { SettingsCard } from "../../settings/components/SettingsCard";
|
||||
import { ApiKeyList } from "./components/ApiKeyList";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const [session, environment, product, organization] = await Promise.all([
|
||||
const [session, environment, organization] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
getEnvironment(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
@@ -35,7 +33,6 @@ const Page = async ({ params }) => {
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isViewer } = getAccessFlags(currentUserMembership?.role);
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return !isViewer ? (
|
||||
<PageContentWrapper>
|
||||
@@ -44,7 +41,6 @@ const Page = async ({ params }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="api-keys"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
<EnvironmentNotice environmentId={environment.id} subPageUrl="/product/api-keys" />
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
|
||||
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { TProductConfigChannel } from "@formbricks/types/product";
|
||||
import { SecondaryNavigation } from "@formbricks/ui/components/SecondaryNavigation";
|
||||
|
||||
interface ProductConfigNavigationProps {
|
||||
environmentId: string;
|
||||
activeId: string;
|
||||
isMultiLanguageAllowed: boolean;
|
||||
productChannel: TProductConfigChannel;
|
||||
}
|
||||
|
||||
export const ProductConfigNavigation = ({
|
||||
environmentId,
|
||||
activeId,
|
||||
isMultiLanguageAllowed,
|
||||
productChannel,
|
||||
}: ProductConfigNavigationProps) => {
|
||||
const pathname = usePathname();
|
||||
let navigation = [
|
||||
@@ -56,21 +53,12 @@ export const ProductConfigNavigation = ({
|
||||
href: `/environments/${environmentId}/product/api-keys`,
|
||||
current: pathname?.includes("/api-keys"),
|
||||
},
|
||||
{
|
||||
id: "website-connection",
|
||||
label: "Website Connection",
|
||||
icon: <ListChecksIcon className="h-5 w-5" />,
|
||||
href: `/environments/${environmentId}/product/website-connection`,
|
||||
current: pathname?.includes("/website-connection"),
|
||||
hidden: !!(productChannel && productChannel !== "website"),
|
||||
},
|
||||
{
|
||||
id: "app-connection",
|
||||
label: "App Connection",
|
||||
label: "Website & App Connection",
|
||||
icon: <ListChecksIcon className="h-5 w-5" />,
|
||||
href: `/environments/${environmentId}/product/app-connection`,
|
||||
current: pathname?.includes("/app-connection"),
|
||||
hidden: !!(productChannel && productChannel !== "app"),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
}
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
@@ -52,20 +51,17 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="general"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
|
||||
<SettingsCard title="Product Name" description="Change your products name.">
|
||||
<EditProductNameForm product={product} isProductNameEditDisabled={isProductNameEditDisabled} />
|
||||
</SettingsCard>
|
||||
{currentProductChannel !== "link" && (
|
||||
<SettingsCard
|
||||
title="Recontact Waiting Time"
|
||||
description="Control how frequently users can be surveyed across all surveys.">
|
||||
<EditWaitingTimeForm product={product} />
|
||||
</SettingsCard>
|
||||
)}
|
||||
<SettingsCard
|
||||
title="Recontact Waiting Time"
|
||||
description="Control how frequently users can be surveyed across all app surveys.">
|
||||
<EditWaitingTimeForm product={product} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Delete Product"
|
||||
description="Delete product with all surveys, responses, people, actions and attributes. This cannot be undone.">
|
||||
|
||||
@@ -26,7 +26,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
if (!isMultiLanguageAllowed) {
|
||||
notFound();
|
||||
}
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
@@ -35,7 +34,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="languages"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
|
||||
@@ -9,7 +9,7 @@ import { UpgradePlanNotice } from "@formbricks/ui/components/UpgradePlanNotice";
|
||||
import { updateProductAction } from "../../actions";
|
||||
|
||||
interface EditFormbricksBrandingProps {
|
||||
type: "linkSurvey" | "inAppSurvey";
|
||||
type: "linkSurvey" | "appSurvey";
|
||||
product: TProduct;
|
||||
canRemoveBranding: boolean;
|
||||
environmentId: string;
|
||||
@@ -53,7 +53,7 @@ export const EditFormbricksBranding = ({
|
||||
disabled={!canRemoveBranding || updatingBranding}
|
||||
/>
|
||||
<Label htmlFor={`branding-${type}`}>
|
||||
Show Formbricks Branding in {type === "linkSurvey" ? "Link" : "In-App"} Surveys
|
||||
Show Formbricks Branding in {type === "linkSurvey" ? "Link" : "App"} Surveys
|
||||
</Label>
|
||||
</div>
|
||||
{!canRemoveBranding && (
|
||||
|
||||
@@ -108,7 +108,7 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
setQuestionId(survey?.questions[0]?.id);
|
||||
};
|
||||
|
||||
const isAppSurvey = previewType === "app" || previewType === "website";
|
||||
const isAppSurvey = previewType === "app";
|
||||
|
||||
const scrollToEditLogoSection = () => {
|
||||
const editLogoSection = document.getElementById("edit-logo");
|
||||
@@ -117,8 +117,6 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
}
|
||||
};
|
||||
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-items-center overflow-hidden">
|
||||
<motion.div
|
||||
@@ -210,13 +208,11 @@ export const ThemeStylingPreviewSurvey = ({
|
||||
Link survey
|
||||
</div>
|
||||
|
||||
{currentProductChannel !== "link" && (
|
||||
<div
|
||||
className={`${isAppSurvey ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
|
||||
onClick={() => setPreviewType("app")}>
|
||||
App / Website survey
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${isAppSurvey ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
|
||||
onClick={() => setPreviewType("app")}>
|
||||
App survey
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
}
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
@@ -57,7 +56,6 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="look"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
@@ -74,13 +72,11 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
<SettingsCard title="Logo" description="Upload your company logo to brand surveys and link previews.">
|
||||
<EditLogo product={product} environmentId={params.environmentId} isViewer={isViewer} />
|
||||
</SettingsCard>
|
||||
{currentProductChannel !== "link" && (
|
||||
<SettingsCard
|
||||
title="In-app Survey Placement"
|
||||
description="Change where surveys will be shown in your web app.">
|
||||
<EditPlacementForm product={product} environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
)}
|
||||
<SettingsCard
|
||||
title="App Survey Placement"
|
||||
description="Change where surveys will be shown in your web app or website.">
|
||||
<EditPlacementForm product={product} environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Formbricks Branding"
|
||||
description="We love your support but understand if you toggle it off.">
|
||||
@@ -91,14 +87,12 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
canRemoveBranding={canRemoveLinkBranding}
|
||||
environmentId={params.environmentId}
|
||||
/>
|
||||
{currentProductChannel !== "link" && (
|
||||
<EditFormbricksBranding
|
||||
type="inAppSurvey"
|
||||
product={product}
|
||||
canRemoveBranding={canRemoveInAppBranding}
|
||||
environmentId={params.environmentId}
|
||||
/>
|
||||
)}
|
||||
<EditFormbricksBranding
|
||||
type="appSurvey"
|
||||
product={product}
|
||||
canRemoveBranding={canRemoveInAppBranding}
|
||||
environmentId={params.environmentId}
|
||||
/>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</PageContentWrapper>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
|
||||
import { getTagsOnResponsesCount } from "@formbricks/lib/tagOnResponse/service";
|
||||
import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent";
|
||||
@@ -21,10 +20,9 @@ const Page = async ({ params }) => {
|
||||
throw new Error("Environment not found");
|
||||
}
|
||||
|
||||
const [tags, environmentTagsCount, product, organization, session] = await Promise.all([
|
||||
const [tags, environmentTagsCount, organization, session] = await Promise.all([
|
||||
getTagsByEnvironmentId(params.environmentId),
|
||||
getTagsOnResponsesCount(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getServerSession(authOptions),
|
||||
]);
|
||||
@@ -45,7 +43,6 @@ const Page = async ({ params }) => {
|
||||
const isTagSettingDisabled = isViewer;
|
||||
|
||||
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
|
||||
const currentProductChannel = product?.config.channel ?? null;
|
||||
|
||||
return !isTagSettingDisabled ? (
|
||||
<PageContentWrapper>
|
||||
@@ -54,7 +51,6 @@ const Page = async ({ params }) => {
|
||||
environmentId={params.environmentId}
|
||||
activeId="tags"
|
||||
isMultiLanguageAllowed={isMultiLanguageAllowed}
|
||||
productChannel={currentProductChannel}
|
||||
/>
|
||||
</PageHeader>
|
||||
<SettingsCard title="Manage Tags" description="Merge and remove response tags.">
|
||||
|
||||
@@ -5,10 +5,9 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
|
||||
interface TEmptyAppSurveysProps {
|
||||
environment: TEnvironment;
|
||||
surveyType?: "app" | "website";
|
||||
}
|
||||
|
||||
export const EmptyAppSurveys = ({ environment, surveyType = "app" }: TEmptyAppSurveysProps) => {
|
||||
export const EmptyAppSurveys = ({ environment }: TEmptyAppSurveysProps) => {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center gap-8 bg-slate-100 py-12">
|
||||
<div className="flex h-20 w-20 items-center justify-center rounded-full border border-slate-200 bg-white">
|
||||
@@ -19,10 +18,10 @@ export const EmptyAppSurveys = ({ environment, surveyType = "app" }: TEmptyAppSu
|
||||
<h1 className="text-xl font-semibold text-slate-900">You're not plugged in yet!</h1>
|
||||
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
Connect your {surveyType} with Formbricks to run {surveyType} surveys.
|
||||
Connect your website or app with Formbricks to get started.
|
||||
</p>
|
||||
|
||||
<Link className="mt-2" href={`/environments/${environment.id}/product/${surveyType}-connection`}>
|
||||
<Link className="mt-2" href={`/environments/${environment.id}/product/app-connection`}>
|
||||
<Button size="sm" className="flex w-[120px] justify-center">
|
||||
Connect
|
||||
</Button>
|
||||
|
||||
@@ -16,9 +16,8 @@ export const SuccessMessage = ({ environment, survey }: SummaryMetadataProps) =>
|
||||
const searchParams = useSearchParams();
|
||||
const [confetti, setConfetti] = useState(false);
|
||||
|
||||
const isAppSurvey = survey.type === "app" || survey.type === "website";
|
||||
const widgetSetupCompleted =
|
||||
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
|
||||
const isAppSurvey = survey.type === "app";
|
||||
const widgetSetupCompleted = environment.appSetupCompleted;
|
||||
|
||||
useEffect(() => {
|
||||
const newSurveyParam = searchParams?.get("success");
|
||||
|
||||
@@ -50,8 +50,6 @@ export const SummaryList = ({
|
||||
attributeClasses,
|
||||
}: SummaryListProps) => {
|
||||
const { setSelectedFilter, selectedFilter } = useResponseFilter();
|
||||
const widgetSetupCompleted =
|
||||
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
|
||||
|
||||
const setFilter = (
|
||||
questionId: string,
|
||||
@@ -107,10 +105,8 @@ export const SummaryList = ({
|
||||
|
||||
return (
|
||||
<div className="mt-10 space-y-8">
|
||||
{(survey.type === "app" || survey.type === "website") &&
|
||||
responseCount === 0 &&
|
||||
!widgetSetupCompleted ? (
|
||||
<EmptyAppSurveys environment={environment} surveyType={survey.type} />
|
||||
{survey.type === "app" && responseCount === 0 && !environment.appSetupCompleted ? (
|
||||
<EmptyAppSurveys environment={environment} />
|
||||
) : summary.length === 0 ? (
|
||||
<SkeletonLoader type="summary" />
|
||||
) : responseCount === 0 ? (
|
||||
@@ -119,7 +115,6 @@ export const SummaryList = ({
|
||||
environment={environment}
|
||||
noWidgetRequired={survey.type === "link"}
|
||||
emptyMessage={totalResponseCount === 0 ? undefined : "No response matches your filter"}
|
||||
widgetSetupCompleted={widgetSetupCompleted}
|
||||
/>
|
||||
) : (
|
||||
summary.map((questionSummary) => {
|
||||
|
||||
@@ -30,9 +30,7 @@ export const SurveyAnalysisCTA = ({
|
||||
const router = useRouter();
|
||||
|
||||
const [showShareSurveyModal, setShowShareSurveyModal] = useState(searchParams.get("share") === "true");
|
||||
|
||||
const widgetSetupCompleted =
|
||||
survey.type === "app" ? environment.appSetupCompleted : environment.websiteSetupCompleted;
|
||||
const widgetSetupCompleted = environment.appSetupCompleted;
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("share") === "true") {
|
||||
|
||||
@@ -65,9 +65,7 @@ export const SurveyStatusDropdown = ({
|
||||
<SelectTrigger className="w-[170px] bg-white md:w-[200px]">
|
||||
<SelectValue>
|
||||
<div className="flex items-center">
|
||||
{(survey.type === "link" ||
|
||||
environment.appSetupCompleted ||
|
||||
environment.websiteSetupCompleted) && (
|
||||
{(survey.type === "link" || environment.appSetupCompleted) && (
|
||||
<SurveyStatusIndicator status={survey.status} />
|
||||
)}
|
||||
<span className="ml-2 text-sm text-slate-700">
|
||||
|
||||
@@ -7,11 +7,8 @@ export const GET = async (_: NextRequest, { params }: { params: { package: strin
|
||||
const packageRequested = params.package;
|
||||
|
||||
switch (packageRequested) {
|
||||
case "app":
|
||||
path = `../../packages/js-core/dist/app.umd.cjs`;
|
||||
break;
|
||||
case "website":
|
||||
path = `../../packages/js-core/dist/website.umd.cjs`;
|
||||
case "js":
|
||||
path = `../../packages/js-core/dist/index.umd.cjs`;
|
||||
break;
|
||||
case "surveys":
|
||||
path = `../../packages/surveys/dist/index.umd.cjs`;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { productCache } from "@formbricks/lib/product/cache";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TJsEnvironmentState } from "@formbricks/types/js";
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,6 @@ import { TJsEnvironmentState } from "@formbricks/types/js";
|
||||
* @param environmentId
|
||||
* @returns The environment state
|
||||
* @throws ResourceNotFoundError if the environment or organization does not exist
|
||||
* @throws InvalidInputError if the channel is not "app"
|
||||
*/
|
||||
export const getEnvironmentState = async (
|
||||
environmentId: string
|
||||
@@ -52,10 +51,6 @@ export const getEnvironmentState = async (
|
||||
throw new ResourceNotFoundError("product", null);
|
||||
}
|
||||
|
||||
if (product.config.channel && product.config.channel !== "app") {
|
||||
throw new InvalidInputError("Invalid channel");
|
||||
}
|
||||
|
||||
if (!environment.appSetupCompleted) {
|
||||
await Promise.all([
|
||||
prisma.environment.update({
|
||||
@@ -117,7 +112,7 @@ export const getEnvironmentState = async (
|
||||
revalidateEnvironment,
|
||||
};
|
||||
},
|
||||
[`environmentState-app-${environmentId}`],
|
||||
[`environmentState-${environmentId}`],
|
||||
{
|
||||
...(IS_FORMBRICKS_CLOUD && { revalidate: 24 * 60 * 60 }),
|
||||
tags: [
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getEnvironmentState } from "@/app/api/v1/client/[environmentId]/app/environment/lib/environmentState";
|
||||
import { getEnvironmentState } from "@/app/api/v1/client/[environmentId]/environment/lib/environmentState";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextRequest } from "next/server";
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getPersonSegmentIds } from "@/app/api/v1/client/[environmentId]/app/people/[userId]/lib/segments";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { attributeCache } from "@formbricks/lib/attribute/cache";
|
||||
import { getAttributesByUserId } from "@formbricks/lib/attribute/service";
|
||||
@@ -17,6 +16,7 @@ import { getResponsesByUserId } from "@formbricks/lib/response/service";
|
||||
import { segmentCache } from "@formbricks/lib/segment/cache";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TJsPersonState } from "@formbricks/types/js";
|
||||
import { getPersonSegmentIds } from "./segments";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,126 +0,0 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { actionClassCache } from "@formbricks/lib/actionClass/cache";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { environmentCache } from "@formbricks/lib/environment/cache";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { organizationCache } from "@formbricks/lib/organization/cache";
|
||||
import {
|
||||
getMonthlyOrganizationResponseCount,
|
||||
getOrganizationByEnvironmentId,
|
||||
} from "@formbricks/lib/organization/service";
|
||||
import {
|
||||
capturePosthogEnvironmentEvent,
|
||||
sendPlanLimitsReachedEventToPosthogWeekly,
|
||||
} from "@formbricks/lib/posthogServer";
|
||||
import { productCache } from "@formbricks/lib/product/cache";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TJsEnvironmentState } from "@formbricks/types/js";
|
||||
|
||||
/**
|
||||
* Get the environment state
|
||||
* @param environmentId
|
||||
* @returns The environment state
|
||||
* @throws ResourceNotFoundError if the organization, environment or product is not found
|
||||
* @throws InvalidInputError if the product channel is not website
|
||||
*/
|
||||
export const getEnvironmentState = async (
|
||||
environmentId: string
|
||||
): Promise<{ state: TJsEnvironmentState["data"]; revalidateEnvironment?: boolean }> =>
|
||||
cache(
|
||||
async () => {
|
||||
let revalidateEnvironment = false;
|
||||
const [environment, organization, product] = await Promise.all([
|
||||
getEnvironment(environmentId),
|
||||
getOrganizationByEnvironmentId(environmentId),
|
||||
getProductByEnvironmentId(environmentId),
|
||||
]);
|
||||
|
||||
if (!environment) {
|
||||
throw new ResourceNotFoundError("environment", environmentId);
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new ResourceNotFoundError("organization", null);
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
throw new ResourceNotFoundError("product", null);
|
||||
}
|
||||
|
||||
if (product.config.channel && product.config.channel !== "website") {
|
||||
throw new InvalidInputError("Product channel is not website");
|
||||
}
|
||||
|
||||
// check if response limit is reached
|
||||
let isWebsiteSurveyResponseLimitReached = false;
|
||||
if (IS_FORMBRICKS_CLOUD) {
|
||||
const currentResponseCount = await getMonthlyOrganizationResponseCount(organization.id);
|
||||
const monthlyResponseLimit = organization.billing.limits.monthly.responses;
|
||||
|
||||
isWebsiteSurveyResponseLimitReached =
|
||||
monthlyResponseLimit !== null && currentResponseCount >= monthlyResponseLimit;
|
||||
|
||||
if (isWebsiteSurveyResponseLimitReached) {
|
||||
try {
|
||||
await sendPlanLimitsReachedEventToPosthogWeekly(environmentId, {
|
||||
plan: organization.billing.plan,
|
||||
limits: { monthly: { responses: monthlyResponseLimit, miu: null } },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!environment?.websiteSetupCompleted) {
|
||||
await Promise.all([
|
||||
await prisma.environment.update({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
data: { websiteSetupCompleted: true },
|
||||
}),
|
||||
capturePosthogEnvironmentEvent(environmentId, "website setup completed"),
|
||||
]);
|
||||
|
||||
revalidateEnvironment = true;
|
||||
}
|
||||
|
||||
const [surveys, actionClasses] = await Promise.all([
|
||||
getSurveys(environmentId),
|
||||
getActionClasses(environmentId),
|
||||
]);
|
||||
|
||||
// Common filter condition for selecting surveys that are in progress, are of type 'website' and have no active segment filtering.
|
||||
const filteredSurveys = surveys.filter(
|
||||
(survey) => survey.status === "inProgress" && survey.type === "website"
|
||||
);
|
||||
|
||||
const state: TJsEnvironmentState["data"] = {
|
||||
surveys: filteredSurveys,
|
||||
actionClasses,
|
||||
product,
|
||||
};
|
||||
|
||||
return {
|
||||
state,
|
||||
revalidateEnvironment,
|
||||
};
|
||||
},
|
||||
[`environmentState-website-${environmentId}`],
|
||||
{
|
||||
...(IS_FORMBRICKS_CLOUD && { revalidate: 24 * 60 * 60 }),
|
||||
tags: [
|
||||
environmentCache.tag.byId(environmentId),
|
||||
organizationCache.tag.byEnvironmentId(environmentId),
|
||||
productCache.tag.byEnvironmentId(environmentId),
|
||||
surveyCache.tag.byEnvironmentId(environmentId),
|
||||
actionClassCache.tag.byEnvironmentId(environmentId),
|
||||
],
|
||||
}
|
||||
)();
|
||||
@@ -1,59 +0,0 @@
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextRequest } from "next/server";
|
||||
import { environmentCache } from "@formbricks/lib/environment/cache";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZJsSyncInput } from "@formbricks/types/js";
|
||||
import { getEnvironmentState } from "./lib/environmentState";
|
||||
|
||||
export const OPTIONS = async (): Promise<Response> => {
|
||||
return responses.successResponse({}, true);
|
||||
};
|
||||
|
||||
export const GET = async (
|
||||
_: NextRequest,
|
||||
{ params }: { params: { environmentId: string } }
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
const syncInputValidation = ZJsSyncInput.safeParse({
|
||||
environmentId: params.environmentId,
|
||||
});
|
||||
|
||||
if (!syncInputValidation.success) {
|
||||
return responses.badRequestResponse(
|
||||
"Fields are missing or incorrectly formatted",
|
||||
transformErrorToDetails(syncInputValidation.error),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const { environmentId } = syncInputValidation.data;
|
||||
|
||||
try {
|
||||
const environmentState = await getEnvironmentState(environmentId);
|
||||
|
||||
if (environmentState.revalidateEnvironment) {
|
||||
environmentCache.revalidate({
|
||||
id: syncInputValidation.data.environmentId,
|
||||
productId: environmentState.state.product.id,
|
||||
});
|
||||
}
|
||||
|
||||
return responses.successResponse(
|
||||
environmentState.state,
|
||||
true,
|
||||
"public, s-maxage=600, max-age=840, stale-while-revalidate=600, stale-if-error=600"
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof ResourceNotFoundError) {
|
||||
return responses.notFoundResponse(err.resourceType, err.resourceId);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
return responses.internalServerErrorResponse(err.message ?? "Unable to complete response", true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return responses.internalServerErrorResponse(`Unable to complete response: ${error.message}`, true);
|
||||
}
|
||||
};
|
||||
@@ -24,7 +24,6 @@ export const GET = async () => {
|
||||
},
|
||||
},
|
||||
appSetupCompleted: true,
|
||||
websiteSetupCompleted: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import formbricks from "@formbricks/js/app";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
|
||||
export const formbricksEnabled =
|
||||
|
||||
@@ -165,6 +165,30 @@ const nextConfig = {
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/api/packages/website",
|
||||
destination: "/api/packages/js",
|
||||
},
|
||||
{
|
||||
source: "/api/packages/app",
|
||||
destination: "/api/packages/js",
|
||||
},
|
||||
{
|
||||
source: "/api/v1/client/:environmentId/website/environment",
|
||||
destination: "/api/v1/client/:environmentId/environment",
|
||||
},
|
||||
{
|
||||
source: "/api/v1/client/:environmentId/app/environment",
|
||||
destination: "/api/v1/client/:environmentId/environment",
|
||||
},
|
||||
{
|
||||
source: "/api/v1/client/:environmentId/app/people/:userId",
|
||||
destination: "/api/v1/client/:environmentId/identify/people/:userId",
|
||||
},
|
||||
];
|
||||
},
|
||||
env: {
|
||||
NEXTAUTH_URL: process.env.WEBAPP_URL,
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ test.describe("JS Package Test", async () => {
|
||||
// Formbricks In App Sync has happened
|
||||
const syncApi = await page.waitForResponse(
|
||||
(response) => {
|
||||
return response.url().includes("/app/environment");
|
||||
return response.url().includes("/environment");
|
||||
},
|
||||
{
|
||||
timeout: 120000,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/* eslint-disable no-console -- logging is allowed in migration scripts */
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const TRANSACTION_TIMEOUT = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||
|
||||
async function runMigration(): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
console.log("Starting data migration...");
|
||||
|
||||
await prisma.$transaction(
|
||||
async (transactionPrisma) => {
|
||||
const websiteSurveys = await transactionPrisma.survey.findMany({
|
||||
where: { type: "website" },
|
||||
});
|
||||
|
||||
const updationPromises = [];
|
||||
|
||||
for (const websiteSurvey of websiteSurveys) {
|
||||
updationPromises.push(
|
||||
transactionPrisma.survey.update({
|
||||
where: { id: websiteSurvey.id },
|
||||
data: {
|
||||
type: "app",
|
||||
segment: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
environmentId_title: {
|
||||
environmentId: websiteSurvey.environmentId,
|
||||
title: websiteSurvey.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
title: websiteSurvey.id,
|
||||
isPrivate: true,
|
||||
environmentId: websiteSurvey.environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(updationPromises);
|
||||
console.log(`Updated ${websiteSurveys.length.toString()} website surveys to app surveys`);
|
||||
},
|
||||
{
|
||||
timeout: TRANSACTION_TIMEOUT,
|
||||
}
|
||||
);
|
||||
|
||||
const endTime = Date.now();
|
||||
console.log(`Data migration completed. Total time: ${((endTime - startTime) / 1000).toFixed(2)}s`);
|
||||
}
|
||||
|
||||
function handleError(error: unknown): void {
|
||||
console.error("An error occurred during migration:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function handleDisconnectError(): void {
|
||||
console.error("Failed to disconnect Prisma client");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
runMigration()
|
||||
.catch(handleError)
|
||||
.finally(() => {
|
||||
prisma.$disconnect().catch(handleDisconnectError);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `websiteSetupCompleted` on the `Environment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Environment" DROP COLUMN "websiteSetupCompleted";
|
||||
@@ -51,7 +51,8 @@
|
||||
"data-migration:add-display-id-to-response": "ts-node ./data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts",
|
||||
"data-migration:address-question": "ts-node ./data-migrations/20240924123456_migrate_address_question/data-migration.ts",
|
||||
"data-migration:advanced-logic": "ts-node ./data-migrations/20240828122408_advanced_logic_editor/data-migration.ts",
|
||||
"data-migration:segments-actions-cleanup": "ts-node ./data-migrations/20240904091113_removed_actions_table/data-migration.ts"
|
||||
"data-migration:segments-actions-cleanup": "ts-node ./data-migrations/20240904091113_removed_actions_table/data-migration.ts",
|
||||
"data-migration:migrate-survey-types": "ts-node ./data-migrations/20241002123456_migrate_survey_types/data-migration.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.18.0",
|
||||
|
||||
@@ -386,24 +386,23 @@ 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)
|
||||
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[]
|
||||
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)
|
||||
surveys Survey[]
|
||||
people Person[]
|
||||
actionClasses ActionClass[]
|
||||
attributeClasses AttributeClass[]
|
||||
apiKeys ApiKey[]
|
||||
webhooks Webhook[]
|
||||
tags Tag[]
|
||||
segments Segment[]
|
||||
integration Integration[]
|
||||
|
||||
@@index([productId])
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
TSegmentUpdateInput,
|
||||
} from "@formbricks/types/segment";
|
||||
import type { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { Alert, AlertDescription } from "@formbricks/ui/components/Alert";
|
||||
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { LoadSegmentModal } from "@formbricks/ui/components/LoadSegmentModal";
|
||||
@@ -161,7 +162,7 @@ export function AdvancedTargetingCard({
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
className="w-full rounded-lg border border-slate-300 bg-white"
|
||||
className="w-full overflow-hidden rounded-lg border border-slate-300 bg-white"
|
||||
onOpenChange={setOpen}
|
||||
open={open}>
|
||||
<Collapsible.CollapsibleTrigger
|
||||
@@ -415,6 +416,23 @@ export function AdvancedTargetingCard({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Alert className="flex items-center rounded-none bg-slate-50">
|
||||
<AlertDescription className="ml-2">
|
||||
<span className="mr-1 text-slate-600">
|
||||
User targeting is currently only available when{" "}
|
||||
<Link
|
||||
href="https://formbricks.com//docs/app-surveys/user-identification"
|
||||
target="blank"
|
||||
className="underline">
|
||||
identifying users
|
||||
</Link>{" "}
|
||||
with the Formbricks SDK.
|
||||
</span>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
|
||||
@@ -20,33 +20,20 @@
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
"./app": {
|
||||
"import": "./dist/app.js",
|
||||
"require": "./dist/app.umd.cjs",
|
||||
"types": "./dist/app.d.ts"
|
||||
},
|
||||
"./website": {
|
||||
"import": "./dist/website.js",
|
||||
"require": "./dist/website.umd.cjs",
|
||||
"types": "./dist/website.d.ts"
|
||||
},
|
||||
"./*": "./dist/*"
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"app": [
|
||||
"./dist/app.d.ts"
|
||||
],
|
||||
"website": [
|
||||
"./dist/website.d.ts"
|
||||
"*": [
|
||||
"./dist/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode dev",
|
||||
"build:app": "tsc && vite build --config app.vite.config.ts",
|
||||
"build:website": "tsc && vite build --config website.vite.config.ts",
|
||||
"build": "pnpm build:app && pnpm build:website",
|
||||
"build": "tsc && vite build",
|
||||
"build:dev": "tsc && vite build --mode dev",
|
||||
"go": "vite build --watch --mode dev",
|
||||
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { NetworkError, Result, err, okVoid } from "../../shared/errors";
|
||||
import { Logger } from "../../shared/logger";
|
||||
import { AppConfig } from "./config";
|
||||
import { deinitalize, initialize } from "./initialize";
|
||||
import { closeSurvey } from "./widget";
|
||||
|
||||
const appConfig = AppConfig.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
|
||||
export const logoutPerson = async (): Promise<void> => {
|
||||
deinitalize();
|
||||
appConfig.resetConfig();
|
||||
};
|
||||
|
||||
export const resetPerson = async (): Promise<Result<void, NetworkError>> => {
|
||||
logger.debug("Resetting state & getting new state from backend");
|
||||
closeSurvey();
|
||||
|
||||
const userId = appConfig.get().personState.data.userId;
|
||||
if (!userId) {
|
||||
return err({
|
||||
code: "network_error",
|
||||
status: 500,
|
||||
message: "Missing userId",
|
||||
url: `${appConfig.get().apiHost}/api/v1/client/${appConfig.get().environmentId}/people/${userId}/attributes`,
|
||||
responseMessage: "Missing userId",
|
||||
});
|
||||
}
|
||||
|
||||
const syncParams = {
|
||||
environmentId: appConfig.get().environmentId,
|
||||
apiHost: appConfig.get().apiHost,
|
||||
userId,
|
||||
attributes: appConfig.get().personState.data.attributes,
|
||||
};
|
||||
await logoutPerson();
|
||||
try {
|
||||
await initialize(syncParams);
|
||||
return okVoid();
|
||||
} catch (e) {
|
||||
return err(e as NetworkError);
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TJsAppConfigInput, TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { CommandQueue } from "../shared/commandQueue";
|
||||
import { ErrorHandler } from "../shared/errors";
|
||||
import { Logger } from "../shared/logger";
|
||||
import { TJsConfigInput, TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { trackCodeAction } from "./lib/actions";
|
||||
import { getApi } from "./lib/api";
|
||||
import { setAttributeInApp } from "./lib/attributes";
|
||||
import { CommandQueue } from "./lib/commandQueue";
|
||||
import { ErrorHandler } from "./lib/errors";
|
||||
import { initialize } from "./lib/initialize";
|
||||
import { Logger } from "./lib/logger";
|
||||
import { checkPageUrl } from "./lib/noCodeActions";
|
||||
import { logoutPerson, resetPerson } from "./lib/person";
|
||||
|
||||
@@ -14,9 +14,9 @@ const logger = Logger.getInstance();
|
||||
logger.debug("Create command queue");
|
||||
const queue = new CommandQueue();
|
||||
|
||||
const init = async (initConfig: TJsAppConfigInput) => {
|
||||
const init = async (initConfig: TJsConfigInput) => {
|
||||
ErrorHandler.init(initConfig.errorHandler);
|
||||
queue.add(false, "app", initialize, initConfig);
|
||||
queue.add(false, initialize, initConfig);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
@@ -26,27 +26,27 @@ const setEmail = async (email: string): Promise<void> => {
|
||||
};
|
||||
|
||||
const setAttribute = async (key: string, value: any): Promise<void> => {
|
||||
queue.add(true, "app", setAttributeInApp, key, value);
|
||||
queue.add(true, setAttributeInApp, key, value);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
queue.add(true, "app", logoutPerson);
|
||||
queue.add(true, logoutPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const reset = async (): Promise<void> => {
|
||||
queue.add(true, "app", resetPerson);
|
||||
queue.add(true, resetPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const track = async (name: string, properties?: TJsTrackProperties): Promise<void> => {
|
||||
queue.add<any>(true, "app", trackCodeAction, name, properties);
|
||||
queue.add<any>(true, trackCodeAction, name, properties);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const registerRouteChange = async (): Promise<void> => {
|
||||
queue.add(true, "app", checkPageUrl);
|
||||
queue.add(true, checkPageUrl);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { InvalidCodeError, NetworkError, Result, err, okVoid } from "../../shared/errors";
|
||||
import { Logger } from "../../shared/logger";
|
||||
import { AppConfig } from "./config";
|
||||
import { Config } from "./config";
|
||||
import { InvalidCodeError, NetworkError, Result, err, okVoid } from "./errors";
|
||||
import { Logger } from "./logger";
|
||||
import { triggerSurvey } from "./widget";
|
||||
|
||||
const logger = Logger.getInstance();
|
||||
const appConfig = AppConfig.getInstance();
|
||||
const config = Config.getInstance();
|
||||
|
||||
export const trackAction = async (
|
||||
name: string,
|
||||
@@ -17,7 +17,7 @@ export const trackAction = async (
|
||||
logger.debug(`Formbricks: Action "${aliasName}" tracked`);
|
||||
|
||||
// get a list of surveys that are collecting insights
|
||||
const activeSurveys = appConfig.get().filteredSurveys;
|
||||
const activeSurveys = config.get().filteredSurveys;
|
||||
|
||||
if (!!activeSurveys && activeSurveys.length > 0) {
|
||||
for (const survey of activeSurveys) {
|
||||
@@ -38,7 +38,7 @@ export const trackCodeAction = (
|
||||
code: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<Result<void, NetworkError>> | Result<void, InvalidCodeError> => {
|
||||
const actionClasses = appConfig.get().environmentState.data.actionClasses;
|
||||
const actionClasses = config.get().environmentState.data.actionClasses;
|
||||
|
||||
const codeActionClasses = actionClasses.filter((action) => action.type === "code");
|
||||
const action = codeActionClasses.find((action) => action.key === code);
|
||||