mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
feat: surveys package integration with v2 apis (#4882)
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { contactCache } from "@/lib/cache/contact";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
|
||||
export const doesContactExist = reactCache(
|
||||
(id: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
const contact = await prisma.contact.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return !!contact;
|
||||
},
|
||||
[`doesContactExistDisplaysApiV2-${id}`],
|
||||
{
|
||||
tags: [contactCache.tag.byId(id)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
TDisplayCreateInputV2,
|
||||
ZDisplayCreateInputV2,
|
||||
} from "@/app/api/v2/client/[environmentId]/displays/types/display";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { displayCache } from "@formbricks/lib/display/cache";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { doesContactExist } from "./contact";
|
||||
|
||||
export const createDisplay = async (displayInput: TDisplayCreateInputV2): Promise<{ id: string }> => {
|
||||
validateInputs([displayInput, ZDisplayCreateInputV2]);
|
||||
|
||||
const { environmentId, contactId, surveyId } = displayInput;
|
||||
|
||||
try {
|
||||
const contactExists = contactId ? await doesContactExist(contactId) : false;
|
||||
|
||||
const display = await prisma.display.create({
|
||||
data: {
|
||||
survey: {
|
||||
connect: {
|
||||
id: surveyId,
|
||||
},
|
||||
},
|
||||
|
||||
...(contactExists && {
|
||||
contact: {
|
||||
connect: {
|
||||
id: contactId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
select: { id: true, contactId: true, surveyId: true },
|
||||
});
|
||||
|
||||
displayCache.revalidate({
|
||||
id: display.id,
|
||||
contactId: display.contactId,
|
||||
surveyId: display.surveyId,
|
||||
environmentId,
|
||||
});
|
||||
|
||||
return display;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,55 @@
|
||||
import { OPTIONS, POST } from "@/app/api/v1/client/[environmentId]/displays/route";
|
||||
import { ZDisplayCreateInputV2 } from "@/app/api/v2/client/[environmentId]/displays/types/display";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { createDisplay } from "./lib/display";
|
||||
|
||||
export { OPTIONS, POST };
|
||||
interface Context {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const OPTIONS = async (): Promise<Response> => {
|
||||
return responses.successResponse({}, true);
|
||||
};
|
||||
|
||||
export const POST = async (request: Request, context: Context): Promise<Response> => {
|
||||
const params = await context.params;
|
||||
const jsonInput = await request.json();
|
||||
const inputValidation = ZDisplayCreateInputV2.safeParse({
|
||||
...jsonInput,
|
||||
environmentId: params.environmentId,
|
||||
});
|
||||
|
||||
if (!inputValidation.success) {
|
||||
return responses.badRequestResponse(
|
||||
"Fields are missing or incorrectly formatted",
|
||||
transformErrorToDetails(inputValidation.error),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (inputValidation.data.contactId) {
|
||||
const isContactsEnabled = await getIsContactsEnabled();
|
||||
if (!isContactsEnabled) {
|
||||
return responses.forbiddenResponse("User identification is only available for enterprise users.", true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await createDisplay(inputValidation.data);
|
||||
|
||||
await capturePosthogEnvironmentEvent(inputValidation.data.environmentId, "display created");
|
||||
return responses.successResponse(response, true);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZDisplayCreateInput } from "@formbricks/types/displays";
|
||||
|
||||
export const ZDisplayCreateInputV2 = ZDisplayCreateInput.omit({ userId: true }).extend({
|
||||
contactId: ZId.optional(),
|
||||
});
|
||||
|
||||
export type TDisplayCreateInputV2 = z.infer<typeof ZDisplayCreateInputV2>;
|
||||
@@ -18,6 +18,7 @@ export class UserAPI {
|
||||
expiresAt: Date | null;
|
||||
data: {
|
||||
userId: string | null;
|
||||
contactId: string | null;
|
||||
segments: string[];
|
||||
displays: { surveyId: string; createdAt: Date }[];
|
||||
responses: string[];
|
||||
@@ -36,7 +37,7 @@ export class UserAPI {
|
||||
attributes[key] = String(userUpdateInput.attributes[key]);
|
||||
}
|
||||
|
||||
return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/user`, "POST", {
|
||||
return makeRequest(this.apiHost, `/api/v2/client/${this.environmentId}/user`, "POST", {
|
||||
userId: userUpdateInput.userId,
|
||||
attributes,
|
||||
});
|
||||
|
||||
@@ -4,11 +4,7 @@ import { type ApiErrorResponse } from "@formbricks/types/errors";
|
||||
import { type TJsConfig, type TJsConfigInput } from "@formbricks/types/js";
|
||||
import { updateAttributes } from "./attributes";
|
||||
import { Config } from "./config";
|
||||
import {
|
||||
JS_LOCAL_STORAGE_KEY,
|
||||
LEGACY_JS_APP_LOCAL_STORAGE_KEY,
|
||||
LEGACY_JS_WEBSITE_LOCAL_STORAGE_KEY,
|
||||
} from "./constants";
|
||||
import { JS_LOCAL_STORAGE_KEY } from "./constants";
|
||||
import { fetchEnvironmentState } from "./environment-state";
|
||||
import {
|
||||
ErrorHandler,
|
||||
@@ -34,105 +30,21 @@ export const setIsInitialized = (value: boolean): void => {
|
||||
isInitialized = value;
|
||||
};
|
||||
|
||||
const migrateLocalStorage = (): { changed: boolean; newState?: TJsConfig } => {
|
||||
const oldWebsiteConfig = localStorage.getItem(LEGACY_JS_WEBSITE_LOCAL_STORAGE_KEY);
|
||||
const oldAppConfig = localStorage.getItem(LEGACY_JS_APP_LOCAL_STORAGE_KEY);
|
||||
// If the js sdk is being used with user identification but there is no contactId, we can just resync
|
||||
export const migrateUserStateAddContactId = (): { changed: boolean } => {
|
||||
const existingConfigString = localStorage.getItem(JS_LOCAL_STORAGE_KEY);
|
||||
|
||||
if (oldWebsiteConfig) {
|
||||
localStorage.removeItem(LEGACY_JS_WEBSITE_LOCAL_STORAGE_KEY);
|
||||
const parsedOldConfig = JSON.parse(oldWebsiteConfig) as Partial<{
|
||||
environmentId: string;
|
||||
apiHost: string;
|
||||
environmentState: TJsConfig["environmentState"];
|
||||
personState: TJsConfig["personState"];
|
||||
filteredSurveys: TJsConfig["filteredSurveys"];
|
||||
}>;
|
||||
if (existingConfigString) {
|
||||
const existingConfig = JSON.parse(existingConfigString) as Partial<TJsConfig>;
|
||||
|
||||
if (
|
||||
parsedOldConfig.environmentId &&
|
||||
parsedOldConfig.apiHost &&
|
||||
parsedOldConfig.environmentState &&
|
||||
parsedOldConfig.personState &&
|
||||
parsedOldConfig.filteredSurveys
|
||||
) {
|
||||
const newLocalStorageConfig = { ...parsedOldConfig };
|
||||
|
||||
return {
|
||||
changed: true,
|
||||
newState: newLocalStorageConfig as TJsConfig,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data could be undefined
|
||||
if (existingConfig.personState?.data?.contactId) {
|
||||
return { changed: false };
|
||||
}
|
||||
}
|
||||
|
||||
if (oldAppConfig) {
|
||||
localStorage.removeItem(LEGACY_JS_APP_LOCAL_STORAGE_KEY);
|
||||
const parsedOldConfig = JSON.parse(oldAppConfig) as Partial<{
|
||||
environmentId: string;
|
||||
apiHost: string;
|
||||
environmentState: TJsConfig["environmentState"];
|
||||
personState: TJsConfig["personState"];
|
||||
filteredSurveys: TJsConfig["filteredSurveys"];
|
||||
}>;
|
||||
|
||||
if (
|
||||
parsedOldConfig.environmentId &&
|
||||
parsedOldConfig.apiHost &&
|
||||
parsedOldConfig.environmentState &&
|
||||
parsedOldConfig.personState &&
|
||||
parsedOldConfig.filteredSurveys
|
||||
) {
|
||||
return {
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
changed: false,
|
||||
};
|
||||
};
|
||||
|
||||
const migrateProductToProject = (): { changed: boolean; newState?: TJsConfig } => {
|
||||
const existingConfig = localStorage.getItem(JS_LOCAL_STORAGE_KEY);
|
||||
|
||||
if (existingConfig) {
|
||||
const parsedConfig = JSON.parse(existingConfig) as TJsConfig;
|
||||
|
||||
// @ts-expect-error - product is not in the type
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- environmentState could be undefined in an error state
|
||||
if (parsedConfig.environmentState?.data?.product) {
|
||||
const { environmentState: _, filteredSurveys, ...restConfig } = parsedConfig;
|
||||
|
||||
const fixedFilteredSurveys = filteredSurveys.map((survey) => {
|
||||
// @ts-expect-error - productOverwrites is not in the type
|
||||
const { productOverwrites, ...rest } = survey;
|
||||
return {
|
||||
...rest,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- productOverwrites is not in the type
|
||||
projectOverwrites: productOverwrites,
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-expect-error - product is not in the type
|
||||
const { product, ...rest } = parsedConfig.environmentState.data;
|
||||
|
||||
const newLocalStorageConfig = {
|
||||
...restConfig,
|
||||
environmentState: {
|
||||
...parsedConfig.environmentState,
|
||||
data: {
|
||||
...rest,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- product is not in the type
|
||||
project: product,
|
||||
},
|
||||
},
|
||||
filteredSurveys: fixedFilteredSurveys,
|
||||
};
|
||||
|
||||
return {
|
||||
changed: true,
|
||||
newState: newLocalStorageConfig,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data could be undefined
|
||||
if (!existingConfig.personState?.data?.contactId && existingConfig.personState?.data?.userId) {
|
||||
return { changed: true };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,28 +61,11 @@ export const initialize = async (
|
||||
|
||||
let config = Config.getInstance();
|
||||
|
||||
const { changed, newState } = migrateLocalStorage();
|
||||
const { changed } = migrateUserStateAddContactId();
|
||||
|
||||
if (changed) {
|
||||
config.resetConfig();
|
||||
config = Config.getInstance();
|
||||
|
||||
// If the js sdk is being used for non identified users, and we have a new state to update to after migrating, we update the state
|
||||
// otherwise, we just sync again!
|
||||
if (!configInput.userId && newState) {
|
||||
config.update(newState);
|
||||
}
|
||||
}
|
||||
|
||||
const { changed: migrated, newState: updatedLocalState } = migrateProductToProject();
|
||||
|
||||
if (migrated) {
|
||||
config.resetConfig();
|
||||
config = Config.getInstance();
|
||||
|
||||
if (updatedLocalState) {
|
||||
config.update(updatedLocalState);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInitialized) {
|
||||
|
||||
@@ -88,7 +88,7 @@ const renderWidget = async (
|
||||
formbricksSurveys.renderSurvey({
|
||||
apiHost: config.get().apiHost,
|
||||
environmentId: config.get().environmentId,
|
||||
userId: config.get().personState.data.userId ?? undefined,
|
||||
contactId: config.get().personState.data.contactId ?? undefined,
|
||||
action,
|
||||
survey,
|
||||
isBrandingEnabled,
|
||||
|
||||
@@ -105,7 +105,7 @@ export function SurveyWebView({ survey }: SurveyWebViewProps): JSX.Element | und
|
||||
html: renderHtml({
|
||||
apiHost: appConfig.get().appUrl,
|
||||
environmentId: appConfig.get().environmentId,
|
||||
userId: appConfig.get().user.data.userId ?? undefined,
|
||||
contactId: appConfig.get().user.data.contactId ?? undefined,
|
||||
survey,
|
||||
isBrandingEnabled,
|
||||
styling,
|
||||
|
||||
@@ -27,12 +27,39 @@ export const setIsSetup = (state: boolean): void => {
|
||||
isSetup = state;
|
||||
};
|
||||
|
||||
export const migrateUserStateAddContactId = async (): Promise<{ changed: boolean }> => {
|
||||
const existingConfigString = await AsyncStorage.getItem(RN_ASYNC_STORAGE_KEY);
|
||||
|
||||
if (existingConfigString) {
|
||||
const existingConfig = JSON.parse(existingConfigString) as Partial<TConfig>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data could be undefined
|
||||
if (existingConfig.user?.data?.contactId) {
|
||||
return { changed: false };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data could be undefined
|
||||
if (!existingConfig.user?.data?.contactId && existingConfig.user?.data?.userId) {
|
||||
return { changed: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { changed: false };
|
||||
};
|
||||
|
||||
export const setup = async (
|
||||
configInput: TConfigInput
|
||||
): Promise<Result<void, MissingFieldError | NetworkError | MissingPersonError>> => {
|
||||
const appConfig = RNConfig.getInstance();
|
||||
let appConfig = RNConfig.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
|
||||
const { changed } = await migrateUserStateAddContactId();
|
||||
|
||||
if (changed) {
|
||||
await appConfig.resetConfig();
|
||||
appConfig = RNConfig.getInstance();
|
||||
}
|
||||
|
||||
if (isSetup) {
|
||||
logger.debug("Already set up, skipping setup.");
|
||||
return okVoid();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// utils.test.ts
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { mockProjectId, mockSurveyId } from "@/lib/common/tests/__mocks__/config.mock";
|
||||
import {
|
||||
diffInDays,
|
||||
@@ -16,7 +17,6 @@ import type {
|
||||
TSurveyStyling,
|
||||
TUserState,
|
||||
} from "@/types/config";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const mockSurveyId1 = "e3kxlpnzmdp84op9qzxl9olj";
|
||||
const mockSurveyId2 = "qo9rwjmms42hoy3k85fp8vgu";
|
||||
@@ -120,6 +120,7 @@ describe("utils.ts", () => {
|
||||
expiresAt: null,
|
||||
data: {
|
||||
userId: null,
|
||||
contactId: null,
|
||||
segments: [],
|
||||
displays: [],
|
||||
responses: [],
|
||||
|
||||
@@ -7,6 +7,7 @@ export const DEFAULT_USER_STATE_NO_USER_ID: TUserState = {
|
||||
expiresAt: null,
|
||||
data: {
|
||||
userId: null,
|
||||
contactId: null,
|
||||
segments: [],
|
||||
displays: [],
|
||||
responses: [],
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface TUserState {
|
||||
expiresAt: Date | null;
|
||||
data: {
|
||||
userId: string | null;
|
||||
contactId: string | null;
|
||||
segments: string[];
|
||||
displays: { surveyId: string; createdAt: Date }[];
|
||||
responses: string[];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type TJsFileUploadParams } from "../../../types/js";
|
||||
import type { TEnvironmentStateSurvey, TProjectStyling, TSurveyStyling } from "@/types/config";
|
||||
import type { TResponseData, TResponseUpdate } from "@/types/response";
|
||||
import type { TFileUploadParams, TUploadFileConfig } from "@/types/storage";
|
||||
@@ -37,15 +38,20 @@ export interface SurveyInlineProps extends SurveyBaseProps {
|
||||
}
|
||||
|
||||
export interface SurveyContainerProps extends Omit<SurveyBaseProps, "onFileUpload"> {
|
||||
apiHost: string;
|
||||
environmentId: string;
|
||||
apiHost?: string;
|
||||
environmentId?: string;
|
||||
userId?: string;
|
||||
onDisplayCreated?: () => void;
|
||||
onResponseCreated?: () => void;
|
||||
contactId?: string;
|
||||
onDisplayCreated?: () => void | Promise<void>;
|
||||
onResponseCreated?: () => void | Promise<void>;
|
||||
onFileUpload?: (file: TJsFileUploadParams["file"], config?: TUploadFileConfig) => Promise<string>;
|
||||
onOpenExternalURL?: (url: string) => void | Promise<void>;
|
||||
mode?: "modal" | "inline";
|
||||
containerId?: string;
|
||||
clickOutside?: boolean;
|
||||
darkOverlay?: boolean;
|
||||
placement?: "bottomLeft" | "bottomRight" | "topLeft" | "topRight" | "center";
|
||||
action?: string;
|
||||
singleUseId?: string;
|
||||
singleUseResponseId?: string;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ interface VariableStackEntry {
|
||||
}
|
||||
|
||||
export function Survey({
|
||||
apiHost,
|
||||
environmentId,
|
||||
userId,
|
||||
contactId,
|
||||
mode,
|
||||
survey,
|
||||
styling,
|
||||
isBrandingEnabled,
|
||||
@@ -43,6 +48,9 @@ export function Survey({
|
||||
onClose,
|
||||
onFinished,
|
||||
onRetry,
|
||||
onDisplayCreated,
|
||||
onResponseCreated,
|
||||
onOpenExternalURL,
|
||||
isRedirectDisabled = false,
|
||||
prefillResponseData,
|
||||
skipPrefilled,
|
||||
@@ -58,16 +66,9 @@ export function Survey({
|
||||
shouldResetQuestionId,
|
||||
fullSizeCards = false,
|
||||
autoFocus,
|
||||
apiHost,
|
||||
environmentId,
|
||||
userId,
|
||||
action,
|
||||
onDisplayCreated,
|
||||
onResponseCreated,
|
||||
singleUseId,
|
||||
singleUseResponseId,
|
||||
mode,
|
||||
onOpenExternalURL,
|
||||
}: SurveyContainerProps) {
|
||||
let apiClient: ApiClient | null = null;
|
||||
|
||||
@@ -81,13 +82,13 @@ export function Survey({
|
||||
const surveyState = useMemo(() => {
|
||||
if (apiHost && environmentId) {
|
||||
if (mode === "inline") {
|
||||
return new SurveyState(survey.id, singleUseId, singleUseResponseId, userId);
|
||||
return new SurveyState(survey.id, singleUseId, singleUseResponseId, userId, contactId);
|
||||
}
|
||||
|
||||
return new SurveyState(survey.id, null, null, userId);
|
||||
return new SurveyState(survey.id, null, null, userId, contactId);
|
||||
}
|
||||
return null;
|
||||
}, [survey.id, userId, apiHost, environmentId, singleUseId, singleUseResponseId, mode]);
|
||||
}, [apiHost, environmentId, mode, survey.id, userId, singleUseId, singleUseResponseId, contactId]);
|
||||
|
||||
// Update the responseQueue to use the stored responseId
|
||||
const responseQueue = useMemo(() => {
|
||||
@@ -208,6 +209,7 @@ export function Survey({
|
||||
const display = await apiClient.createDisplay({
|
||||
surveyId: survey.id,
|
||||
...(userId && { userId }),
|
||||
...(contactId && { contactId }),
|
||||
});
|
||||
|
||||
if (!display.ok) {
|
||||
@@ -225,7 +227,7 @@ export function Survey({
|
||||
console.error("error creating display: ", err);
|
||||
}
|
||||
}
|
||||
}, [apiClient, survey, userId, onDisplayCreated, surveyState, responseQueue]);
|
||||
}, [apiClient, surveyState, responseQueue, survey.id, userId, contactId, onDisplayCreated]);
|
||||
|
||||
useEffect(() => {
|
||||
// call onDisplay when component is mounted
|
||||
@@ -382,6 +384,10 @@ export function Survey({
|
||||
const onResponseCreateOrUpdate = useCallback(
|
||||
(responseUpdate: TResponseUpdate) => {
|
||||
if (surveyState && responseQueue) {
|
||||
if (contactId) {
|
||||
surveyState.updateContactId(contactId);
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
surveyState.updateUserId(userId);
|
||||
}
|
||||
@@ -407,7 +413,7 @@ export function Survey({
|
||||
}
|
||||
}
|
||||
},
|
||||
[surveyState, responseQueue, userId, survey, action, hiddenFieldsRecord, onResponseCreated]
|
||||
[surveyState, responseQueue, contactId, userId, survey, action, hiddenFieldsRecord, onResponseCreated]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,15 +16,29 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async createDisplay(
|
||||
displayInput: Omit<TDisplayCreateInput, "environmentId">
|
||||
displayInput: Omit<TDisplayCreateInput, "environmentId"> & { contactId?: string }
|
||||
): Promise<Result<{ id: string }, ApiErrorResponse>> {
|
||||
return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/displays`, "POST", displayInput);
|
||||
const fromV1 = !!displayInput.userId;
|
||||
|
||||
return makeRequest(
|
||||
this.apiHost,
|
||||
`/api/${fromV1 ? "v1" : "v2"}/client/${this.environmentId}/displays`,
|
||||
"POST",
|
||||
displayInput
|
||||
);
|
||||
}
|
||||
|
||||
async createResponse(
|
||||
responseInput: Omit<TResponseInput, "environmentId">
|
||||
responseInput: Omit<TResponseInput, "environmentId"> & { contactId: string | null }
|
||||
): Promise<Result<{ id: string }, ApiErrorResponse>> {
|
||||
return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/responses`, "POST", responseInput);
|
||||
const fromV1 = !!responseInput.userId;
|
||||
|
||||
return makeRequest(
|
||||
this.apiHost,
|
||||
`/api/${fromV1 ? "v1" : "v2"}/client/${this.environmentId}/responses`,
|
||||
"POST",
|
||||
responseInput
|
||||
);
|
||||
}
|
||||
|
||||
async updateResponse({
|
||||
|
||||
@@ -89,6 +89,7 @@ export class ResponseQueue {
|
||||
const response = await this.api.createResponse({
|
||||
...responseUpdate,
|
||||
surveyId: this.surveyState.surveyId,
|
||||
contactId: this.surveyState.contactId || null,
|
||||
userId: this.surveyState.userId || null,
|
||||
singleUseId: this.surveyState.singleUseId || null,
|
||||
data: { ...responseUpdate.data, ...responseUpdate.hiddenFields },
|
||||
|
||||
@@ -4,6 +4,7 @@ export class SurveyState {
|
||||
responseId: string | null = null;
|
||||
displayId: string | null = null;
|
||||
userId: string | null = null;
|
||||
contactId: string | null = null;
|
||||
surveyId: string;
|
||||
responseAcc: TResponseUpdate = { finished: false, data: {}, ttc: {}, variables: {} };
|
||||
singleUseId: string | null;
|
||||
@@ -12,12 +13,14 @@ export class SurveyState {
|
||||
surveyId: string,
|
||||
singleUseId?: string | null,
|
||||
responseId?: string | null,
|
||||
userId?: string | null
|
||||
userId?: string | null,
|
||||
contactId?: string | null
|
||||
) {
|
||||
this.surveyId = surveyId;
|
||||
this.userId = userId ?? null;
|
||||
this.singleUseId = singleUseId ?? null;
|
||||
this.responseId = responseId ?? null;
|
||||
this.contactId = contactId ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +39,8 @@ export class SurveyState {
|
||||
this.surveyId,
|
||||
this.singleUseId ?? undefined,
|
||||
this.responseId ?? undefined,
|
||||
this.userId ?? undefined
|
||||
this.userId ?? undefined,
|
||||
this.contactId ?? undefined
|
||||
);
|
||||
copyInstance.responseId = this.responseId;
|
||||
copyInstance.responseAcc = this.responseAcc;
|
||||
@@ -67,6 +71,14 @@ export class SurveyState {
|
||||
this.userId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the contact ID
|
||||
* @param id - The contact ID
|
||||
*/
|
||||
updateContactId(id: string) {
|
||||
this.contactId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate the responses
|
||||
* @param responseUpdate - The new response data to add
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface SurveyContainerProps extends Omit<SurveyBaseProps, "onFileUploa
|
||||
apiHost?: string;
|
||||
environmentId?: string;
|
||||
userId?: string;
|
||||
contactId?: string;
|
||||
onDisplayCreated?: () => void | Promise<void>;
|
||||
onResponseCreated?: () => void | Promise<void>;
|
||||
onFileUpload?: (file: TJsFileUploadParams["file"], config?: TUploadFileConfig) => Promise<string>;
|
||||
|
||||
Reference in New Issue
Block a user