mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
feat: Add hiddenFields to app & website surveys (#2628)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
BIN
apps/docs/app/global/hidden-fields/filled-hidden-fields.webp
Normal file
BIN
apps/docs/app/global/hidden-fields/filled-hidden-fields.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
BIN
apps/docs/app/global/hidden-fields/hidden-field-responses.webp
Normal file
BIN
apps/docs/app/global/hidden-fields/hidden-field-responses.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/docs/app/global/hidden-fields/hidden-fields.webp
Normal file
BIN
apps/docs/app/global/hidden-fields/hidden-fields.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
apps/docs/app/global/hidden-fields/input-hidden-fields.webp
Normal file
BIN
apps/docs/app/global/hidden-fields/input-hidden-fields.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
79
apps/docs/app/global/hidden-fields/page.mdx
Normal file
79
apps/docs/app/global/hidden-fields/page.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import FilledHiddenFields from "./filled-hidden-fields.webp";
|
||||
import HiddenFieldResponses from "./hidden-field-responses.webp";
|
||||
import HiddenFields from "./hidden-fields.webp";
|
||||
import InputHiddenFields from "./input-hidden-fields.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Hidden Fields",
|
||||
description: "Add hidden fields to your surveys to capture additional data without requiring user inputs!",
|
||||
};
|
||||
|
||||
# Hidden Fields
|
||||
|
||||
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
|
||||
|
||||
1. Edit the survey you want to add hidden fields to & switch to the Questions tab and scroll down to the bottom of the page. You will see a section called **Hidden Fields**. Make sure to enable it by toggling the switch.
|
||||
|
||||
<MdxImage
|
||||
src={HiddenFields}
|
||||
alt="Enable Hidden Fields"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Now click on it to add a new hidden field ID. You can add as many hidden fields as you want.
|
||||
|
||||
<MdxImage
|
||||
src={InputHiddenFields}
|
||||
alt="Add Hidden Fields"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
<MdxImage
|
||||
src={FilledHiddenFields}
|
||||
alt="Filled Hidden Fields"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
### Set Hidden Field
|
||||
|
||||
<Col>
|
||||
<CodeGroup title="Example Screen from which the User filled it">
|
||||
|
||||
```sh
|
||||
formbricks.track("my event", {
|
||||
hiddenFields: {
|
||||
screen: "landing_page",
|
||||
job: "Founder"
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
|
||||
## View Hidden Fields in Responses
|
||||
|
||||
These hidden fields will now be visible in the responses tab just like other fields in the Summary as well as the Response Cards, and you can use them to filter and analyze your responses.
|
||||
|
||||
<MdxImage
|
||||
src={HiddenFieldResponses}
|
||||
alt="Hidden Field Responses"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **User Metadata**: You can add hidden fields to capture user metadata such as user ID, email, or any other user-specific information.
|
||||
- **Survey Metadata**: You can add hidden fields to capture other metadata, e.g. the screen from which the survey was filled, or any other app specific information.
|
||||
@@ -4,7 +4,6 @@ import FilledHiddenFields from "./filled-hidden-fields.webp";
|
||||
import HiddenFieldResponses from "./hidden-field-responses.webp";
|
||||
import HiddenFields from "./hidden-fields.webp";
|
||||
import InputHiddenFields from "./input-hidden-fields.webp";
|
||||
import SettingsPage from "./settings.webp";
|
||||
|
||||
export const metadata = {
|
||||
title: "Hidden Fields",
|
||||
@@ -21,16 +20,7 @@ Hidden fields are a powerful feature in Formbricks that allows you to add data t
|
||||
|
||||
### Enable them in the Survey Builder
|
||||
|
||||
1. Edit the survey you want to add hidden fields to & open it's settings, make sure it's selected as a **Link Survey**.
|
||||
|
||||
<MdxImage
|
||||
src={SettingsPage}
|
||||
alt="Select the Survey Type as Link Survey"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
2. Switch to the Questions tab and scroll down to the bottom of the page. You will see a section called **Hidden Fields**. Make sure to enable it by toggling the switch.
|
||||
1. Edit the survey you want to add hidden fields to & switch to the Questions tab and scroll down to the bottom of the page. You will see a section called **Hidden Fields**. Make sure to enable it by toggling the switch.
|
||||
|
||||
<MdxImage
|
||||
src={HiddenFields}
|
||||
@@ -39,7 +29,7 @@ Hidden fields are a powerful feature in Formbricks that allows you to add data t
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
|
||||
3. Now click on it to add a new hidden field ID. You can add as many hidden fields as you want.
|
||||
2. Now click on it to add a new hidden field ID. You can add as many hidden fields as you want.
|
||||
|
||||
<MdxImage
|
||||
src={InputHiddenFields}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB |
@@ -35,6 +35,7 @@ export const navigation: Array<NavGroup> = [
|
||||
{ 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
|
||||
@@ -57,6 +58,7 @@ export const navigation: Array<NavGroup> = [
|
||||
{ title: "Actions & Targeting", href: "/website-surveys/actions-and-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
|
||||
|
||||
@@ -383,14 +383,12 @@ export const QuestionsView = ({
|
||||
attributeClasses={attributeClasses}
|
||||
/>
|
||||
|
||||
{localSurvey.type === "link" ? (
|
||||
<HiddenFieldsCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
activeQuestionId={activeQuestionId}
|
||||
/>
|
||||
) : null}
|
||||
<HiddenFieldsCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
activeQuestionId={activeQuestionId}
|
||||
/>
|
||||
|
||||
<MultiLanguageCard
|
||||
localSurvey={localSurvey}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ResponseQueue } from "@formbricks/lib/responseQueue";
|
||||
import { SurveyState } from "@formbricks/lib/surveyState";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TResponse, TResponseData, TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TResponse, TResponseHiddenFieldValue, TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { ClientLogo } from "@formbricks/ui/ClientLogo";
|
||||
@@ -123,20 +123,17 @@ export const LinkSurvey = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const hiddenFieldsRecord = useMemo<TResponseData | undefined>(() => {
|
||||
const fieldsRecord: TResponseData = {};
|
||||
let fieldsSet = false;
|
||||
const hiddenFieldsRecord = useMemo<TResponseHiddenFieldValue>(() => {
|
||||
const fieldsRecord: TResponseHiddenFieldValue = {};
|
||||
|
||||
survey.hiddenFields?.fieldIds?.forEach((field) => {
|
||||
const answer = searchParams?.get(field);
|
||||
if (answer) {
|
||||
fieldsRecord[field] = answer;
|
||||
fieldsSet = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Only return the record if at least one field was set.
|
||||
return fieldsSet ? fieldsRecord : undefined;
|
||||
return fieldsRecord;
|
||||
}, [searchParams, survey.hiddenFields?.fieldIds]);
|
||||
|
||||
const getVerifiedEmail = useMemo<Record<string, string> | null>(() => {
|
||||
@@ -255,7 +252,6 @@ export const LinkSurvey = ({
|
||||
responseQueue.add({
|
||||
data: {
|
||||
...responseUpdate.data,
|
||||
...hiddenFieldsRecord,
|
||||
...getVerifiedEmail,
|
||||
},
|
||||
ttc: responseUpdate.ttc,
|
||||
@@ -266,6 +262,7 @@ export const LinkSurvey = ({
|
||||
url: window.location.href,
|
||||
source: sourceParam || "",
|
||||
},
|
||||
...(Object.keys(hiddenFieldsRecord).length > 0 && { hiddenFields: hiddenFieldsRecord }),
|
||||
});
|
||||
}}
|
||||
onFileUpload={async (file: File, params: TUploadFileConfig) => {
|
||||
@@ -285,7 +282,6 @@ export const LinkSurvey = ({
|
||||
setQuestionId = f;
|
||||
}}
|
||||
startAtQuestionId={startAt && isStartAtValid ? startAt : undefined}
|
||||
hiddenFieldsRecord={hiddenFieldsRecord}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TJsAppConfigInput } from "@formbricks/types/js";
|
||||
import { TJsAppConfigInput, TJsTrackProperties } from "@formbricks/types/js";
|
||||
|
||||
import { CommandQueue } from "../shared/commandQueue";
|
||||
import { ErrorHandler } from "../shared/errors";
|
||||
@@ -41,7 +41,7 @@ const reset = async (): Promise<void> => {
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const track = async (name: string, properties: any = {}): Promise<void> => {
|
||||
const track = async (name: string, properties?: TJsTrackProperties): Promise<void> => {
|
||||
queue.add<any>(true, "app", trackCodeAction, name, properties);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
import { TJsActionInput } from "@formbricks/types/js";
|
||||
import { TJsActionInput, TJsTrackProperties } from "@formbricks/types/js";
|
||||
|
||||
import { InvalidCodeError, NetworkError, Result, err, okVoid } from "../../shared/errors";
|
||||
import { Logger } from "../../shared/logger";
|
||||
@@ -13,7 +13,11 @@ const inAppConfig = AppConfig.getInstance();
|
||||
|
||||
const intentsToNotCreateOnApp = ["Exit Intent (Desktop)", "50% Scroll"];
|
||||
|
||||
export const trackAction = async (name: string, alias?: string): Promise<Result<void, NetworkError>> => {
|
||||
export const trackAction = async (
|
||||
name: string,
|
||||
alias?: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<Result<void, NetworkError>> => {
|
||||
const aliasName = alias || name;
|
||||
const { userId } = inAppConfig.get();
|
||||
|
||||
@@ -71,7 +75,7 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
|
||||
for (const survey of activeSurveys) {
|
||||
for (const trigger of survey.triggers) {
|
||||
if (trigger.actionClass.name === name) {
|
||||
await triggerSurvey(survey, name);
|
||||
await triggerSurvey(survey, name, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +87,8 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
|
||||
};
|
||||
|
||||
export const trackCodeAction = (
|
||||
code: string
|
||||
code: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<Result<void, NetworkError>> | Result<void, InvalidCodeError> => {
|
||||
const {
|
||||
state: { actionClasses = [] },
|
||||
@@ -99,7 +104,7 @@ export const trackCodeAction = (
|
||||
});
|
||||
}
|
||||
|
||||
return trackAction(action.name, code);
|
||||
return trackAction(action.name, code, properties);
|
||||
};
|
||||
|
||||
export const trackNoCodeAction = (name: string): Promise<Result<void, NetworkError>> => {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { FormbricksAPI } from "@formbricks/api";
|
||||
import { ResponseQueue } from "@formbricks/lib/responseQueue";
|
||||
import { SurveyState } from "@formbricks/lib/surveyState";
|
||||
import { getStyling } from "@formbricks/lib/utils/styling";
|
||||
import { TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { TResponseHiddenFieldValue, TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
|
||||
import { ErrorHandler } from "../../shared/errors";
|
||||
import { Logger } from "../../shared/logger";
|
||||
import { getDefaultLanguageCode, getLanguageCode } from "../../shared/utils";
|
||||
import { getDefaultLanguageCode, getLanguageCode, handleHiddenFields } from "../../shared/utils";
|
||||
import { AppConfig } from "./config";
|
||||
import { putFormbricksInErrorState } from "./initialize";
|
||||
import { sync } from "./sync";
|
||||
@@ -30,7 +31,11 @@ const shouldDisplayBasedOnPercentage = (displayPercentage: number) => {
|
||||
return randomNum <= displayPercentage;
|
||||
};
|
||||
|
||||
export const triggerSurvey = async (survey: TSurvey, action?: string): Promise<void> => {
|
||||
export const triggerSurvey = async (
|
||||
survey: TSurvey,
|
||||
action?: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<void> => {
|
||||
// Check if the survey should be displayed based on displayPercentage
|
||||
if (survey.displayPercentage) {
|
||||
const shouldDisplaySurvey = shouldDisplayBasedOnPercentage(survey.displayPercentage);
|
||||
@@ -39,10 +44,20 @@ export const triggerSurvey = async (survey: TSurvey, action?: string): Promise<v
|
||||
return; // skip displaying the survey
|
||||
}
|
||||
}
|
||||
await renderWidget(survey, action);
|
||||
|
||||
const hiddenFieldsObject: TResponseHiddenFieldValue = handleHiddenFields(
|
||||
survey.hiddenFields,
|
||||
properties?.hiddenFields
|
||||
);
|
||||
|
||||
await renderWidget(survey, action, hiddenFieldsObject);
|
||||
};
|
||||
|
||||
const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
const renderWidget = async (
|
||||
survey: TSurvey,
|
||||
action?: string,
|
||||
hiddenFields: TResponseHiddenFieldValue = {}
|
||||
) => {
|
||||
if (isSurveyRunning) {
|
||||
logger.debug("A survey is already running. Skipping.");
|
||||
return;
|
||||
@@ -95,8 +110,8 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
|
||||
setTimeout(() => {
|
||||
formbricksSurveys.renderSurveyModal({
|
||||
survey: survey,
|
||||
isBrandingEnabled: isBrandingEnabled,
|
||||
survey,
|
||||
isBrandingEnabled,
|
||||
clickOutside,
|
||||
darkOverlay,
|
||||
languageCode,
|
||||
@@ -144,6 +159,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
url: window.location.href,
|
||||
action,
|
||||
},
|
||||
hiddenFields,
|
||||
});
|
||||
},
|
||||
onClose: closeSurvey,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { TAttributes } from "@formbricks/types/attributes";
|
||||
import { TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { TResponseHiddenFieldValue } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
|
||||
import { Logger } from "../shared/logger";
|
||||
|
||||
const logger = Logger.getInstance();
|
||||
|
||||
export const getIsDebug = () => window.location.search.includes("formbricksDebug=true");
|
||||
|
||||
export const getLanguageCode = (survey: TSurvey, attributes: TAttributes): string | undefined => {
|
||||
@@ -34,3 +40,34 @@ export const getDefaultLanguageCode = (survey: TSurvey) => {
|
||||
});
|
||||
if (defaultSurveyLanguage) return defaultSurveyLanguage.language.code;
|
||||
};
|
||||
|
||||
export const handleHiddenFields = (
|
||||
hiddenFieldsConfig: TSurvey["hiddenFields"],
|
||||
hiddenFields: TJsTrackProperties["hiddenFields"]
|
||||
): TResponseHiddenFieldValue => {
|
||||
const { enabled: enabledHiddenFields, fieldIds: hiddenFieldIds } = hiddenFieldsConfig || {};
|
||||
|
||||
let hiddenFieldsObject: TResponseHiddenFieldValue = {};
|
||||
|
||||
if (!enabledHiddenFields) {
|
||||
logger.error("Hidden fields are not enabled for this survey");
|
||||
} else if (hiddenFieldIds && hiddenFields) {
|
||||
const unknownHiddenFields: string[] = [];
|
||||
hiddenFieldsObject = Object.keys(hiddenFields).reduce((acc, key) => {
|
||||
if (hiddenFieldIds?.includes(key)) {
|
||||
acc[key] = hiddenFields?.[key];
|
||||
} else {
|
||||
unknownHiddenFields.push(key);
|
||||
}
|
||||
return acc;
|
||||
}, {} as TResponseHiddenFieldValue);
|
||||
|
||||
if (unknownHiddenFields.length > 0) {
|
||||
logger.error(
|
||||
`Unknown hidden fields: ${unknownHiddenFields.join(", ")}. Please add them to the survey hidden fields.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return hiddenFieldsObject;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TJsWebsiteConfigInput } from "@formbricks/types/js";
|
||||
import { TJsTrackProperties, TJsWebsiteConfigInput } from "@formbricks/types/js";
|
||||
|
||||
// Shared imports
|
||||
import { CommandQueue } from "../shared/commandQueue";
|
||||
@@ -26,7 +26,7 @@ const reset = async (): Promise<void> => {
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const track = async (name: string, properties: any = {}): Promise<void> => {
|
||||
const track = async (name: string, properties?: TJsTrackProperties): Promise<void> => {
|
||||
queue.add<any>(true, "website", trackCodeAction, name, properties);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TJsTrackProperties } from "@formbricks/types/js";
|
||||
|
||||
import { InvalidCodeError, NetworkError, Result, err, okVoid } from "../../shared/errors";
|
||||
import { Logger } from "../../shared/logger";
|
||||
import { WebsiteConfig } from "./config";
|
||||
@@ -6,7 +8,11 @@ import { triggerSurvey } from "./widget";
|
||||
const logger = Logger.getInstance();
|
||||
const websiteConfig = WebsiteConfig.getInstance();
|
||||
|
||||
export const trackAction = async (name: string, alias?: string): Promise<Result<void, NetworkError>> => {
|
||||
export const trackAction = async (
|
||||
name: string,
|
||||
alias?: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<Result<void, NetworkError>> => {
|
||||
const aliasName = alias || name;
|
||||
logger.debug(`Formbricks: Action "${aliasName}" tracked`);
|
||||
|
||||
@@ -17,7 +23,7 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
|
||||
for (const survey of activeSurveys) {
|
||||
for (const trigger of survey.triggers) {
|
||||
if (trigger.actionClass.name === name) {
|
||||
await triggerSurvey(survey, name);
|
||||
await triggerSurvey(survey, name, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +35,8 @@ export const trackAction = async (name: string, alias?: string): Promise<Result<
|
||||
};
|
||||
|
||||
export const trackCodeAction = (
|
||||
code: string
|
||||
code: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<Result<void, NetworkError>> | Result<void, InvalidCodeError> => {
|
||||
const {
|
||||
state: { actionClasses = [] },
|
||||
@@ -45,7 +52,7 @@ export const trackCodeAction = (
|
||||
});
|
||||
}
|
||||
|
||||
return trackAction(action.name, code);
|
||||
return trackAction(action.name, code, properties);
|
||||
};
|
||||
|
||||
export const trackNoCodeAction = (name: string): Promise<Result<void, NetworkError>> => {
|
||||
|
||||
@@ -2,12 +2,12 @@ import { FormbricksAPI } from "@formbricks/api";
|
||||
import { ResponseQueue } from "@formbricks/lib/responseQueue";
|
||||
import { SurveyState } from "@formbricks/lib/surveyState";
|
||||
import { getStyling } from "@formbricks/lib/utils/styling";
|
||||
import { TJSWebsiteStateDisplay } from "@formbricks/types/js";
|
||||
import { TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TJSWebsiteStateDisplay, TJsTrackProperties } from "@formbricks/types/js";
|
||||
import { TResponseHiddenFieldValue, TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
|
||||
import { Logger } from "../../shared/logger";
|
||||
import { getDefaultLanguageCode, getLanguageCode } from "../../shared/utils";
|
||||
import { getDefaultLanguageCode, getLanguageCode, handleHiddenFields } from "../../shared/utils";
|
||||
import { WebsiteConfig } from "./config";
|
||||
import { filterPublicSurveys } from "./sync";
|
||||
|
||||
@@ -29,7 +29,11 @@ const shouldDisplayBasedOnPercentage = (displayPercentage: number) => {
|
||||
return randomNum <= displayPercentage;
|
||||
};
|
||||
|
||||
export const triggerSurvey = async (survey: TSurvey, action?: string): Promise<void> => {
|
||||
export const triggerSurvey = async (
|
||||
survey: TSurvey,
|
||||
action?: string,
|
||||
properties?: TJsTrackProperties
|
||||
): Promise<void> => {
|
||||
// Check if the survey should be displayed based on displayPercentage
|
||||
if (survey.displayPercentage) {
|
||||
const shouldDisplaySurvey = shouldDisplayBasedOnPercentage(survey.displayPercentage);
|
||||
@@ -38,10 +42,20 @@ export const triggerSurvey = async (survey: TSurvey, action?: string): Promise<v
|
||||
return; // skip displaying the survey
|
||||
}
|
||||
}
|
||||
await renderWidget(survey, action);
|
||||
|
||||
const hiddenFieldsObject: TResponseHiddenFieldValue = handleHiddenFields(
|
||||
survey.hiddenFields,
|
||||
properties?.hiddenFields
|
||||
);
|
||||
|
||||
await renderWidget(survey, action, hiddenFieldsObject);
|
||||
};
|
||||
|
||||
const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
const renderWidget = async (
|
||||
survey: TSurvey,
|
||||
action?: string,
|
||||
hiddenFields: TResponseHiddenFieldValue = {}
|
||||
) => {
|
||||
if (isSurveyRunning) {
|
||||
logger.debug("A survey is already running. Skipping.");
|
||||
return;
|
||||
@@ -94,8 +108,8 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
|
||||
setTimeout(() => {
|
||||
formbricksSurveys.renderSurveyModal({
|
||||
survey: survey,
|
||||
isBrandingEnabled: isBrandingEnabled,
|
||||
survey,
|
||||
isBrandingEnabled,
|
||||
clickOutside,
|
||||
darkOverlay,
|
||||
languageCode,
|
||||
@@ -175,6 +189,7 @@ const renderWidget = async (survey: TSurvey, action?: string) => {
|
||||
url: window.location.href,
|
||||
action,
|
||||
},
|
||||
hiddenFields,
|
||||
});
|
||||
},
|
||||
onClose: closeSurvey,
|
||||
|
||||
@@ -87,6 +87,7 @@ export class ResponseQueue {
|
||||
surveyId: this.surveyState.surveyId,
|
||||
userId: this.surveyState.userId || null,
|
||||
singleUseId: this.surveyState.singleUseId || null,
|
||||
data: { ...responseUpdate.data, ...responseUpdate.hiddenFields },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Could not create response");
|
||||
|
||||
@@ -35,7 +35,6 @@ export const Survey = ({
|
||||
onFileUpload,
|
||||
responseCount,
|
||||
startAtQuestionId,
|
||||
hiddenFieldsRecord,
|
||||
clickOutside,
|
||||
shouldResetQuestionId,
|
||||
}: SurveyBaseProps) => {
|
||||
@@ -58,7 +57,7 @@ export const Survey = ({
|
||||
|
||||
const [loadingElement, setLoadingElement] = useState(false);
|
||||
const [history, setHistory] = useState<string[]>([]);
|
||||
const [responseData, setResponseData] = useState<TResponseData>(hiddenFieldsRecord ?? {});
|
||||
const [responseData, setResponseData] = useState<TResponseData>({});
|
||||
const [ttc, setTtc] = useState<TResponseTtc>({});
|
||||
const cardArrangement = useMemo(() => {
|
||||
if (survey.type === "link") {
|
||||
|
||||
@@ -24,7 +24,6 @@ export interface SurveyBaseProps {
|
||||
responseCount?: number;
|
||||
isCardBorderVisible?: boolean;
|
||||
startAtQuestionId?: string;
|
||||
hiddenFieldsRecord?: TResponseData;
|
||||
clickOutside?: boolean;
|
||||
shouldResetQuestionId?: boolean;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ZActionClass } from "./actionClasses";
|
||||
import { ZAttributes } from "./attributes";
|
||||
import { ZPerson } from "./people";
|
||||
import { ZProduct } from "./product";
|
||||
import { ZResponseHiddenFieldValue } from "./responses";
|
||||
import { ZSurvey } from "./surveys";
|
||||
|
||||
export const ZJsPerson = z.object({
|
||||
@@ -252,3 +253,9 @@ export type TSettings = z.infer<typeof ZJsSettings>;
|
||||
export const ZJsPackageType = z.union([z.literal("app"), z.literal("website")]);
|
||||
|
||||
export type TJsPackageType = z.infer<typeof ZJsPackageType>;
|
||||
|
||||
export const ZJsTrackProperties = z.object({
|
||||
hiddenFields: ZResponseHiddenFieldValue.optional(),
|
||||
});
|
||||
|
||||
export type TJsTrackProperties = z.infer<typeof ZJsTrackProperties>;
|
||||
|
||||
@@ -292,6 +292,9 @@ export const ZResponseWithSurvey = ZResponse.extend({
|
||||
|
||||
export type TResponseWithSurvey = z.infer<typeof ZResponseWithSurvey>;
|
||||
|
||||
export const ZResponseHiddenFieldValue = z.record(z.union([z.string(), z.number(), z.array(z.string())]));
|
||||
export type TResponseHiddenFieldValue = z.infer<typeof ZResponseHiddenFieldValue>;
|
||||
|
||||
export const ZResponseUpdate = z.object({
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
@@ -304,6 +307,7 @@ export const ZResponseUpdate = z.object({
|
||||
action: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
hiddenFields: ZResponseHiddenFieldValue.optional(),
|
||||
});
|
||||
|
||||
export type TResponseUpdate = z.infer<typeof ZResponseUpdate>;
|
||||
|
||||
@@ -226,7 +226,7 @@ export const PreviewSurvey = ({
|
||||
: "expanded_with_fixed_positioning"
|
||||
: "shrink"
|
||||
}
|
||||
className="relative flex h-[95] max-h-[95%] w-5/6 items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
|
||||
className="relative flex h-[95%] max-h-[95%] w-5/6 items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
|
||||
{previewMode === "mobile" && (
|
||||
<>
|
||||
<p className="absolute left-0 top-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
|
||||
|
||||
Reference in New Issue
Block a user