chore: Freeze client api + api docs (#4373)

Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
Dhruwang Jariwala
2024-12-06 18:04:30 +05:30
committed by GitHub
parent 97a66168c0
commit dfe025ab8e
40 changed files with 6145 additions and 210 deletions

View File

@@ -0,0 +1,66 @@
"use client";
import { Button } from "@/components/Button";
import { LoadingSpinner } from "@/components/icons/LoadingSpinner";
import { useState } from "react";
import { RedocStandalone } from "redoc";
import "./style.css";
const redocTheme = {
hideDownloadButton: true,
hideLoading: true,
nativeScrollbars: true,
theme: {
sidebar: {
backgroundColor: "transparent",
textColor: "rgb(203, 213, 225)",
activeTextColor: "#2dd4bf",
},
rightPanel: {
backgroundColor: "transparent",
},
colors: {
primary: { main: "#2dd4bf" },
text: {
primary: "rgb(203, 213, 225)",
secondary: "rgb(203, 213, 225)",
},
responses: {
success: { color: "#22c55e" },
error: { color: "#ef4444" },
info: { color: "#3b82f6" },
},
},
typography: {
fontSize: "16px",
lineHeight: "2rem",
fontFamily: "Jost, system-ui, -apple-system, sans-serif",
headings: {
fontFamily: "Jost, system-ui, -apple-system, sans-serif",
fontWeight: "600",
},
code: {
fontSize: "16px",
fontFamily: "ui-monospace, monospace",
},
},
codeBlock: {
backgroundColor: "rgb(24, 35, 58)",
},
spacing: { unit: 5 },
},
};
export const ApiDocs = () => {
const [loading, setLoading] = useState(true);
return (
<div className="px-4">
<Button href="/developer-docs/rest-api" arrow="left" className="mb-4 mt-8">
Back to docs
</Button>
<RedocStandalone specUrl="/docs/openapi.yaml" onLoaded={() => setLoading(false)} options={redocTheme} />
{loading && <LoadingSpinner />}
</div>
);
};

View File

@@ -0,0 +1,74 @@
h5,
.sc-dhCplO,
.sc-dpBQxM {
color: rgb(203, 213, 225) !important;
}
.tab-success,
.react-tabs__tab,
.tab-error {
background-color: transparent !important;
border: 1px solid rgb(203, 213, 225) !important;
margin: 10px 5px !important;
}
.sc-dwGkES,
.sc-ePpfBx {
background-color: rgb(24, 24, 27) !important;
border: 1px solid rgb(203, 213, 225) !important;
}
.sc-ePpfBx,
.corVrN {
background-color: rgb(24, 24, 27) !important;
border: none !important;
}
.cqdCbT {
display: none !important;
}
.kiMaJz, .iZNUDY {
align-items: center !important;
}
.react-tabs__tab-panel > div {
padding: 0 !important;
border-radius: 8px !important;
overflow: hidden !important;
}
.sc-Rjrgp {
background-color: rgb(15, 23, 42) !important;
display: flex !important;
justify-content: center !important;
flex-direction: column !important;
padding: 8px 16px !important;
}
.cugBNu {
position: static !important;
}
.daIHdK {
padding: 0 !important;
}
.kqHNPM {
margin-top: 0px !important;
}
.sc-iwXfZk,
.redoc-json {
padding: 1rem !important;
background-color: rgb(24, 35, 58) !important;
}
.sc-uYFMi {
background-color: rgb(24, 35, 58) !important;
padding: 0.5rem 0 !important;
color: #2dd4bf !important;
font-weight: 600 !important;
}
.token {
color: #2dd4bf !important;
}
.property {
color: rgb(203, 213, 225) !important;
}
.sc-iPHsxv {
background-color: rgb(15, 23, 42) !important;
border-radius: 8px !important;
}

View File

@@ -0,0 +1,5 @@
import { ApiDocs } from "./components/api-docs";
export default function ApiDocsPage() {
return <ApiDocs />;
}

View File

@@ -20,31 +20,31 @@ export const metadata = {
Formbricks offers two types of APIs: the **Public Client API** and the **Management API**. Each API serves a different purpose, has different authentication requirements, and provides access to different data and settings.
View our [API Documentation](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh) in more than 30 frameworks and languages.
View our [API Documentation](/api-docs) in more than 30 frameworks and languages.
## Public Client API
The [Public Client API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#5c981d9e-5e7d-455d-9795-b9c45bc2f930) is designed for our SDKs and **does not require authentication**. This API is ideal for client-side interactions, as it doesn't expose sensitive information.
The [Public Client API](/api-docs#tag/Client-API) is designed for our SDKs and **does not require authentication**. This API is ideal for client-side interactions, as it doesn't expose sensitive information.
We currently have the following Client API methods exposed and below is their documentation attached in Postman:
- [Displays API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#949272bf-daec-4d72-9b52-47af3d74a62c) - Mark Survey as Displayed or Update an existing Display by linking it with a Response for a Person
- [People API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#ee3d2188-4253-4bca-9238-6b76455805a9) - Create & Update a Person (e.g. attributes, email, userId, etc)
- [Responses API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#8c773032-536c-483c-a237-c7697347946e) - Create & Update a Response for a Survey
- [Displays API](/api-docs#tag/Public-Client-API-greater-Displays) - Mark Survey as Displayed or Update an existing Display by linking it with a Response for a Person
- [People API](/api-docs#tag/Public-Client-API-greater-People) - Create & Update a Person (e.g. attributes, email, userId, etc)
- [Responses API](/api-docs#tag/Public-Client-API-greater-Responses) - Create & Update a Response for a Survey
## Management API
The [Management API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#98fce5a1-1365-4125-8de1-acdb28206766) provides access to all data and settings that your account has access to in the Formbricks app. This API **requires a personal API Key** for authentication, which can be generated in the Settings section of the Formbricks app. Checkout the [API Key Setup](#how-to-generate-an-api-key) below to generate & manage API Keys.
The [Management API](/api-docs#tag/Management-API) provides access to all data and settings that your account has access to in the Formbricks app. This API **requires a personal API Key** for authentication, which can be generated in the Settings section of the Formbricks app. Checkout the [API Key Setup](#how-to-generate-an-api-key) below to generate & manage API Keys.
We currently have the following Management API methods exposed and below is their documentation attached in Postman:
- [Action Class API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#81947f69-99fc-41c9-a184-f3260e02be48) - Create, List, and Delete Action Classes
- [Attribute Class API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#31089010-d468-4a7c-943e-8ebe71b9a36e) - Create, List, and Delete Attribute Classes
- [Me API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#79e08365-641d-4b2d-aea2-9a855e0438ec) - Retrieve Account Information
- [People API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#cffc27a6-dafb-428f-8ea7-5165bedb911e) - List and Delete People
- [Response API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#e544ec0d-8b30-4e33-8d35-2441cb40d676) - List, List by Survey, Update, and Delete Responses
- [Survey API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#953189b2-37b5-4429-a7bd-f4d01ceae242) - List, Create, Update, generate multiple suId and Delete Surveys
- [Webhook API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#62e6ec65-021b-42a4-ac93-d1434b393c6c) - List, Create, and Delete Webhooks
- [Action Class API](/api-docs#tag/Management-API-greater-Action-Class) - Create, List, and Delete Action Classes
- [Attribute Class API](/api-docs#tag/Management-API-greater-Attribute-Class) - Create, List, and Delete Attribute Classes
- [Me API](/api-docs#tag/Management-API-greater-Me) - Retrieve Account Information
- [People API](/api-docs#tag/Management-API-greater-People) - List and Delete People
- [Response API](/api-docs#tag/Management-API-greater-Response) - List, List by Survey, Update, and Delete Responses
- [Survey API](/api-docs#tag/Management-API-greater-Survey) - List, Create, Update, generate multiple suId and Delete Surveys
- [Webhook API](/api-docs#tag/Management-API-greater-Webhook) - List, Create, and Delete Webhooks
## How to Generate an API key

View File

@@ -19,6 +19,14 @@ export const Layout = ({
}) => {
const pathname = usePathname();
const fullWidthRoutes = ["/api-docs"];
const isFullWidth = fullWidthRoutes.includes(pathname || "");
// If it's a full-width route, return just the children
if (isFullWidth) {
return children;
}
return (
<SectionProvider sections={allSections[pathname || ""] ?? []}>
<div className="h-full lg:ml-72 xl:ml-80">

View File

@@ -0,0 +1,7 @@
import React from "react";
export const LoadingSpinner = () => (
<div className="absolute inset-0 flex items-center justify-center">
<div className="border-primary h-12 w-12 animate-spin rounded-full border-4 border-t-transparent" />
</div>
);

View File

@@ -51,6 +51,7 @@
"react-highlight-words": "0.20.0",
"react-markdown": "9.0.1",
"react-responsive-embed": "2.1.0",
"redoc": "2.2.0",
"remark": "15.0.1",
"remark-gfm": "4.0.0",
"remark-mdx": "3.0.1",
@@ -65,8 +66,8 @@
},
"devDependencies": {
"@formbricks/config-typescript": "workspace:*",
"@formbricks/eslint-config": "workspace:*",
"@types/dompurify": "3.0.5",
"@types/react-highlight-words": "0.20.0",
"@formbricks/eslint-config": "workspace:*"
"@types/react-highlight-words": "0.20.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -187,8 +187,8 @@ const Page = async (props) => {
)}
</p>
<Button asChild>
<Link
href="https://app.formbricks.com/s/clvupq3y205i5yrm3sm9v1xt5"
<Link
href="https://app.formbricks.com/s/clvupq3y205i5yrm3sm9v1xt5"
target="_blank"
rel="noopener noreferrer">
{t("environments.settings.enterprise.request_30_day_trial_license")}

View File

@@ -19,7 +19,7 @@ import {
} from "@formbricks/lib/posthogServer";
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { TJsAppStateSync, ZJsPeopleUserIdInput } from "@formbricks/types/js";
import { TJsRNStateSync, ZJsPeopleUserIdInput } from "@formbricks/types/js";
import { TSurvey } from "@formbricks/types/surveys/types";
export const OPTIONS = async (): Promise<Response> => {
@@ -174,7 +174,7 @@ export const GET = async (
let transformedSurveys: TSurvey[] = surveys;
// creating state object
let state: TJsAppStateSync = {
let state: TJsRNStateSync = {
surveys: !isAppSurveyResponseLimitReached
? transformedSurveys.map((survey) => replaceAttributeRecall(survey, contactAttributes))
: [],

View File

@@ -0,0 +1,38 @@
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { actionClassCache } from "@formbricks/lib/actionClass/cache";
import { cache } from "@formbricks/lib/cache";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TJsEnvironmentStateActionClass } from "@formbricks/types/js";
export const getActionClassesForEnvironmentState = reactCache(
async (environmentId: string): Promise<TJsEnvironmentStateActionClass[]> =>
cache(
async () => {
validateInputs([environmentId, ZId]);
try {
return await prisma.actionClass.findMany({
where: {
environmentId: environmentId,
},
select: {
id: true,
type: true,
name: true,
key: true,
noCodeConfig: true,
},
});
} catch (error) {
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
}
},
[`getActionClassesForEnvironmentState-${environmentId}`],
{
tags: [actionClassCache.tag.byEnvironmentId(environmentId)],
}
)()
);

View File

@@ -1,6 +1,5 @@
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";
@@ -15,11 +14,12 @@ import {
sendPlanLimitsReachedEventToPosthogWeekly,
} from "@formbricks/lib/posthogServer";
import { projectCache } from "@formbricks/lib/project/cache";
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
import { surveyCache } from "@formbricks/lib/survey/cache";
import { getSurveys } from "@formbricks/lib/survey/service";
import { ResourceNotFoundError } from "@formbricks/types/errors";
import { TJsEnvironmentState } from "@formbricks/types/js";
import { getActionClassesForEnvironmentState } from "./actionClass";
import { getProjectForEnvironmentState } from "./project";
import { getSurveysForEnvironmentState } from "./survey";
/**
*
@@ -36,7 +36,7 @@ export const getEnvironmentState = async (
const [environment, organization, project] = await Promise.all([
getEnvironment(environmentId),
getOrganizationByEnvironmentId(environmentId),
getProjectByEnvironmentId(environmentId),
getProjectForEnvironmentState(environmentId),
]);
if (!environment) {
@@ -94,8 +94,8 @@ export const getEnvironmentState = async (
}
const [surveys, actionClasses] = await Promise.all([
getSurveys(environmentId),
getActionClasses(environmentId),
getSurveysForEnvironmentState(environmentId),
getActionClassesForEnvironmentState(environmentId),
]);
const filteredSurveys = surveys.filter(

View File

@@ -0,0 +1,49 @@
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
import { projectCache } from "@formbricks/lib/project/cache";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TJsEnvironmentStateProject } from "@formbricks/types/js";
export const getProjectForEnvironmentState = reactCache(
async (environmentId: string): Promise<TJsEnvironmentStateProject | null> =>
cache(
async () => {
validateInputs([environmentId, ZId]);
try {
return await prisma.project.findFirst({
where: {
environments: {
some: {
id: environmentId,
},
},
},
select: {
id: true,
recontactDays: true,
clickOutsideClose: true,
darkOverlay: true,
placement: true,
inAppSurveyBranding: true,
styling: true,
},
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getProjectForEnvironmentState-${environmentId}`],
{
tags: [projectCache.tag.byEnvironmentId(environmentId)],
}
)()
);

View File

@@ -0,0 +1,77 @@
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
import { surveyCache } from "@formbricks/lib/survey/cache";
import { transformPrismaSurvey } from "@formbricks/lib/survey/utils";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
export const getSurveysForEnvironmentState = reactCache(
async (environmentId: string): Promise<TJsEnvironmentStateSurvey[]> =>
cache(
async () => {
validateInputs([environmentId, ZId]);
try {
const surveysPrisma = await prisma.survey.findMany({
where: {
environmentId,
},
select: {
id: true,
welcomeCard: true,
name: true,
questions: true,
variables: true,
type: true,
showLanguageSwitch: true,
languages: true,
endings: true,
autoClose: true,
styling: true,
status: true,
segment: {
include: {
surveys: {
select: {
id: true,
},
},
},
},
recontactDays: true,
displayLimit: true,
displayOption: true,
hiddenFields: true,
triggers: {
select: {
actionClass: {
select: {
name: true,
},
},
},
},
displayPercentage: true,
delay: true,
},
});
return surveysPrisma.map((survey) => transformPrismaSurvey<TJsEnvironmentStateSurvey>(survey));
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getSurveysForEnvironmentState-${environmentId}`],
{
tags: [surveyCache.tag.byEnvironmentId(environmentId)],
}
)()
);

View File

@@ -24,9 +24,16 @@ export const OPTIONS = async (): Promise<Response> => {
export const POST = async (request: Request, context: Context): Promise<Response> => {
const params = await context.params;
const requestHeaders = await headers();
let responseInput;
try {
responseInput = await request.json();
} catch (error) {
return responses.badRequestResponse("Invalid JSON in request body", { error: error.message }, true);
}
const { environmentId } = params;
const environmentIdValidation = ZId.safeParse(environmentId);
const responseInputValidation = ZResponseInput.safeParse({ ...responseInput, environmentId });
if (!environmentIdValidation.success) {
return responses.badRequestResponse(
@@ -36,7 +43,13 @@ export const POST = async (request: Request, context: Context): Promise<Response
);
}
const responseInput = await request.json();
if (!responseInputValidation.success) {
return responses.badRequestResponse(
"Fields are missing or incorrectly formatted",
transformErrorToDetails(responseInputValidation.error),
true
);
}
const agent = UAParser(request.headers.get("user-agent"));
const country =
@@ -44,17 +57,10 @@ export const POST = async (request: Request, context: Context): Promise<Response
requestHeaders.get("X-Vercel-IP-Country") ||
requestHeaders.get("CloudFront-Viewer-Country") ||
undefined;
const inputValidation = ZResponseInput.safeParse({ ...responseInput, environmentId });
if (!inputValidation.success) {
return responses.badRequestResponse(
"Fields are missing or incorrectly formatted",
transformErrorToDetails(inputValidation.error),
true
);
}
const responseInputData = responseInputValidation.data;
if (inputValidation.data.userId) {
if (responseInputData.userId) {
const isContactsEnabled = await getIsContactsEnabled();
if (!isContactsEnabled) {
return responses.forbiddenResponse("User identification is only available for enterprise users.", true);
@@ -62,9 +68,9 @@ export const POST = async (request: Request, context: Context): Promise<Response
}
// get and check survey
const survey = await getSurvey(responseInput.surveyId);
const survey = await getSurvey(responseInputData.surveyId);
if (!survey) {
return responses.notFoundResponse("Survey", responseInput.surveyId, true);
return responses.notFoundResponse("Survey", responseInputData.surveyId, true);
}
if (survey.environmentId !== environmentId) {
return responses.badRequestResponse(
@@ -80,19 +86,19 @@ export const POST = async (request: Request, context: Context): Promise<Response
let response: TResponse;
try {
const meta: TResponseInput["meta"] = {
source: responseInput?.meta?.source,
url: responseInput?.meta?.url,
source: responseInputData?.meta?.source,
url: responseInputData?.meta?.url,
userAgent: {
browser: agent?.browser.name,
device: agent?.device.type || "desktop",
os: agent?.os.name,
},
country: country,
action: responseInput?.meta?.action,
action: responseInputData?.meta?.action,
};
response = await createResponse({
...inputValidation.data,
...responseInputData,
meta,
});
} catch (error) {

View File

@@ -1,4 +1,4 @@
import type { TActionClass } from "@formbricks/types/action-classes";
import { TJsEnvironmentStateActionClass } from "@formbricks/types/js";
import { trackNoCodeAction } from "./actions";
import { Config } from "./config";
import { ErrorHandler, NetworkError, Result, err, match, okVoid } from "./errors";
@@ -65,7 +65,7 @@ const checkClickMatch = (event: MouseEvent) => {
const targetElement = event.target as HTMLElement;
noCodeClickActionClasses.forEach((action: TActionClass) => {
noCodeClickActionClasses.forEach((action: TJsEnvironmentStateActionClass) => {
if (evaluateNoCodeConfigClick(targetElement, action)) {
trackNoCodeAction(action.name).then((res) => {
match(

View File

@@ -1,13 +1,14 @@
import { diffInDays } from "@formbricks/lib/utils/datetime";
import {
TActionClass,
TActionClassNoCodeConfig,
TActionClassPageUrlRule,
} from "@formbricks/types/action-classes";
import { TActionClassNoCodeConfig, TActionClassPageUrlRule } from "@formbricks/types/action-classes";
import { TAttributes } from "@formbricks/types/attributes";
import { TJsEnvironmentState, TJsPersonState, TJsTrackProperties } from "@formbricks/types/js";
import {
TJsEnvironmentState,
TJsEnvironmentStateActionClass,
TJsEnvironmentStateSurvey,
TJsPersonState,
TJsTrackProperties,
} from "@formbricks/types/js";
import { TResponseHiddenFieldValue } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Logger } from "./logger";
const logger = Logger.getInstance();
@@ -50,7 +51,10 @@ export const handleUrlFilters = (urlFilters: TActionClassNoCodeConfig["urlFilter
return isMatch;
};
export const evaluateNoCodeConfigClick = (targetElement: HTMLElement, action: TActionClass): boolean => {
export const evaluateNoCodeConfigClick = (
targetElement: HTMLElement,
action: TJsEnvironmentStateActionClass
): boolean => {
if (action.noCodeConfig?.type !== "click") return false;
const innerHtml = action.noCodeConfig.elementSelector.innerHtml;
@@ -79,7 +83,7 @@ export const evaluateNoCodeConfigClick = (targetElement: HTMLElement, action: TA
};
export const handleHiddenFields = (
hiddenFieldsConfig: TSurvey["hiddenFields"],
hiddenFieldsConfig: TJsEnvironmentStateSurvey["hiddenFields"],
hiddenFields: TJsTrackProperties["hiddenFields"]
): TResponseHiddenFieldValue => {
const { enabled: enabledHiddenFields, fieldIds: hiddenFieldIds } = hiddenFieldsConfig || {};
@@ -114,7 +118,10 @@ export const shouldDisplayBasedOnPercentage = (displayPercentage: number) => {
return randomNum <= displayPercentage;
};
export const getLanguageCode = (survey: TSurvey, attributes: TAttributes): string | undefined => {
export const getLanguageCode = (
survey: TJsEnvironmentStateSurvey,
attributes: TAttributes
): string | undefined => {
const language = attributes.language;
const availableLanguageCodes = survey.languages.map((surveyLanguage) => surveyLanguage.language.code);
if (!language) return "default";
@@ -139,7 +146,7 @@ export const getLanguageCode = (survey: TSurvey, attributes: TAttributes): strin
}
};
export const getDefaultLanguageCode = (survey: TSurvey) => {
export const getDefaultLanguageCode = (survey: TJsEnvironmentStateSurvey) => {
const defaultSurveyLanguage = survey.languages?.find((surveyLanguage) => {
return surveyLanguage.default === true;
});
@@ -159,7 +166,7 @@ export const getIsDebug = () => window.location.search.includes("formbricksDebug
export const filterSurveys = (
environmentState: TJsEnvironmentState,
personState: TJsPersonState
): TSurvey[] => {
): TJsEnvironmentStateSurvey[] => {
const { project, surveys } = environmentState.data;
const { displays, responses, lastDisplayAt, segments, userId } = personState.data;
@@ -168,7 +175,7 @@ export const filterSurveys = (
}
// Function to filter surveys based on displayOption criteria
let filteredSurveys = surveys.filter((survey: TSurvey) => {
let filteredSurveys = surveys.filter((survey: TJsEnvironmentStateSurvey) => {
switch (survey.displayOption) {
case "respondMultiple":
return true;

View File

@@ -2,10 +2,14 @@ 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 { TJsFileUploadParams, TJsPersonState, TJsTrackProperties } from "@formbricks/types/js";
import {
TJsEnvironmentStateSurvey,
TJsFileUploadParams,
TJsPersonState,
TJsTrackProperties,
} from "@formbricks/types/js";
import { TResponseHiddenFieldValue, TResponseUpdate } from "@formbricks/types/responses";
import { TUploadFileConfig } from "@formbricks/types/storage";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Config } from "./config";
import { CONTAINER_ID } from "./constants";
import { Logger } from "./logger";
@@ -28,7 +32,7 @@ export const setIsSurveyRunning = (value: boolean) => {
};
export const triggerSurvey = async (
survey: TSurvey,
survey: TJsEnvironmentStateSurvey,
action?: string,
properties?: TJsTrackProperties
): Promise<void> => {
@@ -50,7 +54,7 @@ export const triggerSurvey = async (
};
const renderWidget = async (
survey: TSurvey,
survey: TJsEnvironmentStateSurvey,
action?: string,
hiddenFields: TResponseHiddenFieldValue = {}
) => {

View File

@@ -224,7 +224,7 @@ export const getSurvey = reactCache(
return null;
}
return transformPrismaSurvey(surveyPrisma);
return transformPrismaSurvey<TSurvey>(surveyPrisma);
},
[`getSurvey-${surveyId}`],
{
@@ -267,7 +267,7 @@ export const getSurveysByActionClassId = reactCache(
const surveys: TSurvey[] = [];
for (const surveyPrisma of surveysPrisma) {
const transformedSurvey = transformPrismaSurvey(surveyPrisma);
const transformedSurvey = transformPrismaSurvey<TSurvey>(surveyPrisma);
surveys.push(transformedSurvey);
}
@@ -303,7 +303,7 @@ export const getSurveys = reactCache(
skip: offset,
});
return surveysPrisma.map(transformPrismaSurvey);
return surveysPrisma.map((surveyPrisma) => transformPrismaSurvey<TSurvey>(surveyPrisma));
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
@@ -1366,7 +1366,7 @@ export const getSurveysBySegmentId = reactCache(
const surveys: TSurvey[] = [];
for (const surveyPrisma of surveysPrisma) {
const transformedSurvey = transformPrismaSurvey(surveyPrisma);
const transformedSurvey = transformPrismaSurvey<TSurvey>(surveyPrisma);
surveys.push(transformedSurvey);
}

View File

@@ -2,6 +2,7 @@ import "server-only";
import { Prisma } from "@prisma/client";
import { generateObject } from "ai";
import { z } from "zod";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TSegment } from "@formbricks/types/segment";
import {
TSurvey,
@@ -11,7 +12,9 @@ import {
} from "@formbricks/types/surveys/types";
import { llmModel } from "../aiModels";
export const transformPrismaSurvey = (surveyPrisma: any): TSurvey => {
export const transformPrismaSurvey = <T extends TSurvey | TJsEnvironmentStateSurvey>(
surveyPrisma: any
): T => {
let segment: TSegment | null = null;
if (surveyPrisma.segment) {
@@ -21,11 +24,11 @@ export const transformPrismaSurvey = (surveyPrisma: any): TSurvey => {
};
}
const transformedSurvey: TSurvey = {
const transformedSurvey = {
...surveyPrisma,
displayPercentage: Number(surveyPrisma.displayPercentage) || null,
segment,
};
} as T;
return transformedSurvey;
};

View File

@@ -1,11 +1,11 @@
import { createId } from "@paralleldrive/cuid2";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
import {
TActionCalculate,
TActionObjective,
TConditionGroup,
TSingleCondition,
TSurvey,
TSurveyLogic,
TSurveyLogicAction,
TSurveyQuestion,
@@ -212,7 +212,7 @@ export const getUpdatedActionBody = (
};
export const evaluateLogic = (
localSurvey: TSurvey,
localSurvey: TJsEnvironmentStateSurvey,
data: TResponseData,
variablesData: TResponseVariables,
conditions: TConditionGroup,
@@ -234,7 +234,7 @@ export const evaluateLogic = (
};
const evaluateSingleCondition = (
localSurvey: TSurvey,
localSurvey: TJsEnvironmentStateSurvey,
data: TResponseData,
variablesData: TResponseVariables,
condition: TSingleCondition,
@@ -476,7 +476,7 @@ const getVariableValue = (
};
const getLeftOperandValue = (
localSurvey: TSurvey,
localSurvey: TJsEnvironmentStateSurvey,
data: TResponseData,
variablesData: TResponseVariables,
leftOperand: TSingleCondition["leftOperand"],
@@ -541,7 +541,7 @@ const getLeftOperandValue = (
};
const getRightOperandValue = (
localSurvey: TSurvey,
localSurvey: TJsEnvironmentStateSurvey,
data: TResponseData,
variablesData: TResponseVariables,
rightOperand: TSingleCondition["rightOperand"]
@@ -564,7 +564,7 @@ const getRightOperandValue = (
};
export const performActions = (
survey: TSurvey,
survey: TJsEnvironmentStateSurvey,
actions: TSurveyLogicAction[],
data: TResponseData,
calculationResults: TResponseVariables
@@ -598,7 +598,7 @@ export const performActions = (
};
const performCalculation = (
survey: TSurvey,
survey: TJsEnvironmentStateSurvey,
action: TActionCalculate,
data: TResponseData,
calculations: Record<string, number | string>

View File

@@ -1,7 +1,6 @@
import { TProject } from "@formbricks/types/project";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TJsEnvironmentStateProject, TJsEnvironmentStateSurvey } from "@formbricks/types/js";
export const getStyling = (project: TProject, survey: TSurvey) => {
export const getStyling = (project: TJsEnvironmentStateProject, survey: TJsEnvironmentStateSurvey) => {
// allow style overwrite is disabled from the project
if (!project.styling.allowStyleOverwrite) {
return project.styling;

View File

@@ -1,12 +1,12 @@
import React, { useCallback, useEffect, useSyncExternalStore } from "react";
import { type TJsReactNativeConfigInput } from "@formbricks/types/js";
import { type TJsRNConfigInput } from "@formbricks/types/js";
import { Logger } from "../../js-core/src/lib/logger";
import { init } from "./lib";
import { SurveyStore } from "./lib/survey-store";
import { SurveyWebView } from "./survey-web-view";
interface FormbricksProps {
initConfig: TJsReactNativeConfigInput;
initConfig: TJsRNConfigInput;
}
const surveyStore = SurveyStore.getInstance();
const logger = Logger.getInstance();

View File

@@ -1,4 +1,4 @@
import { type TSurvey } from "@formbricks/types/surveys/types";
import { type TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import {
type InvalidCodeError,
type NetworkError,
@@ -14,7 +14,7 @@ import { SurveyStore } from "./survey-store";
const logger = Logger.getInstance();
const surveyStore = SurveyStore.getInstance();
export const triggerSurvey = (survey: TSurvey): void => {
export const triggerSurvey = (survey: TJsEnvironmentStateSurvey): void => {
// Check if the survey should be displayed based on displayPercentage
if (survey.displayPercentage) {
const shouldDisplaySurvey = shouldDisplayBasedOnPercentage(survey.displayPercentage);

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console -- Required for error logging */
import AsyncStorage from "@react-native-async-storage/async-storage";
import { type Result, err, ok, wrapThrowsAsync } from "@formbricks/types/error-handlers";
import type { TJsAppConfigUpdateInput, TJsRNConfig } from "@formbricks/types/js";
import type { TJsRNConfig, TJsRNConfigUpdateInput } from "@formbricks/types/js";
import { RN_ASYNC_STORAGE_KEY } from "../../../js-core/src/lib/constants";
export class RNConfig {
@@ -27,7 +27,7 @@ export class RNConfig {
return RNConfig.instance;
}
public update(newConfig: TJsAppConfigUpdateInput): void {
public update(newConfig: TJsRNConfigUpdateInput): void {
this.config = {
...this.config,
...newConfig,

View File

@@ -1,4 +1,4 @@
import { type TJsReactNativeConfigInput } from "@formbricks/types/js";
import { type TJsRNConfigInput } from "@formbricks/types/js";
import { ErrorHandler } from "../../../js-core/src/lib/errors";
import { Logger } from "../../../js-core/src/lib/logger";
import { trackCodeAction } from "./actions";
@@ -9,7 +9,7 @@ const logger = Logger.getInstance();
logger.debug("Create command queue");
const queue = new CommandQueue();
export const init = async (initConfig: TJsReactNativeConfigInput): Promise<void> => {
export const init = async (initConfig: TJsRNConfigInput): Promise<void> => {
ErrorHandler.init(initConfig.errorHandler);
queue.add(initialize, false, initConfig);
await queue.wait();

View File

@@ -1,5 +1,5 @@
import { type TAttributes } from "@formbricks/types/attributes";
import { type TJsRNConfig, type TJsReactNativeConfigInput } from "@formbricks/types/js";
import { type TJsRNConfig, type TJsRNConfigInput } from "@formbricks/types/js";
import {
ErrorHandler,
type MissingFieldError,
@@ -24,7 +24,7 @@ export const setIsInitialize = (state: boolean): void => {
};
export const initialize = async (
c: TJsReactNativeConfigInput
c: TJsRNConfigInput
): Promise<Result<void, MissingFieldError | NetworkError | MissingPersonError>> => {
if (isInitialized) {
logger.debug("Already initialized, skipping initialization.");

View File

@@ -1,10 +1,13 @@
import { type TSurvey } from "@formbricks/types/surveys/types";
import { type TJsEnvironmentStateSurvey } from "@formbricks/types/js";
type Listener = (state: TSurvey | null, prevSurvey: TSurvey | null) => void;
type Listener = (
state: TJsEnvironmentStateSurvey | null,
prevSurvey: TJsEnvironmentStateSurvey | null
) => void;
export class SurveyStore {
private static instance: SurveyStore | undefined;
private survey: TSurvey | null = null;
private survey: TJsEnvironmentStateSurvey | null = null;
private listeners = new Set<Listener>();
static getInstance(): SurveyStore {
@@ -14,11 +17,11 @@ export class SurveyStore {
return SurveyStore.instance;
}
public getSurvey(): TSurvey | null {
public getSurvey(): TJsEnvironmentStateSurvey | null {
return this.survey;
}
public setSurvey(survey: TSurvey): void {
public setSurvey(survey: TJsEnvironmentStateSurvey): void {
const prevSurvey = this.survey;
if (prevSurvey !== survey) {
this.survey = survey;

View File

@@ -4,7 +4,7 @@
import type { TAttributes } from "@formbricks/types/attributes";
import { type Result, err, ok } from "@formbricks/types/error-handlers";
import type { NetworkError } from "@formbricks/types/errors";
import type { TJsAppState, TJsAppStateSync, TJsRNSyncParams } from "@formbricks/types/js";
import type { TJsRNState, TJsRNStateSync, TJsRNSyncParams } from "@formbricks/types/js";
import { Logger } from "../../../js-core/src/lib/logger";
import type { RNConfig } from "./config";
@@ -15,7 +15,7 @@ let syncIntervalId: number | null = null;
const syncWithBackend = async (
{ apiHost, environmentId, userId }: TJsRNSyncParams,
noCache: boolean
): Promise<Result<TJsAppStateSync, NetworkError>> => {
): Promise<Result<TJsRNStateSync, NetworkError>> => {
try {
const fetchOptions: RequestInit = {};
@@ -37,10 +37,10 @@ const syncWithBackend = async (
message: "Error syncing with backend",
url,
responseMessage: jsonRes.message,
}) as Result<TJsAppStateSync, NetworkError>;
}) as Result<TJsRNStateSync, NetworkError>;
}
const data = (await response.json()) as { data: TJsAppStateSync };
const data = (await response.json()) as { data: TJsRNStateSync };
const { data: state } = data;
return ok(state);
@@ -63,7 +63,7 @@ export const sync = async (params: TJsRNSyncParams, appConfig: RNConfig, noCache
attributes.language = syncResult.data.language;
}
const state: TJsAppState = {
const state: TJsRNState = {
surveys: syncResult.data.surveys,
actionClasses: syncResult.data.actionClasses,
project: syncResult.data.project,

View File

@@ -10,10 +10,9 @@ import { SurveyState } from "@formbricks/lib/surveyState";
import { getStyling } from "@formbricks/lib/utils/styling";
import type { SurveyInlineProps } from "@formbricks/types/formbricks-surveys";
import { ZJsRNWebViewOnMessageData } from "@formbricks/types/js";
import type { TJsFileUploadParams } from "@formbricks/types/js";
import type { TJsEnvironmentStateSurvey, TJsFileUploadParams } from "@formbricks/types/js";
import type { TResponseUpdate } from "@formbricks/types/responses";
import type { TUploadFileConfig } from "@formbricks/types/storage";
import type { TSurvey } from "@formbricks/types/surveys/types";
import { Logger } from "../../js-core/src/lib/logger";
import { getDefaultLanguageCode, getLanguageCode } from "../../js-core/src/lib/utils";
import { appConfig } from "./lib/config";
@@ -27,7 +26,7 @@ logger.configure({ logLevel: "debug" });
const surveyStore = SurveyStore.getInstance();
interface SurveyWebViewProps {
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
}
export function SurveyWebView({ survey }: SurveyWebViewProps): JSX.Element | undefined {

View File

@@ -7,11 +7,12 @@ import { ScrollableContainer } from "@/components/wrappers/ScrollableContainer";
import { replaceRecallInfo } from "@/lib/recall";
import { useEffect } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TResponseData, TResponseVariables } from "@formbricks/types/responses";
import { TSurvey, TSurveyEndScreenCard, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
import { TSurveyEndScreenCard, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
interface EndingCardProps {
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
endingCard: TSurveyEndScreenCard | TSurveyRedirectUrlCard;
isRedirectDisabled: boolean;
isResponseSendingFinished: boolean;

View File

@@ -1,10 +1,11 @@
import { calculateElementIdx } from "@/lib/utils";
import { useCallback, useMemo } from "preact/hooks";
import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { Progress } from "./Progress";
interface ProgressBarProps {
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
questionId: TSurveyQuestionId;
}

View File

@@ -14,13 +14,14 @@ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import type { JSX } from "react";
import { evaluateLogic, performActions } from "@formbricks/lib/surveyLogic/utils";
import { SurveyBaseProps } from "@formbricks/types/formbricks-surveys";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import type {
TResponseData,
TResponseDataValue,
TResponseTtc,
TResponseVariables,
} from "@formbricks/types/responses";
import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface VariableStackEntry {
questionId: TSurveyQuestionId;
@@ -53,7 +54,7 @@ export const Survey = ({
fullSizeCards = false,
autoFocus,
}: SurveyBaseProps) => {
const [localSurvey, setlocalSurvey] = useState<TSurvey>(survey);
const [localSurvey, setlocalSurvey] = useState<TJsEnvironmentStateSurvey>(survey);
// Update localSurvey when the survey prop changes (it changes in case of survey editor)
useEffect(() => {

View File

@@ -4,8 +4,9 @@ import { replaceRecallInfo } from "@/lib/recall";
import { calculateElementIdx } from "@/lib/utils";
import { useEffect } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TResponseData, TResponseTtc, TResponseVariables } from "@formbricks/types/responses";
import { TI18nString, TSurvey } from "@formbricks/types/surveys/types";
import { TI18nString } from "@formbricks/types/surveys/types";
import { Headline } from "./Headline";
import { HtmlBody } from "./HtmlBody";
@@ -15,7 +16,7 @@ interface WelcomeCardProps {
fileUrl?: string;
buttonLabel?: TI18nString;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
languageCode: string;
responseCount?: number;
autoFocusEnabled: boolean;

View File

@@ -1,10 +1,10 @@
import { AutoCloseProgressBar } from "@/components/general/AutoCloseProgressBar";
import React from "preact/compat";
import { useEffect, useRef, useState } from "preact/hooks";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
interface AutoCloseProps {
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
onClose: () => void;
offset: number;
children: React.ReactNode;

View File

@@ -1,9 +1,10 @@
import { cn } from "@/lib/utils";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import type { JSX } from "react";
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TProjectStyling } from "@formbricks/types/project";
import { TCardArrangementOptions } from "@formbricks/types/styling";
import { TSurvey, TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/surveys/types";
import { TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/surveys/types";
// offset = 0 -> Current question card
// offset < 0 -> Question cards that are already answered
@@ -11,7 +12,7 @@ import { TSurvey, TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/su
interface StackedCardsContainerProps {
cardArrangement: TCardArrangementOptions;
currentQuestionId: TSurveyQuestionId;
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
getCardContent: (questionIdxTemp: number, offset: number) => JSX.Element | undefined;
styling: TProjectStyling | TSurveyStyling;
setQuestionId: (questionId: TSurveyQuestionId) => void;

View File

@@ -1,6 +1,6 @@
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import {
TShuffleOption,
TSurvey,
TSurveyLogic,
TSurveyLogicAction,
TSurveyQuestion,
@@ -61,7 +61,7 @@ export const getShuffledChoicesIds = (
return shuffledChoices.map((choice) => choice.id);
};
export const calculateElementIdx = (survey: TSurvey, currentQustionIdx: number): number => {
export const calculateElementIdx = (survey: TJsEnvironmentStateSurvey, currentQustionIdx: number): number => {
const currentQuestion = survey.questions[currentQustionIdx];
const surveyLength = survey.questions.length;
const middleIdx = Math.floor(surveyLength / 2);

View File

@@ -1,11 +1,11 @@
import type { TJsFileUploadParams } from "./js";
import type { TJsEnvironmentStateSurvey, TJsFileUploadParams } from "./js";
import type { TProjectStyling } from "./project";
import type { TResponseData, TResponseUpdate } from "./responses";
import type { TUploadFileConfig } from "./storage";
import type { TSurvey, TSurveyStyling } from "./surveys/types";
import type { TSurveyStyling } from "./surveys/types";
export interface SurveyBaseProps {
survey: TSurvey;
survey: TJsEnvironmentStateSurvey;
styling: TSurveyStyling | TProjectStyling;
isBrandingEnabled: boolean;
getSetIsError?: (getSetError: (value: boolean) => void) => void;

View File

@@ -12,71 +12,76 @@ export const ZJsPerson = z.object({
userId: z.string().optional(),
});
export type TJsPerson = z.infer<typeof ZJsPerson>;
// ZSurvey is a refinement, so to extend it to ZSurveyWithTriggers, we need to extend the innerType and then apply the same refinements.
const ZSurveyWithTriggers = ZSurvey.innerType()
.extend({
triggers: z.array(ZActionClass).or(z.array(z.string())),
export const ZJsEnvironmentStateSurvey = ZSurvey.innerType()
.pick({
id: true,
name: true,
welcomeCard: true,
questions: true,
variables: true,
type: true,
showLanguageSwitch: true,
languages: true,
endings: true,
autoClose: true,
styling: true,
status: true,
segment: true,
recontactDays: true,
displayLimit: true,
displayOption: true,
hiddenFields: true,
triggers: true,
displayPercentage: true,
delay: true,
projectOverwrites: true,
})
.superRefine(ZSurvey._def.effect.type === "refinement" ? ZSurvey._def.effect.refinement : () => null);
export type TSurveyWithTriggers = z.infer<typeof ZSurveyWithTriggers>;
export type TJsEnvironmentStateSurvey = z.infer<typeof ZJsEnvironmentStateSurvey>;
export const ZJSWebsiteStateDisplay = z.object({
createdAt: z.date(),
surveyId: z.string().cuid2(),
responded: z.boolean(),
});
export type TJSWebsiteStateDisplay = z.infer<typeof ZJSWebsiteStateDisplay>;
export const ZJsAppStateSync = z.object({
export const ZJsRNStateSync = z.object({
person: ZJsPerson.nullish(),
userId: z.string().optional(),
surveys: z.array(ZSurvey),
surveys: z.array(ZJsEnvironmentStateSurvey),
actionClasses: z.array(ZActionClass),
project: ZProject,
language: z.string().optional(),
});
export type TJsAppStateSync = z.infer<typeof ZJsAppStateSync>;
export type TJsRNStateSync = z.infer<typeof ZJsRNStateSync>;
export const ZJsAppState = z.object({
export const ZJsRNState = z.object({
attributes: ZAttributes,
surveys: z.array(ZSurvey),
surveys: z.array(ZJsEnvironmentStateSurvey),
actionClasses: z.array(ZActionClass),
project: ZProject,
});
export type TJsAppState = z.infer<typeof ZJsAppState>;
export type TJsRNState = z.infer<typeof ZJsRNState>;
export const ZJsAppConfigUpdateInput = z.object({
export const ZJsRNConfigUpdateInput = z.object({
environmentId: z.string().cuid2(),
apiHost: z.string(),
userId: z.string(),
state: ZJsAppState,
state: ZJsRNState,
expiresAt: z.date(),
status: z.enum(["success", "error"]).optional(),
});
export type TJsAppConfigUpdateInput = z.infer<typeof ZJsAppConfigUpdateInput>;
export type TJsRNConfigUpdateInput = z.infer<typeof ZJsRNConfigUpdateInput>;
export const ZJsRNConfig = z.object({
environmentId: z.string().cuid(),
apiHost: z.string(),
userId: z.string(),
state: ZJsAppState,
state: ZJsRNState,
expiresAt: z.date(),
status: z.enum(["success", "error"]).optional(),
});
export type TJsRNConfig = z.infer<typeof ZJsRNConfig>;
export const ZJsWebsiteStateSync = ZJsAppStateSync.omit({ person: true });
export type TJsWebsiteStateSync = z.infer<typeof ZJsWebsiteStateSync>;
export const ZJsRNSyncParams = z.object({
environmentId: z.string().cuid(),
apiHost: z.string(),
@@ -85,23 +90,34 @@ export const ZJsRNSyncParams = z.object({
});
export type TJsRNSyncParams = z.infer<typeof ZJsRNSyncParams>;
export const ZJsWebsiteState = z.object({
surveys: z.array(ZSurvey),
actionClasses: z.array(ZActionClass),
project: ZProject,
displays: z.array(ZJSWebsiteStateDisplay),
attributes: ZAttributes.optional(),
export const ZJsEnvironmentStateActionClass = ZActionClass.pick({
id: true,
key: true,
type: true,
name: true,
noCodeConfig: true,
});
export type TJsWebsiteState = z.infer<typeof ZJsWebsiteState>;
export type TJsEnvironmentStateActionClass = z.infer<typeof ZJsEnvironmentStateActionClass>;
export const ZJsEnvironmentStateProject = ZProject.pick({
id: true,
recontactDays: true,
clickOutsideClose: true,
darkOverlay: true,
placement: true,
inAppSurveyBranding: true,
styling: true,
});
export type TJsEnvironmentStateProject = z.infer<typeof ZJsEnvironmentStateProject>;
export const ZJsEnvironmentState = z.object({
expiresAt: z.date(),
data: z.object({
surveys: z.array(ZSurvey),
actionClasses: z.array(ZActionClass),
project: ZProject,
surveys: z.array(ZJsEnvironmentStateSurvey),
actionClasses: z.array(ZJsEnvironmentStateActionClass),
project: ZJsEnvironmentStateProject,
}),
});
@@ -143,7 +159,7 @@ export const ZJsConfig = z.object({
apiHost: z.string(),
environmentState: ZJsEnvironmentState,
personState: ZJsPersonState,
filteredSurveys: z.array(ZSurvey).default([]),
filteredSurveys: z.array(ZJsEnvironmentStateSurvey).default([]),
attributes: z.record(z.string()),
status: z.object({
value: z.enum(["success", "error"]),
@@ -164,15 +180,6 @@ export const ZJsConfigUpdateInput = ZJsConfig.omit({ status: true }).extend({
export type TJsConfigUpdateInput = z.infer<typeof ZJsConfigUpdateInput>;
export const ZJsWebsiteConfigInput = z.object({
environmentId: z.string().cuid2(),
apiHost: z.string(),
errorHandler: z.function().args(z.any()).returns(z.void()).optional(),
attributes: z.record(z.string()).optional(),
});
export type TJsWebsiteConfigInput = z.infer<typeof ZJsWebsiteConfigInput>;
export const ZJsConfigInput = z.object({
environmentId: z.string().cuid2(),
apiHost: z.string(),
@@ -183,8 +190,8 @@ export const ZJsConfigInput = z.object({
export type TJsConfigInput = z.infer<typeof ZJsConfigInput>;
export const ZJsReactNativeConfigInput = ZJsConfigInput.omit({ userId: true }).extend({ userId: z.string() });
export type TJsReactNativeConfigInput = z.infer<typeof ZJsReactNativeConfigInput>;
export const ZJsRNConfigInput = ZJsConfigInput.omit({ userId: true }).extend({ userId: z.string() });
export type TJsRNConfigInput = z.infer<typeof ZJsRNConfigInput>;
export const ZJsPeopleUserIdInput = z.object({
environmentId: z.string().cuid2(),

827
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff