Files
calendar/apps/web/app/api/routing-forms/queued-response/route.ts
T
22f136d19b feat: Headless router - queue recording booking response (#21805)
* Add queued booking response table

* Create `RoutingFormResponseRepository`

* Pass `queueFormResponse` param

* Queue up form response if param is passed

* Forward queued form response parma to booker

* Pass `queuedFormResponse` from booker to `handleNewBooking`

* Write queued routing form response

* Type fixes

* Clean up

* Allow dry run to work which wont have any QueuedFormResponse or FormResponse

* Support passing the time when the modal was actually shown to the user and consider that time as the time of form submission

* fix ts error

* Queue -> Response through separate endpoint that would be triggered by
embed

* Make queueResponseId a non-guessable uid

* Change queueFormResponse query param

* fix ts error

* Support useQueuedResponse to record new response data

* revert handleNewBooking

* Remove dead code formResponse

* Refactor use repository

* Unify migration files

* refactor: moved api endpoint to app dir

Signed-off-by: Omar López <zomars@me.com>

* Update formResponse.ts

* Refactor use-queued-response for test

* Add tests

* Fix ts error and unit test. recordFormResponse cant return nullish response

* fix schema

* feat: Support full reuse of preloaded iframe (#21803)

* feat: support updating cal video settings in API v2 (#21784)

* feat: support updating cal video settings in API v2

* chore: update descriptio

* feat: support create event type

* test: add test for updating event type

* test: add test for create event type

* chore: undo openapi

* chore: bump libraries

* Revert "chore: bump libraries"

This reverts commit bdf36d09b021fc531497a7b7ea66ab9c52b7d136.

* chore: bump libraries

---------

Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>

* fix tests and ts

* Fix tests

* wip-useQueuedResponseEndpoint

* Add one more test

* Change queueFormResponse query param

* wip

* Support useQueuedResponse to record new response data

* Use the update useQueuedResponse endpoint

* self-review addressed

* Use queuedResponse if available in slots/utils

* Add documentation

* Remove use-queued-response from critical-path

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>

* Update schema.prisma

* refactor: renamed to avoid react hooks confusion

Signed-off-by: Omar López <zomars@me.com>

---------

Signed-off-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2025-06-17 12:11:49 -03:00

111 lines
3.4 KiB
TypeScript

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { z, ZodError } from "zod";
import { onSubmissionOfFormResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
import { getResponseToStore } from "@calcom/app-store/routing-forms/lib/getResponseToStore";
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
import { defaultResponderForAppDir } from "../../defaultResponderForAppDir";
const queuedResponseSchema = z.object({
queuedFormResponseId: z.string(),
params: z.record(z.string(), z.string().or(z.array(z.string()))),
});
export const queuedResponseHandler = async ({
queuedFormResponseId,
params,
}: {
queuedFormResponseId: string;
params: Record<string, string | string[]>;
}) => {
// Get the queued response
const queuedFormResponse = await RoutingFormResponseRepository.getQueuedFormResponseFromId(
queuedFormResponseId
);
if (!queuedFormResponse) {
return {
formResponseId: null,
message: "Already processed",
};
}
const serializableForm = await getSerializableForm({
form: queuedFormResponse.form,
});
if (!serializableForm.fields) {
throw new Error("Form has no fields");
}
const response = getResponseToStore({
formFields: serializableForm.fields,
fieldsResponses: params,
});
const formResponse = await RoutingFormResponseRepository.recordFormResponse({
formId: queuedFormResponse.formId,
queuedFormResponseId: queuedFormResponse.id,
// We record new response here as that might be different from the queued response depending on if the user changed something in b/w before clicking CTA and that something wasn't prerendered
response,
// We use the queuedFormResponse's chosenRouteId because that is what decided routed team members
chosenRouteId: queuedFormResponse.chosenRouteId,
});
const chosenRoute = serializableForm.routes?.find((r) => r.id === queuedFormResponse.chosenRouteId);
await onSubmissionOfFormResponse({
form: {
...queuedFormResponse.form,
...serializableForm,
},
formResponseInDb: formResponse,
chosenRouteAction: chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : null) : null,
});
return {
formResponseId: formResponse.id,
message: "Processed",
};
};
export const handler = async (req: NextRequest) => {
try {
const body = await req.json();
const { params, queuedFormResponseId } = queuedResponseSchema.parse(body);
const result = await queuedResponseHandler({
queuedFormResponseId,
params,
});
return NextResponse.json({ status: "success", data: result });
} catch (error) {
if (error instanceof ZodError) {
logger.error("Invalid input", safeStringify(error));
return NextResponse.json(
{
status: "error",
message: "Invalid input",
},
{ status: 400 }
);
}
logger.error("Error in queuedResponseHandler", safeStringify(error));
return NextResponse.json(
{
status: "error",
message: "Internal server error",
},
{ status: 500 }
);
}
};
export const POST = defaultResponderForAppDir(handler);