Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33560ef82 | ||
|
|
84f34b2244 | ||
|
|
086bae03d3 | ||
|
|
4a0a429774 | ||
|
|
c4142ab4e7 | ||
|
|
95f8046db9 | ||
|
|
d8a87e9f04 | ||
|
|
86dcd6f0ff | ||
|
|
411a14dd3d | ||
|
|
03bc8db64a | ||
|
|
fe7fd8bc79 | ||
|
|
92eb41c117 | ||
|
|
a1fca56aac |
@@ -19,5 +19,17 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/app-store/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"warn",
|
||||
{
|
||||
paths: ["@calcom/trpc"],
|
||||
patterns: ["@calcom/trpc/*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Vendored
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.344",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.347",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
@@ -117,6 +117,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
slug: seatedEventTypeSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 3,
|
||||
seatsShowAttendees: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
@@ -53,8 +53,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let apiKeyString: string;
|
||||
|
||||
let seatedEventTypeId: number;
|
||||
let seatedEventTypeIdAttendeesDisabledId: number;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const seatedEventSlugAttendeesDisabled = `seated-bookings-event-type-attendees-disabled-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdSeatedBooking2: CreateSeatedBookingOutput_2024_08_13;
|
||||
@@ -121,6 +123,28 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
);
|
||||
seatedEventTypeId = seatedEvent.id;
|
||||
|
||||
const seatedEventAttendeesDisabled = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `seated-bookings-2024-08-13-event-type-attendees-disabled-${randomString()}`,
|
||||
slug: seatedEventSlugAttendeesDisabled,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: false,
|
||||
seatsShowAvailabilityCount: false,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
metadata: {
|
||||
disableStandardEmails: {
|
||||
all: {
|
||||
attendee: true,
|
||||
host: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user.id
|
||||
);
|
||||
seatedEventTypeIdAttendeesDisabledId = seatedEventAttendeesDisabled.id;
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
@@ -414,6 +438,63 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should book an event type with attendees disabled", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 9, 14, 0, 0)).toISOString(),
|
||||
eventTypeId: seatedEventTypeIdAttendeesDisabledId,
|
||||
attendee: {
|
||||
name: nameAttendeeOne,
|
||||
email: emailAttendeeOne,
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
bookingFieldsResponses: {
|
||||
codingLanguage: "TypeScript",
|
||||
},
|
||||
metadata: {
|
||||
userId: "100",
|
||||
},
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsCreateSeatedBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsCreateSeatedBooking(responseBody.data)) {
|
||||
const data: CreateSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.seatUid).toBeDefined();
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(
|
||||
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
|
||||
);
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeIdAttendeesDisabledId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeIdAttendeesDisabledId,
|
||||
slug: seatedEventSlugAttendeesDisabled,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
expect(data.absentHost).toEqual(false);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected seated booking but received non array response"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancel seated booking", () => {
|
||||
describe("cancel seated booking as attendee", () => {
|
||||
it("should cancel seated booking", async () => {
|
||||
|
||||
@@ -576,10 +576,13 @@ export class BookingsService_2024_08_13 {
|
||||
return this.outputService.getOutputRecurringBooking(booking);
|
||||
}
|
||||
if (isRecurring && isSeated) {
|
||||
return this.outputService.getOutputRecurringSeatedBooking(booking);
|
||||
return this.outputService.getOutputRecurringSeatedBooking(
|
||||
booking,
|
||||
!!booking.eventType?.seatsShowAttendees
|
||||
);
|
||||
}
|
||||
if (isSeated) {
|
||||
return this.outputService.getOutputSeatedBooking(booking);
|
||||
return this.outputService.getOutputSeatedBooking(booking, !!booking.eventType?.seatsShowAttendees);
|
||||
}
|
||||
return this.outputService.getOutputBooking(booking);
|
||||
}
|
||||
@@ -591,7 +594,10 @@ export class BookingsService_2024_08_13 {
|
||||
const ids = recurringBooking.map((booking) => booking.id);
|
||||
const isRecurringSeated = !!recurringBooking[0].eventType?.seatsPerTimeSlot;
|
||||
if (isRecurringSeated) {
|
||||
return this.outputService.getOutputRecurringSeatedBookings(ids);
|
||||
return this.outputService.getOutputRecurringSeatedBookings(
|
||||
ids,
|
||||
!!recurringBooking[0].eventType?.seatsShowAttendees
|
||||
);
|
||||
}
|
||||
|
||||
return this.outputService.getOutputRecurringBookings(ids);
|
||||
@@ -657,9 +663,19 @@ export class BookingsService_2024_08_13 {
|
||||
if (isRecurring && !isSeated) {
|
||||
formattedBookings.push(this.outputService.getOutputRecurringBooking(formatted));
|
||||
} else if (isRecurring && isSeated) {
|
||||
formattedBookings.push(this.outputService.getOutputRecurringSeatedBooking(formatted));
|
||||
formattedBookings.push(
|
||||
this.outputService.getOutputRecurringSeatedBooking(
|
||||
formatted,
|
||||
!!formatted.eventType?.seatsShowAttendees
|
||||
)
|
||||
);
|
||||
} else if (isSeated) {
|
||||
formattedBookings.push(await this.outputService.getOutputSeatedBooking(formatted));
|
||||
formattedBookings.push(
|
||||
await this.outputService.getOutputSeatedBooking(
|
||||
formatted,
|
||||
!!formatted.eventType?.seatsShowAttendees
|
||||
)
|
||||
);
|
||||
} else {
|
||||
formattedBookings.push(await this.outputService.getOutputBooking(formatted));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ type DatabaseBooking = Booking & {
|
||||
eventType: {
|
||||
id: number;
|
||||
slug: string;
|
||||
seatsShowAttendees?: boolean | null;
|
||||
} | null;
|
||||
attendees: {
|
||||
name: string;
|
||||
@@ -167,6 +168,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
getUserDefinedMetadata(databaseMetadata: DatabaseMetadata) {
|
||||
if (databaseMetadata === null) return {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { videoCallUrl, ...userDefinedMetadata } = databaseMetadata;
|
||||
|
||||
return userDefinedMetadata;
|
||||
@@ -266,11 +268,14 @@ export class OutputBookingsService_2024_08_13 {
|
||||
databaseBooking: DatabaseBooking,
|
||||
seatUid: string
|
||||
): Promise<CreateSeatedBookingOutput_2024_08_13> {
|
||||
const getSeatedBookingOutput = await this.getOutputSeatedBooking(databaseBooking);
|
||||
const getSeatedBookingOutput = await this.getOutputSeatedBooking(
|
||||
databaseBooking,
|
||||
!!databaseBooking.eventType?.seatsShowAttendees
|
||||
);
|
||||
return { ...getSeatedBookingOutput, seatUid };
|
||||
}
|
||||
|
||||
async getOutputSeatedBooking(databaseBooking: DatabaseBooking) {
|
||||
async getOutputSeatedBooking(databaseBooking: DatabaseBooking, showAttendees: boolean) {
|
||||
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
||||
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
||||
const duration = dateEnd.diff(dateStart, "minutes").minutes;
|
||||
@@ -317,41 +322,43 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const parsed = plainToClass(GetSeatedBookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
||||
|
||||
// note(Lauris): I don't know why plainToClass erases booking.attendees[n].responses so attaching manually
|
||||
parsed.attendees = databaseBooking.attendees.map((attendee) => {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
parsed.attendees = showAttendees
|
||||
? databaseBooking.attendees.map((attendee) => {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
seatUid: attendee.bookingSeat?.referenceUid,
|
||||
bookingFieldsResponses: {},
|
||||
};
|
||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
seatUid: attendee.bookingSeat?.referenceUid,
|
||||
bookingFieldsResponses: {},
|
||||
};
|
||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
|
||||
return attendeeParsed;
|
||||
});
|
||||
return attendeeParsed;
|
||||
})
|
||||
: [];
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async getOutputRecurringSeatedBookings(bookingsIds: number[]) {
|
||||
async getOutputRecurringSeatedBookings(bookingsIds: number[], showAttendees: boolean) {
|
||||
const transformed = [];
|
||||
|
||||
for (const bookingId of bookingsIds) {
|
||||
@@ -361,7 +368,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
throw new Error(`Booking with id=${bookingId} was not found in the database`);
|
||||
}
|
||||
|
||||
transformed.push(this.getOutputRecurringSeatedBooking(databaseBooking));
|
||||
transformed.push(this.getOutputRecurringSeatedBooking(databaseBooking, showAttendees));
|
||||
}
|
||||
|
||||
return transformed.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
||||
@@ -386,11 +393,14 @@ export class OutputBookingsService_2024_08_13 {
|
||||
databaseBooking: DatabaseBooking,
|
||||
seatUid: string
|
||||
): CreateRecurringSeatedBookingOutput_2024_08_13 {
|
||||
const getRecurringSeatedBookingOutput = this.getOutputRecurringSeatedBooking(databaseBooking);
|
||||
const getRecurringSeatedBookingOutput = this.getOutputRecurringSeatedBooking(
|
||||
databaseBooking,
|
||||
!!databaseBooking.eventType?.seatsShowAttendees
|
||||
);
|
||||
return { ...getRecurringSeatedBookingOutput, seatUid };
|
||||
}
|
||||
|
||||
getOutputRecurringSeatedBooking(databaseBooking: DatabaseBooking) {
|
||||
getOutputRecurringSeatedBooking(databaseBooking: DatabaseBooking, showAttendees: boolean) {
|
||||
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
||||
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
||||
const duration = dateEnd.diff(dateStart, "minutes").minutes;
|
||||
@@ -430,35 +440,37 @@ export class OutputBookingsService_2024_08_13 {
|
||||
});
|
||||
|
||||
// note(Lauris): I don't know why plainToClass erases booking.attendees[n].responses so attaching manually
|
||||
parsed.attendees = databaseBooking.attendees.map((attendee) => {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
parsed.attendees = showAttendees
|
||||
? databaseBooking.attendees.map((attendee) => {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
seatUid: attendee.bookingSeat?.referenceUid,
|
||||
bookingFieldsResponses: {},
|
||||
};
|
||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
return attendeeParsed;
|
||||
});
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
seatUid: attendee.bookingSeat?.referenceUid,
|
||||
bookingFieldsResponses: {},
|
||||
};
|
||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
return attendeeParsed;
|
||||
})
|
||||
: [];
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { z } from "zod";
|
||||
|
||||
import { StripeService } from "@calcom/lib/server/service/stripe";
|
||||
|
||||
import VerifyPage from "~/auth/verify-view";
|
||||
|
||||
const querySchema = z.object({
|
||||
@@ -14,18 +12,10 @@ const querySchema = z.object({
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const p = { ...(await params), ...(await searchParams) };
|
||||
const { sessionId, stripeCustomerId } = querySchema.parse(p);
|
||||
|
||||
const data = await StripeService.getCheckoutSession({
|
||||
stripeCustomerId,
|
||||
checkoutSessionId: sessionId,
|
||||
});
|
||||
|
||||
const { hasPaymentFailed } = data;
|
||||
const { sessionId } = querySchema.parse(p);
|
||||
|
||||
return await _generateMetadata(
|
||||
() =>
|
||||
hasPaymentFailed ? "Your payment failed" : sessionId ? "Payment successful!" : `Verify your email`,
|
||||
() => (sessionId ? "Payment Page" : `Verify your email`),
|
||||
() => "",
|
||||
undefined,
|
||||
undefined,
|
||||
|
||||
@@ -15,6 +15,7 @@ const resolveEndpoint = (links: any) => {
|
||||
// to the correct API endpoints.
|
||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
||||
// - viewer.eventTypes.heavy.create - 4 segments paths for heavy sub-router
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (ctx: any) => {
|
||||
const parts = ctx.op.path.split(".");
|
||||
@@ -23,7 +24,11 @@ const resolveEndpoint = (links: any) => {
|
||||
if (parts.length == 2) {
|
||||
endpoint = parts[0] as keyof typeof links;
|
||||
path = parts[1];
|
||||
} else {
|
||||
} else if (parts.length >= 3 && parts[2] === "heavy") {
|
||||
endpoint = parts[1] + "/heavy" as keyof typeof links;
|
||||
path = parts[3];
|
||||
}
|
||||
else {
|
||||
endpoint = parts[1] as keyof typeof links;
|
||||
path = parts.splice(2, parts.length - 2).join(".");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const UserProfile = ({ user }: UserProfileProps) => {
|
||||
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
||||
const utils = trpc.useUtils();
|
||||
const router = useRouter();
|
||||
const createEventType = trpc.viewer.eventTypes.create.useMutation();
|
||||
const createEventType = trpc.viewer.eventTypes.heavy.create.useMutation();
|
||||
const telemetry = useTelemetry();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ const OnboardingPage = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [eventTypeGroups]);
|
||||
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
const updateMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
||||
onSuccess: async (data) => {
|
||||
showToast(t("event_type_updated_successfully", { eventTypeTitle: data.eventType?.title }), "success");
|
||||
},
|
||||
|
||||
@@ -268,7 +268,7 @@ export const InfiniteEventTypeList = ({
|
||||
},
|
||||
});
|
||||
|
||||
const setHiddenMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
const setHiddenMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
||||
onMutate: async (data) => {
|
||||
await utils.viewer.eventTypes.getEventTypesFromGroup.cancel();
|
||||
const previousValue = utils.viewer.eventTypes.getEventTypesFromGroup.getInfiniteData({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "5.6.14",
|
||||
"version": "5.6.15",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getRoutedUrl } from "@calcom/features/routing-forms/lib/getRoutedUrl";
|
||||
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import { getRoutedUrl } from "@calcom/lib/server/getRoutedUrl";
|
||||
|
||||
export default defaultHandler({
|
||||
OPTIONS: Promise.resolve({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import appBasecamp3 from "@calcom/app-store/basecamp3/trpc-router";
|
||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import appBasecamp3 from "@calcom/trpc/server/routers/apps/basecamp3/_router";
|
||||
|
||||
export default createNextApiHandler(appBasecamp3);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import appRoutingForms from "@calcom/app-store/routing-forms/trpc-router";
|
||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import appRoutingForms from "@calcom/trpc/server/routers/apps/routing-forms/_router";
|
||||
|
||||
export default createNextApiHandler(appRoutingForms);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import { eventTypesRouter } from "@calcom/trpc/server/routers/viewer/eventTypes/heavy/_router";
|
||||
|
||||
export default createNextApiHandler(eventTypesRouter);
|
||||
@@ -113,7 +113,7 @@ test("multiple duration selection updates event length correctly", async ({ page
|
||||
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
||||
await page.locator('[data-testid="update-eventtype"]').click();
|
||||
await page.waitForResponse("/api/trpc/eventTypes/update?batch=1");
|
||||
await page.waitForResponse("/api/trpc/eventTypes/heavy/update?batch=1");
|
||||
});
|
||||
|
||||
await page.goto(`/${user.username}/multiple-duration`);
|
||||
|
||||
@@ -131,7 +131,7 @@ test.describe("Event Types tests", () => {
|
||||
expect(formTitle).toBe(firstTitle);
|
||||
expect(formSlug).toContain(firstSlug);
|
||||
|
||||
const submitPromise = page.waitForResponse("/api/trpc/eventTypes/duplicate?batch=1");
|
||||
const submitPromise = page.waitForResponse("/api/trpc/eventTypes/heavy/duplicate?batch=1");
|
||||
await page.getByTestId("continue").click();
|
||||
const response = await submitPromise;
|
||||
expect(response.status()).toBe(200);
|
||||
@@ -144,7 +144,7 @@ test.describe("Event Types tests", () => {
|
||||
await page.waitForURL((url) => {
|
||||
return !!url.pathname.match(/\/event-types\/.+/);
|
||||
});
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
});
|
||||
@@ -167,7 +167,7 @@ test.describe("Event Types tests", () => {
|
||||
await page.locator("[data-testid=add-location]").click();
|
||||
await fillLocation(page, locationData[2], 2);
|
||||
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
|
||||
@@ -314,7 +314,7 @@ test.describe("Event Types tests", () => {
|
||||
const locationAddress = "New Delhi";
|
||||
|
||||
await fillLocation(page, locationAddress, 0, false);
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
|
||||
@@ -429,7 +429,7 @@ test.describe("Event Types tests", () => {
|
||||
await page.locator('[aria-label="Timezone Select"]').fill("New York");
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
await page.goto("/event-types");
|
||||
|
||||
@@ -50,7 +50,7 @@ export function createBookingPageFixture(page: Page) {
|
||||
await page.goto("/event-types");
|
||||
},
|
||||
updateEventType: async () => {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ test.describe("private links creation and usage", () => {
|
||||
const $url = await page.locator('[data-testid="private-link-url"]').inputValue();
|
||||
|
||||
// click update
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
// book using generated url hash
|
||||
@@ -74,7 +74,7 @@ test.describe("private links creation and usage", () => {
|
||||
await page.locator("[data-testid=event-slug]").first().fill("somethingrandom");
|
||||
await expect(page.locator('[data-testid="event-slug"]').first()).toHaveValue("somethingrandom");
|
||||
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
await page.locator("[data-testid=vertical-tab-event_advanced_tab_title]").click();
|
||||
@@ -113,7 +113,7 @@ test.describe("private links creation and usage", () => {
|
||||
await page.locator('[data-testid="private-link-expiration-settings-save"]').click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
// click update
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
// book using generated url hash
|
||||
@@ -164,7 +164,7 @@ test.describe("private links creation and usage", () => {
|
||||
await page.locator('[data-testid="private-link-expiration-settings-save"]').click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
// click update
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
// book using generated url hash
|
||||
|
||||
@@ -476,7 +476,7 @@ export async function gotoBookingPage(page: Page) {
|
||||
}
|
||||
|
||||
export async function saveEventType(page: Page) {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -677,7 +677,7 @@ async function openBookingFormInPreviewTab(context: PlaywrightTestArgs["context"
|
||||
}
|
||||
|
||||
async function saveEventType(page: Page) {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -239,5 +239,5 @@ async function gotoBookingPage(page: Page) {
|
||||
}
|
||||
|
||||
async function saveAndWaitForResponse(page: Page) {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1");
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1");
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ test.describe("Bookings", () => {
|
||||
await page.goto(`/event-types/${teamEvent.id}?tabName=advanced`);
|
||||
await page.getByTestId("requires-confirmation").click();
|
||||
await page.getByTestId("update-eventtype").click();
|
||||
await page.waitForResponse((response) => response.url().includes("/api/trpc/eventTypes/update"));
|
||||
await page.waitForResponse((response) => response.url().includes("/api/trpc/eventTypes/heavy/update"));
|
||||
|
||||
await doOnOrgDomain(
|
||||
{
|
||||
@@ -783,7 +783,7 @@ const markPhoneNumberAsRequiredAndEmailAsOptional = async (page: Page, eventId:
|
||||
const emailRequiredFiled = await page.locator('[data-testid="field-required"]').first();
|
||||
await emailRequiredFiled.click();
|
||||
await page.getByTestId("field-add-save").click();
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
};
|
||||
@@ -797,7 +797,7 @@ const markPhoneNumberAsRequiredField = async (page: Page, eventId: number) => {
|
||||
const phoneRequiredFiled = await page.locator('[data-testid="field-required"]').first();
|
||||
await phoneRequiredFiled.click();
|
||||
await page.getByTestId("field-add-save").click();
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -458,7 +458,7 @@ test.describe("Organization", () => {
|
||||
await page.locator('[data-testid="fixed-hosts-select"]').click();
|
||||
await page.locator(`text="${invitedUserEmail}"`).click();
|
||||
await page.locator('[data-testid="update-eventtype"]').click();
|
||||
await page.waitForResponse("/api/trpc/eventTypes/update?batch=1");
|
||||
await page.waitForResponse("/api/trpc/eventTypes/heavy/update?batch=1");
|
||||
|
||||
await expectPageToBeNotFound({ page, url: `/team/${team.slug}/${teamEvent.slug}` });
|
||||
await doOnOrgDomain(
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe("Payment app", () => {
|
||||
await page.getByPlaceholder("Price").click();
|
||||
await page.getByPlaceholder("Price").fill("200");
|
||||
await page.getByText("SatoshissatsCurrencyBTCPayment optionCollect payment on booking").click();
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/heavy/update?batch=1", {
|
||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { wrapGetServerSidePropsWithSentry } from "@sentry/nextjs";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getRoutedUrl, hasEmbedPath } from "@calcom/lib/server/getRoutedUrl";
|
||||
import { getRoutedUrl, hasEmbedPath } from "@calcom/features/routing-forms/lib/getRoutedUrl";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import Link from "next/link";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -13,7 +12,7 @@ export const AppDependencyComponent = ({
|
||||
dependencyData,
|
||||
}: {
|
||||
appName: string;
|
||||
dependencyData: RouterOutputs["viewer"]["apps"]["queryForDependencies"];
|
||||
dependencyData?: { name: string; slug: string; installed: boolean }[];
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@ import Link from "next/link";
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
import type { CredentialOwner } from "../types";
|
||||
import type { AppCardApp } from "../types";
|
||||
import OmniInstallAppButton from "./OmniInstallAppButton";
|
||||
|
||||
export default function AppCard({
|
||||
@@ -27,9 +26,7 @@ export default function AppCard({
|
||||
hideSettingsIcon = false,
|
||||
hideAppCardOptions = false,
|
||||
}: {
|
||||
app: RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
|
||||
credentialOwner?: CredentialOwner;
|
||||
};
|
||||
app: AppCardApp;
|
||||
description?: React.ReactNode;
|
||||
switchChecked?: boolean;
|
||||
switchOnClick?: (e: boolean) => void;
|
||||
|
||||
@@ -4,20 +4,15 @@ import type { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppConte
|
||||
import EventTypeAppContext from "@calcom/app-store/EventTypeAppContext";
|
||||
import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
|
||||
|
||||
import type { EventTypeAppCardComponentProps, CredentialOwner } from "../types";
|
||||
import type { EventTypeAppCardComponentProps, AppCardApp } from "../types";
|
||||
import { DynamicComponent } from "./DynamicComponent";
|
||||
|
||||
export type EventTypeApp = RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
|
||||
credentialOwner?: CredentialOwner;
|
||||
};
|
||||
|
||||
export type EventTypeForAppCard = EventTypeAppCardComponentProps["eventType"];
|
||||
|
||||
export const EventTypeAppCard = (props: {
|
||||
app: EventTypeApp;
|
||||
app: AppCardApp;
|
||||
eventType: EventTypeForAppCard;
|
||||
getAppData: GetAppData;
|
||||
setAppData: SetAppData;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { CredentialOwner } from "types";
|
||||
import type { AppCardApp } from "types";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { RouterOutputs } from "@calcom/trpc";
|
||||
|
||||
import { DynamicComponent } from "./DynamicComponent";
|
||||
import { EventTypeAppCard } from "./EventTypeAppCardInterface";
|
||||
|
||||
@@ -26,9 +24,7 @@ const mockProps = {
|
||||
name: "TestApp",
|
||||
slug: "testapp",
|
||||
credentialOwner: {},
|
||||
} as RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
|
||||
credentialOwner?: CredentialOwner;
|
||||
},
|
||||
} as AppCardApp,
|
||||
eventType: {},
|
||||
getAppData: getAppDataMock,
|
||||
setAppData: setAppDataMock,
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Copied from node_modules/stripe/types/Errors.d.ts because stripe lib doesn't expose these types
|
||||
// Ref: https://github.com/stripe/stripe-node/blob/master/types/Errors.d.ts#L194
|
||||
interface StripeInvalidRequestError extends Error {
|
||||
readonly type: "StripeInvalidRequestError";
|
||||
readonly rawType: "invalid_request_error";
|
||||
/**
|
||||
* A human-readable message giving more details about the error. For card errors, these messages can
|
||||
* be shown to your users.
|
||||
*/
|
||||
readonly message: string;
|
||||
/**
|
||||
* For card errors, a short string describing the kind of card error that occurred.
|
||||
*
|
||||
* @docs https://stripe.com/docs/error-codes
|
||||
*/
|
||||
readonly code?: string;
|
||||
|
||||
/**
|
||||
* A URL to more information about the error code reported.
|
||||
*
|
||||
* @docs https://stripe.com/docs/error-codes
|
||||
*/
|
||||
readonly doc_url?: string;
|
||||
|
||||
/**
|
||||
* Typically a 4xx or 5xx.
|
||||
*/
|
||||
readonly statusCode?: number;
|
||||
|
||||
readonly raw: unknown;
|
||||
|
||||
readonly headers: {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
readonly requestId: string;
|
||||
|
||||
/**
|
||||
* The parameter the error relates to if the error is parameter-specific. You can use this to display a
|
||||
* message near the correct form field, for example.
|
||||
*/
|
||||
readonly param?: string;
|
||||
}
|
||||
|
||||
const errorSchema = z.object({
|
||||
name: z.string(),
|
||||
message: z.string(),
|
||||
stack: z.string().optional(),
|
||||
});
|
||||
|
||||
export const stripeInvalidRequestErrorSchema = errorSchema.extend({
|
||||
type: z.literal("StripeInvalidRequestError"),
|
||||
rawType: z.literal("invalid_request_error"),
|
||||
code: z.string().optional(),
|
||||
doc_url: z.string().optional(),
|
||||
statusCode: z.number().optional(),
|
||||
raw: z.unknown(),
|
||||
headers: z.record(z.string()),
|
||||
requestId: z.string(),
|
||||
param: z.string().optional(),
|
||||
});
|
||||
@@ -16,7 +16,7 @@ function useIsAppEnabled(app: EventTypeAppCardApp) {
|
||||
const credentialId = getAppData("credentialId");
|
||||
const isAppEnabledForCredential =
|
||||
isAppEnabled &&
|
||||
(app.userCredentialIds.some((id) => id === credentialId) ||
|
||||
(app.userCredentialIds.some((id: number) => id === credentialId) ||
|
||||
app.credentialOwner.credentialId === credentialId);
|
||||
return isAppEnabledForCredential ?? false; // Default to false if undefined
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./trpc/_router";
|
||||
@@ -1,3 +1,5 @@
|
||||
export const MeetLocationType = "integrations:google:meet";
|
||||
|
||||
export const MSTeamsLocationType = "integrations:office365_video";
|
||||
|
||||
export const DailyLocationType = "integrations:daily";
|
||||
|
||||
@@ -1310,8 +1310,10 @@ describe("Date Optimization Benchmarks", () => {
|
||||
)}ms, Speedup: ${speedupRatio.toFixed(1)}x`
|
||||
);
|
||||
|
||||
const minSpeedup = process.env.CI ? 1.5 : 5; // Lower threshold for CI
|
||||
expect(speedupRatio).toBeGreaterThan(minSpeedup);
|
||||
if (!process.env.CI) {
|
||||
const minSpeedup = 5; // Assert significant performance improvement (at least 5x faster)
|
||||
expect(speedupRatio).toBeGreaterThan(minSpeedup);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ import type { EventLocationTypeFromAppMeta } from "../types/App";
|
||||
import {
|
||||
MeetLocationType as importedMeetLocationType,
|
||||
MSTeamsLocationType as importedMSTeamsLocationType,
|
||||
DailyLocationType as importedDailyLocationType,
|
||||
} from "./constants";
|
||||
|
||||
export const MeetLocationType = importedMeetLocationType;
|
||||
export const MSTeamsLocationType = importedMSTeamsLocationType;
|
||||
export const DailyLocationType = importedDailyLocationType;
|
||||
|
||||
export type DefaultEventLocationType = {
|
||||
default: true;
|
||||
@@ -69,7 +71,6 @@ export type EventLocationTypeFromApp = Ensure<
|
||||
|
||||
export type EventLocationType = DefaultEventLocationType | EventLocationTypeFromApp;
|
||||
|
||||
export const DailyLocationType = "integrations:daily";
|
||||
export const CalVideoLocationType = DailyLocationType;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
|
||||
import { _onFormSubmission } from "./utils";
|
||||
import { _onFormSubmission } from "./formSubmissionUtils";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
|
||||
@@ -1,13 +1,244 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { App_RoutingForms_Form, User } from "@prisma/client";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { Tasker } from "@calcom/features/tasker/tasker";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { App_RoutingForms_Form } from "@calcom/prisma/client";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import { RoutingFormSettings } from "@calcom/prisma/zod-utils";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { onFormSubmission } from "../trpc/utils";
|
||||
import type { FormResponse, SerializableForm } from "../types/types";
|
||||
import type { FormResponse, SerializableForm, SerializableField, OrderedResponses } from "../types/types";
|
||||
|
||||
const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/lib/formSubmissionUtils"] });
|
||||
|
||||
type SelectFieldWebhookResponse = string | number | string[] | { label: string; id: string | null };
|
||||
export type FORM_SUBMITTED_WEBHOOK_RESPONSES = Record<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* Deprecates `value` prop as it now has both the id(that doesn't change) and the label(that can change but is human friendly)
|
||||
*/
|
||||
response: number | string | string[] | SelectFieldWebhookResponse | SelectFieldWebhookResponse[];
|
||||
/**
|
||||
* @deprecated Use `response` instead
|
||||
*/
|
||||
value: FormResponse[keyof FormResponse]["value"];
|
||||
}
|
||||
>;
|
||||
|
||||
function isOptionsField(field: Pick<SerializableField, "type" | "options">) {
|
||||
return (field.type === "select" || field.type === "multiselect") && field.options;
|
||||
}
|
||||
|
||||
export function getFieldResponse({
|
||||
field,
|
||||
fieldResponseValue,
|
||||
}: {
|
||||
fieldResponseValue: FormResponse[keyof FormResponse]["value"];
|
||||
field: Pick<SerializableField, "type" | "options">;
|
||||
}) {
|
||||
if (!isOptionsField(field)) {
|
||||
return {
|
||||
value: fieldResponseValue,
|
||||
response: fieldResponseValue,
|
||||
};
|
||||
}
|
||||
|
||||
if (!field.options) {
|
||||
return {
|
||||
value: fieldResponseValue,
|
||||
response: fieldResponseValue,
|
||||
};
|
||||
}
|
||||
|
||||
const valueArray = fieldResponseValue instanceof Array ? fieldResponseValue : [fieldResponseValue];
|
||||
|
||||
const chosenOptions = valueArray.map((idOrLabel) => {
|
||||
const foundOptionById = field.options?.find((option) => {
|
||||
return option.id === idOrLabel;
|
||||
});
|
||||
if (foundOptionById) {
|
||||
return {
|
||||
label: foundOptionById.label,
|
||||
id: foundOptionById.id,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: idOrLabel.toString(),
|
||||
id: null,
|
||||
};
|
||||
}
|
||||
});
|
||||
return {
|
||||
// value is a legacy prop that is just sending the labels which can change
|
||||
value: chosenOptions.map((option) => option.label),
|
||||
// response is new prop that is sending the label along with id(which doesn't change)
|
||||
response: chosenOptions,
|
||||
};
|
||||
}
|
||||
|
||||
export const sendResponseEmail = async (
|
||||
form: Pick<App_RoutingForms_Form, "id" | "name" | "fields">,
|
||||
orderedResponses: OrderedResponses,
|
||||
toAddresses: string[]
|
||||
) => {
|
||||
try {
|
||||
if (typeof window === "undefined") {
|
||||
const { default: ResponseEmail } = await import("../emails/templates/response-email");
|
||||
const email = new ResponseEmail({ form: form, toAddresses, orderedResponses });
|
||||
await email.sendEmail();
|
||||
}
|
||||
} catch (e) {
|
||||
moduleLogger.error("Error sending response email", e);
|
||||
}
|
||||
};
|
||||
|
||||
function getWebhookTargetEntity(form: { teamId?: number | null; user: { id: number } }) {
|
||||
// If it's a team form, the target must be team webhook
|
||||
// If it's a user form, the target must be user webhook
|
||||
const isTeamForm = form.teamId;
|
||||
return { userId: isTeamForm ? null : form.user.id, teamId: isTeamForm ? form.teamId : null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Not called in preview mode or dry run mode
|
||||
* It takes care of sending webhooks and emails for form submissions
|
||||
*/
|
||||
export async function _onFormSubmission(
|
||||
form: Ensure<
|
||||
SerializableForm<App_RoutingForms_Form> & { user: Pick<User, "id" | "email">; userWithEmails?: string[] },
|
||||
"fields"
|
||||
>,
|
||||
response: FormResponse,
|
||||
responseId: number,
|
||||
chosenAction?: {
|
||||
type: "customPageMessage" | "externalRedirectUrl" | "eventTypeRedirectUrl";
|
||||
value: string;
|
||||
}
|
||||
) {
|
||||
const fieldResponsesByIdentifier: FORM_SUBMITTED_WEBHOOK_RESPONSES = {};
|
||||
|
||||
for (const [fieldId, fieldResponse] of Object.entries(response)) {
|
||||
const field = form.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
throw new Error(`Field with id ${fieldId} not found`);
|
||||
}
|
||||
// Use the label lowercased as the key to identify a field.
|
||||
// TODO: We seem to be using label from the response, Can we not use the field.label
|
||||
const key =
|
||||
form.fields.find((f) => f.id === fieldId)?.identifier ||
|
||||
(fieldResponse.label as keyof typeof fieldResponsesByIdentifier);
|
||||
fieldResponsesByIdentifier[key] = getFieldResponse({
|
||||
fieldResponseValue: fieldResponse.value,
|
||||
field,
|
||||
});
|
||||
}
|
||||
|
||||
const { userId, teamId } = getWebhookTargetEntity(form);
|
||||
|
||||
const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId });
|
||||
|
||||
const subscriberOptionsFormSubmitted = {
|
||||
userId,
|
||||
teamId,
|
||||
orgId,
|
||||
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED,
|
||||
};
|
||||
|
||||
const subscriberOptionsFormSubmittedNoEvent = {
|
||||
userId,
|
||||
teamId,
|
||||
orgId,
|
||||
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT,
|
||||
};
|
||||
|
||||
const webhooksFormSubmitted = await getWebhooks(subscriberOptionsFormSubmitted);
|
||||
const webhooksFormSubmittedNoEvent = await getWebhooks(subscriberOptionsFormSubmittedNoEvent);
|
||||
|
||||
const promisesFormSubmitted = webhooksFormSubmitted.map((webhook) => {
|
||||
sendGenericWebhookPayload({
|
||||
secretKey: webhook.secret,
|
||||
triggerEvent: "FORM_SUBMITTED",
|
||||
createdAt: new Date().toISOString(),
|
||||
webhook,
|
||||
data: {
|
||||
formId: form.id,
|
||||
formName: form.name,
|
||||
teamId: form.teamId,
|
||||
responses: fieldResponsesByIdentifier,
|
||||
},
|
||||
rootData: {
|
||||
// Send responses unwrapped at root level for backwards compatibility
|
||||
...Object.entries(fieldResponsesByIdentifier).reduce((acc, [key, value]) => {
|
||||
acc[key] = value.value;
|
||||
return acc;
|
||||
}, {} as Record<string, FormResponse[keyof FormResponse]["value"]>),
|
||||
},
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing routing form webhook`, webhook, e);
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
try {
|
||||
const tasker: Tasker = await (await import("@calcom/features/tasker")).default;
|
||||
const promisesFormSubmittedNoEvent = webhooksFormSubmittedNoEvent.map((webhook) => {
|
||||
const scheduledAt = dayjs().add(15, "minute").toDate();
|
||||
|
||||
return tasker.create(
|
||||
"triggerFormSubmittedNoEventWebhook",
|
||||
{
|
||||
responseId,
|
||||
form: {
|
||||
id: form.id,
|
||||
name: form.name,
|
||||
teamId: form.teamId ?? null,
|
||||
},
|
||||
responses: fieldResponsesByIdentifier,
|
||||
redirect: chosenAction,
|
||||
webhook,
|
||||
},
|
||||
{ scheduledAt }
|
||||
);
|
||||
});
|
||||
|
||||
const promises = [...promisesFormSubmitted, ...promisesFormSubmittedNoEvent];
|
||||
|
||||
await Promise.all(promises);
|
||||
const orderedResponses = form.fields.reduce((acc, field) => {
|
||||
acc.push(response[field.id]);
|
||||
return acc;
|
||||
}, [] as OrderedResponses);
|
||||
|
||||
if (form.teamId) {
|
||||
if (form.userWithEmails?.length) {
|
||||
moduleLogger.debug(
|
||||
`Preparing to send Form Response email for Form:${form.id} to users: ${form.userWithEmails.join(
|
||||
","
|
||||
)}`
|
||||
);
|
||||
await sendResponseEmail(form, orderedResponses, form.userWithEmails);
|
||||
}
|
||||
} else if (form.settings?.emailOwnerOnSubmission) {
|
||||
moduleLogger.debug(
|
||||
`Preparing to send Form Response email for Form:${form.id} to form owner: ${form.user.email}`
|
||||
);
|
||||
await sendResponseEmail(form, orderedResponses, [form.user.email]);
|
||||
}
|
||||
} catch (e) {
|
||||
moduleLogger.error("Error triggering routing form response side effects", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const onFormSubmission = withReporting(_onFormSubmission, "onFormSubmission");
|
||||
|
||||
export type TargetRoutingFormForResponse = SerializableForm<
|
||||
App_RoutingForms_Form & {
|
||||
|
||||
@@ -13,10 +13,10 @@ import type { z } from "zod";
|
||||
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import { areTheySiblingEntities } from "@calcom/lib/entityPermissionUtils.shared";
|
||||
import type { EventTypesByViewer } from "@calcom/lib/event-types/getEventTypesByViewer";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { buildEmptyQueryValue, raqbQueryValueUtils } from "@calcom/lib/raqb/raqbUtils";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
@@ -63,8 +63,6 @@ import type {
|
||||
import type { zodRoutes } from "../../zod";
|
||||
import { RouteActionType } from "../../zod";
|
||||
|
||||
type EventTypesByGroup = RouterOutputs["viewer"]["eventTypes"]["getByViewer"];
|
||||
|
||||
type Form = inferSSRProps<typeof getServerSideProps>["form"];
|
||||
|
||||
type SetRoute = (id: string, route: Partial<EditFormRoute>) => void;
|
||||
@@ -141,7 +139,7 @@ const buildEventsData = ({
|
||||
form,
|
||||
route,
|
||||
}: {
|
||||
eventTypesByGroup: EventTypesByGroup | undefined;
|
||||
eventTypesByGroup: EventTypesByViewer | undefined;
|
||||
form: Form;
|
||||
route: EditFormRoute;
|
||||
}) => {
|
||||
@@ -372,7 +370,7 @@ const Route = ({
|
||||
moveDown?: { fn: () => void; check: () => boolean } | null;
|
||||
appUrl: string;
|
||||
disabled?: boolean;
|
||||
eventTypesByGroup: EventTypesByGroup;
|
||||
eventTypesByGroup: EventTypesByViewer;
|
||||
attributes?: Attribute[];
|
||||
cardOptions?: {
|
||||
collapsible?: boolean;
|
||||
@@ -1129,7 +1127,7 @@ const Routes = ({
|
||||
hookForm: UseFormReturn<RoutingFormWithResponseCount>;
|
||||
appUrl: string;
|
||||
attributes?: Attribute[];
|
||||
eventTypesByGroup: EventTypesByGroup;
|
||||
eventTypesByGroup: EventTypesByViewer;
|
||||
}) => {
|
||||
const { routes: serializedRoutes } = hookForm.getValues();
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./trpc/_router";
|
||||
@@ -1,237 +0,0 @@
|
||||
import type { App_RoutingForms_Form, User } from "@prisma/client";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { Tasker } from "@calcom/features/tasker/tasker";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
|
||||
import type { SerializableField, OrderedResponses } from "../types/types";
|
||||
import type { FormResponse, SerializableForm } from "../types/types";
|
||||
|
||||
const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/trpc/utils"] });
|
||||
|
||||
type SelectFieldWebhookResponse = string | number | string[] | { label: string; id: string | null };
|
||||
export type FORM_SUBMITTED_WEBHOOK_RESPONSES = Record<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* Deprecates `value` prop as it now has both the id(that doesn't change) and the label(that can change but is human friendly)
|
||||
*/
|
||||
response: number | string | string[] | SelectFieldWebhookResponse | SelectFieldWebhookResponse[];
|
||||
/**
|
||||
* @deprecated Use `response` instead
|
||||
*/
|
||||
value: FormResponse[keyof FormResponse]["value"];
|
||||
}
|
||||
>;
|
||||
|
||||
function isOptionsField(field: Pick<SerializableField, "type" | "options">) {
|
||||
return (field.type === "select" || field.type === "multiselect") && field.options;
|
||||
}
|
||||
|
||||
export function getFieldResponse({
|
||||
field,
|
||||
fieldResponseValue,
|
||||
}: {
|
||||
fieldResponseValue: FormResponse[keyof FormResponse]["value"];
|
||||
field: Pick<SerializableField, "type" | "options">;
|
||||
}) {
|
||||
if (!isOptionsField(field)) {
|
||||
return {
|
||||
value: fieldResponseValue,
|
||||
response: fieldResponseValue,
|
||||
};
|
||||
}
|
||||
|
||||
if (!field.options) {
|
||||
return {
|
||||
value: fieldResponseValue,
|
||||
response: fieldResponseValue,
|
||||
};
|
||||
}
|
||||
|
||||
const valueArray = fieldResponseValue instanceof Array ? fieldResponseValue : [fieldResponseValue];
|
||||
|
||||
const chosenOptions = valueArray.map((idOrLabel) => {
|
||||
const foundOptionById = field.options?.find((option) => {
|
||||
return option.id === idOrLabel;
|
||||
});
|
||||
if (foundOptionById) {
|
||||
return {
|
||||
label: foundOptionById.label,
|
||||
id: foundOptionById.id,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: idOrLabel.toString(),
|
||||
id: null,
|
||||
};
|
||||
}
|
||||
});
|
||||
return {
|
||||
// value is a legacy prop that is just sending the labels which can change
|
||||
value: chosenOptions.map((option) => option.label),
|
||||
// response is new prop that is sending the label along with id(which doesn't change)
|
||||
response: chosenOptions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Not called in preview mode or dry run mode
|
||||
* It takes care of sending webhooks and emails for form submissions
|
||||
*/
|
||||
export async function _onFormSubmission(
|
||||
form: Ensure<
|
||||
SerializableForm<App_RoutingForms_Form> & { user: Pick<User, "id" | "email">; userWithEmails?: string[] },
|
||||
"fields"
|
||||
>,
|
||||
response: FormResponse,
|
||||
responseId: number,
|
||||
chosenAction?: {
|
||||
type: "customPageMessage" | "externalRedirectUrl" | "eventTypeRedirectUrl";
|
||||
value: string;
|
||||
}
|
||||
) {
|
||||
const fieldResponsesByIdentifier: FORM_SUBMITTED_WEBHOOK_RESPONSES = {};
|
||||
|
||||
for (const [fieldId, fieldResponse] of Object.entries(response)) {
|
||||
const field = form.fields.find((f) => f.id === fieldId);
|
||||
if (!field) {
|
||||
throw new Error(`Field with id ${fieldId} not found`);
|
||||
}
|
||||
// Use the label lowercased as the key to identify a field.
|
||||
// TODO: We seem to be using label from the response, Can we not use the field.label
|
||||
const key =
|
||||
form.fields.find((f) => f.id === fieldId)?.identifier ||
|
||||
(fieldResponse.label as keyof typeof fieldResponsesByIdentifier);
|
||||
fieldResponsesByIdentifier[key] = getFieldResponse({
|
||||
fieldResponseValue: fieldResponse.value,
|
||||
field,
|
||||
});
|
||||
}
|
||||
|
||||
const { userId, teamId } = getWebhookTargetEntity(form);
|
||||
|
||||
const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId });
|
||||
|
||||
const subscriberOptionsFormSubmitted = {
|
||||
userId,
|
||||
teamId,
|
||||
orgId,
|
||||
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED,
|
||||
};
|
||||
|
||||
const subscriberOptionsFormSubmittedNoEvent = {
|
||||
userId,
|
||||
teamId,
|
||||
orgId,
|
||||
triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT,
|
||||
};
|
||||
|
||||
const webhooksFormSubmitted = await getWebhooks(subscriberOptionsFormSubmitted);
|
||||
const webhooksFormSubmittedNoEvent = await getWebhooks(subscriberOptionsFormSubmittedNoEvent);
|
||||
|
||||
const promisesFormSubmitted = webhooksFormSubmitted.map((webhook) => {
|
||||
sendGenericWebhookPayload({
|
||||
secretKey: webhook.secret,
|
||||
triggerEvent: "FORM_SUBMITTED",
|
||||
createdAt: new Date().toISOString(),
|
||||
webhook,
|
||||
data: {
|
||||
formId: form.id,
|
||||
formName: form.name,
|
||||
teamId: form.teamId,
|
||||
responses: fieldResponsesByIdentifier,
|
||||
},
|
||||
rootData: {
|
||||
// Send responses unwrapped at root level for backwards compatibility
|
||||
...Object.entries(fieldResponsesByIdentifier).reduce((acc, [key, value]) => {
|
||||
acc[key] = value.value;
|
||||
return acc;
|
||||
}, {} as Record<string, FormResponse[keyof FormResponse]["value"]>),
|
||||
},
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing routing form webhook`, webhook, e);
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
try {
|
||||
const tasker: Tasker = await (await import("@calcom/features/tasker")).default;
|
||||
const promisesFormSubmittedNoEvent = webhooksFormSubmittedNoEvent.map((webhook) => {
|
||||
const scheduledAt = dayjs().add(15, "minute").toDate();
|
||||
|
||||
return tasker.create(
|
||||
"triggerFormSubmittedNoEventWebhook",
|
||||
{
|
||||
responseId,
|
||||
form: {
|
||||
id: form.id,
|
||||
name: form.name,
|
||||
teamId: form.teamId ?? null,
|
||||
},
|
||||
responses: fieldResponsesByIdentifier,
|
||||
redirect: chosenAction,
|
||||
webhook,
|
||||
},
|
||||
{ scheduledAt }
|
||||
);
|
||||
});
|
||||
|
||||
const promises = [...promisesFormSubmitted, ...promisesFormSubmittedNoEvent];
|
||||
|
||||
await Promise.all(promises);
|
||||
const orderedResponses = form.fields.reduce((acc, field) => {
|
||||
acc.push(response[field.id]);
|
||||
return acc;
|
||||
}, [] as OrderedResponses);
|
||||
|
||||
if (form.teamId) {
|
||||
if (form.userWithEmails?.length) {
|
||||
moduleLogger.debug(
|
||||
`Preparing to send Form Response email for Form:${form.id} to users: ${form.userWithEmails.join(
|
||||
","
|
||||
)}`
|
||||
);
|
||||
await sendResponseEmail(form, orderedResponses, form.userWithEmails);
|
||||
}
|
||||
} else if (form.settings?.emailOwnerOnSubmission) {
|
||||
moduleLogger.debug(
|
||||
`Preparing to send Form Response email for Form:${form.id} to form owner: ${form.user.email}`
|
||||
);
|
||||
await sendResponseEmail(form, orderedResponses, [form.user.email]);
|
||||
}
|
||||
} catch (e) {
|
||||
moduleLogger.error("Error triggering routing form response side effects", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const onFormSubmission = withReporting(_onFormSubmission, "onFormSubmission");
|
||||
|
||||
export const sendResponseEmail = async (
|
||||
form: Pick<App_RoutingForms_Form, "id" | "name" | "fields">,
|
||||
orderedResponses: OrderedResponses,
|
||||
toAddresses: string[]
|
||||
) => {
|
||||
try {
|
||||
if (typeof window === "undefined") {
|
||||
const { default: ResponseEmail } = await import("../emails/templates/response-email");
|
||||
const email = new ResponseEmail({ form: form, toAddresses, orderedResponses });
|
||||
await email.sendEmail();
|
||||
}
|
||||
} catch (e) {
|
||||
moduleLogger.error("Error sending response email", e);
|
||||
}
|
||||
};
|
||||
|
||||
function getWebhookTargetEntity(form: { teamId?: number | null; user: { id: number } }) {
|
||||
// If it's a team form, the target must be team webhook
|
||||
// If it's a user form, the target must be user webhook
|
||||
const isTeamForm = form.teamId;
|
||||
return { userId: isTeamForm ? null : form.user.id, teamId: isTeamForm ? form.teamId : null };
|
||||
}
|
||||
Vendored
+5
-2
@@ -1,8 +1,8 @@
|
||||
import type React from "react";
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { ConnectedApps } from "@calcom/lib/getConnectedApps";
|
||||
import type { EventTypeFormMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import type { ButtonProps } from "@calcom/ui/components/button";
|
||||
|
||||
export type IntegrationOAuthCallbackState = {
|
||||
@@ -22,8 +22,11 @@ export type CredentialOwner = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export type EventTypeAppCardApp = RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
|
||||
export type AppCardApp = ConnectedApps[number] & {
|
||||
credentialOwner?: CredentialOwner;
|
||||
};
|
||||
|
||||
export type EventTypeAppCardApp = AppCardApp & {
|
||||
credentialIds?: number[];
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCallback, useState } from "react";
|
||||
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
|
||||
import { AppSettings } from "@calcom/app-store/_components/AppSettings";
|
||||
import { getLocationFromApp, type EventLocationType } from "@calcom/app-store/locations";
|
||||
import type { CredentialOwner } from "@calcom/app-store/types";
|
||||
import type { AppCardApp } from "@calcom/app-store/types";
|
||||
import AppListCard from "@calcom/features/apps/components/AppListCard";
|
||||
import type { UpdateUsersDefaultConferencingAppParams } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
||||
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
||||
@@ -69,13 +69,7 @@ export const AppList = ({
|
||||
showToast("Default app updated successfully", "success");
|
||||
}, []);
|
||||
|
||||
const ChildAppCard = ({
|
||||
item,
|
||||
}: {
|
||||
item: RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
|
||||
credentialOwner?: CredentialOwner;
|
||||
};
|
||||
}) => {
|
||||
const ChildAppCard = ({ item }: { item: AppCardApp }) => {
|
||||
const appSlug = item?.slug;
|
||||
const appIsDefault =
|
||||
appSlug === defaultConferencingApp?.appSlug ||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Prisma, WorkflowReminder } from "@prisma/client";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendCancelledEmailsAndSMS } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
|
||||
@@ -18,6 +18,7 @@ import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
|
||||
import type { BookingDataSchemaGetter } from "@calcom/features/bookings/lib/dto/types";
|
||||
import type { CreateRegularBookingData, CreateBookingMeta } from "@calcom/features/bookings/lib/dto/types";
|
||||
import type { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits";
|
||||
import { handlePayment } from "@calcom/features/bookings/lib/handlePayment";
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
@@ -57,7 +58,6 @@ import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import type { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { handlePayment } from "@calcom/lib/payment/handlePayment";
|
||||
import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import type { Booking, User, Webhook } from "@prisma/client";
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import { getMeetingSessionsFromRoomName } from "@calcom/features/tasker/tasks/triggerNoShow/getMeetingSessionsFromRoomName";
|
||||
import { triggerHostNoShow } from "@calcom/features/tasker/tasks/triggerNoShow/triggerHostNoShow";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import tasker from "@calcom/features/tasker";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { uuid } from "short-uuid";
|
||||
|
||||
import { sendScheduledSeatsEmailsAndSMS } from "@calcom/emails";
|
||||
import { refreshCredentials } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials";
|
||||
import { handlePayment } from "@calcom/features/bookings/lib/handlePayment";
|
||||
import {
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import EventManager from "@calcom/lib/EventManager";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { handlePayment } from "@calcom/lib/payment/handlePayment";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
+6
-8
@@ -1,16 +1,14 @@
|
||||
import stripe from "@calcom/features/ee/payments/server/stripe";
|
||||
import {
|
||||
ZStripeCheckoutSessionInputSchema,
|
||||
type TStripeCheckoutSessionInputSchema,
|
||||
} from "@calcom/trpc/server/routers/publicViewer/stripeCheckoutSession.schema";
|
||||
|
||||
type StripeCheckoutSessionInput = {
|
||||
checkoutSessionId?: string;
|
||||
stripeCustomerId?: string;
|
||||
};
|
||||
|
||||
export class StripeService {
|
||||
static async getCheckoutSession(input: TStripeCheckoutSessionInputSchema) {
|
||||
static async getCheckoutSession(input: StripeCheckoutSessionInput) {
|
||||
const { checkoutSessionId, stripeCustomerId } = input;
|
||||
|
||||
// Moved the following data checks to superRefine
|
||||
ZStripeCheckoutSessionInputSchema.parse(input);
|
||||
|
||||
let customerId: string;
|
||||
let isPremiumUsername = false;
|
||||
let hasPaymentFailed = false;
|
||||
@@ -70,7 +70,7 @@ const DuplicateDialog = () => {
|
||||
}
|
||||
}, [searchParams?.get("dialog")]);
|
||||
|
||||
const duplicateMutation = trpc.viewer.eventTypes.duplicate.useMutation({
|
||||
const duplicateMutation = trpc.viewer.eventTypes.heavy.duplicate.useMutation({
|
||||
onSuccess: async ({ eventType }) => {
|
||||
await router.replace(`/event-types/${eventType.id}`);
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ export type LocationFormValues = Pick<FormValues, "id" | "locations" | "bookingF
|
||||
|
||||
export type EventTypeAssignedUsers = RouterOutputs["viewer"]["eventTypes"]["get"]["eventType"]["children"];
|
||||
export type EventTypeHosts = RouterOutputs["viewer"]["eventTypes"]["get"]["eventType"]["hosts"];
|
||||
export type EventTypeUpdateInput = RouterInputs["viewer"]["eventTypes"]["update"];
|
||||
export type EventTypeUpdateInput = RouterInputs["viewer"]["eventTypes"]["heavy"]["update"];
|
||||
export type TabMap = {
|
||||
advanced: React.ReactNode;
|
||||
ai?: React.ReactNode;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
|
||||
import incompleteBookingActionFunctions from "@calcom/app-store/routing-forms/lib/incompleteBooking/actionFunctions";
|
||||
import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/trpc/utils";
|
||||
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
|
||||
@@ -157,11 +157,12 @@ const getSeatReferenceId = (calEvent: Pick<CalendarEvent, "attendeeSeatId">): st
|
||||
};
|
||||
|
||||
export const getBookingUrl = (calEvent: CalendarEvent) => {
|
||||
const seatReferenceUid = getSeatReferenceId(calEvent);
|
||||
if (calEvent.platformClientId) {
|
||||
if (!calEvent.platformBookingUrl) return "";
|
||||
return `${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${
|
||||
calEvent.organizer.username
|
||||
}&changes=true`;
|
||||
}${seatReferenceUid ? `&seatReferenceUid=${seatReferenceUid}` : ""}&changes=true`;
|
||||
}
|
||||
|
||||
return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent)}?changes=true`;
|
||||
@@ -174,6 +175,8 @@ export const getPlatformManageLink = (
|
||||
t: TFunction
|
||||
) => {
|
||||
const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl;
|
||||
const seatUid = getSeatReferenceId(calEvent);
|
||||
|
||||
let res =
|
||||
calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl
|
||||
? `${t("need_to_reschedule_or_cancel")} `
|
||||
@@ -181,9 +184,9 @@ export const getPlatformManageLink = (
|
||||
if (calEvent.platformBookingUrl) {
|
||||
res += `Check Here: ${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${
|
||||
calEvent.organizer.username
|
||||
}${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}&changes=true${
|
||||
calEvent.platformCancelUrl || shouldDisplayReschedule ? ` ${t("or_lowercase")} ` : ""
|
||||
}`;
|
||||
}${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}${
|
||||
seatUid ? `&seatReferenceUid=${seatUid}` : ""
|
||||
}&changes=true${calEvent.platformCancelUrl || shouldDisplayReschedule ? ` ${t("or_lowercase")} ` : ""}`;
|
||||
}
|
||||
if (calEvent.platformCancelUrl) {
|
||||
res += `${t("cancel")}: ${getCancelLink(calEvent)}`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Prisma, SelectedCalendar } from "@prisma/client";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import { PeriodType, SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { userSelect } from "@calcom/prisma/selects";
|
||||
|
||||
@@ -46,7 +46,7 @@ export const useCreateEventType = (
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
|
||||
const createMutation = trpc.viewer.eventTypes.create.useMutation({
|
||||
const createMutation = trpc.viewer.eventTypes.heavy.create.useMutation({
|
||||
onSuccess: async ({ eventType }) => {
|
||||
onSuccessMutation(eventType);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
|
||||
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Prisma } from "@prisma/client";
|
||||
import type { ZodIssue } from "zod";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { stripeInvalidRequestErrorSchema } from "@calcom/app-store/_utils/stripe.types";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { ErrorWithCode } from "@calcom/lib/errors";
|
||||
|
||||
@@ -11,6 +10,7 @@ import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||
|
||||
import { HttpError } from "../http-error";
|
||||
import { redactError } from "../redactError";
|
||||
import { stripeInvalidRequestErrorSchema } from "../stripe-error";
|
||||
|
||||
function hasName(cause: unknown): cause is { name: string } {
|
||||
return !!cause && typeof cause === "object" && "name" in cause;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const errorSchema = z.object({
|
||||
name: z.string(),
|
||||
message: z.string(),
|
||||
stack: z.string().optional(),
|
||||
});
|
||||
|
||||
export const stripeInvalidRequestErrorSchema = errorSchema.extend({
|
||||
type: z.literal("StripeInvalidRequestError"),
|
||||
rawType: z.literal("invalid_request_error"),
|
||||
code: z.string().optional(),
|
||||
doc_url: z.string().optional(),
|
||||
statusCode: z.number().optional(),
|
||||
raw: z.unknown(),
|
||||
headers: z.record(z.string()),
|
||||
requestId: z.string(),
|
||||
param: z.string().optional(),
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import short from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { VideoApiAdapterMap } from "@calcom/app-store/video.adapters.generated";
|
||||
import { sendBrokenIntegrationEmail } from "@calcom/emails";
|
||||
import { getUid } from "@calcom/lib/CalEventParser";
|
||||
|
||||
@@ -131,7 +131,7 @@ const EventTypeWeb = ({
|
||||
teamId: eventType.team?.id || eventType.parent?.teamId,
|
||||
onlyInstalled: true,
|
||||
});
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
const updateMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
const currentValues = form.getValues();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export const useConnectedCalendars = (props: { enabled?: boolean }) => {
|
||||
});
|
||||
},
|
||||
enabled: props?.enabled !== undefined ? props.enabled && isInit : isInit,
|
||||
staleTime: 5000,
|
||||
});
|
||||
|
||||
return calendars;
|
||||
|
||||
@@ -4,10 +4,10 @@ export { getPublicEvent, type PublicEventType } from "@calcom/features/eventtype
|
||||
|
||||
export { getBulkUserEventTypes, getBulkTeamEventTypes } from "@calcom/lib/event-types/getBulkEventTypes";
|
||||
|
||||
export { createHandler as createEventType } from "@calcom/trpc/server/routers/viewer/eventTypes/create.handler";
|
||||
export { updateHandler as updateEventType } from "@calcom/trpc/server/routers/viewer/eventTypes/update.handler";
|
||||
export { createHandler as createEventType } from "@calcom/trpc/server/routers/viewer/eventTypes/heavy/create.handler";
|
||||
export { updateHandler as updateEventType } from "@calcom/trpc/server/routers/viewer/eventTypes/heavy/update.handler";
|
||||
|
||||
export type { TUpdateInputSchema as TUpdateEventTypeInputSchema } from "@calcom/trpc/server/routers/viewer/eventTypes/update.schema";
|
||||
export type { TUpdateInputSchema as TUpdateEventTypeInputSchema } from "@calcom/trpc/server/routers/viewer/eventTypes/heavy/update.schema";
|
||||
export type { EventTypesPublic } from "@calcom/lib/event-types/getEventTypesPublic";
|
||||
export { getEventTypesPublic } from "@calcom/lib/event-types/getEventTypesPublic";
|
||||
export { parseEventTypeColor } from "@calcom/lib/isEventTypeColor";
|
||||
@@ -24,7 +24,7 @@ export { getEventTypeById } from "@calcom/lib/event-types/getEventTypeById";
|
||||
export { getEventTypesByViewer } from "@calcom/lib/event-types/getEventTypesByViewer";
|
||||
export type { EventType } from "@calcom/lib/event-types/getEventTypeById";
|
||||
export type { EventTypesByViewer } from "@calcom/lib/event-types/getEventTypesByViewer";
|
||||
export type { UpdateEventTypeReturn } from "@calcom/trpc/server/routers/viewer/eventTypes/update.handler";
|
||||
export type { UpdateEventTypeReturn } from "@calcom/trpc/server/routers/viewer/eventTypes/heavy/update.handler";
|
||||
export { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries/teams";
|
||||
|
||||
export { bulkUpdateEventsToDefaultLocation } from "@calcom/lib/bulkUpdateEventsToDefaultLocation";
|
||||
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
import { handleCreatePhoneCall } from "@calcom/features/handleCreatePhoneCall";
|
||||
import handleMarkNoShow from "@calcom/features/handleMarkNoShow";
|
||||
import * as instantMeetingMethods from "@calcom/features/instant-meeting/handleInstantMeeting";
|
||||
import { getRoutedUrl } from "@calcom/features/routing-forms/lib/getRoutedUrl";
|
||||
import getAllUserBookings from "@calcom/lib/bookings/getAllUserBookings";
|
||||
import { symmetricEncrypt, symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { getRoutedUrl } from "@calcom/lib/server/getRoutedUrl";
|
||||
import { getTeamMemberEmailForResponseOrContactUsingUrlQuery } from "@calcom/lib/server/getTeamMemberEmailFromCrm";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
@@ -16,6 +16,7 @@ export const ENDPOINTS = [
|
||||
"deploymentSetup",
|
||||
"dsync",
|
||||
"eventTypes",
|
||||
"eventTypes/heavy",
|
||||
"features",
|
||||
"i18n",
|
||||
"insights",
|
||||
|
||||
@@ -27,6 +27,7 @@ const resolveEndpoint = (links: any) => {
|
||||
// to the correct API endpoints.
|
||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
||||
// - viewer.eventTypes.heavy.create - 4 segments paths for heavy sub-router
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (ctx: any) => {
|
||||
const parts = ctx.op.path.split(".");
|
||||
@@ -35,6 +36,9 @@ const resolveEndpoint = (links: any) => {
|
||||
if (parts.length == 2) {
|
||||
endpoint = parts[0] as keyof typeof links;
|
||||
path = parts[1];
|
||||
} else if (parts.length >= 3 && parts[2] === "heavy") {
|
||||
endpoint = parts[1] + "/heavy" as keyof typeof links;
|
||||
path = parts[3];
|
||||
} else {
|
||||
endpoint = parts[1] as keyof typeof links;
|
||||
path = parts.splice(2, parts.length - 2).join(".");
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "@calcom/app-store/basecamp3/lib/helpers";
|
||||
import type { BasecampToken } from "@calcom/app-store/basecamp3/lib/types";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "../lib/helpers";
|
||||
import type { BasecampToken } from "../lib/types";
|
||||
import type { TProjectMutationInputSchema } from "./projectMutation.schema";
|
||||
|
||||
interface ProjectMutationHandlerOptions {
|
||||
+3
-4
@@ -1,13 +1,12 @@
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "@calcom/app-store/basecamp3/lib/helpers";
|
||||
import type { BasecampToken } from "@calcom/app-store/basecamp3/lib/types";
|
||||
import type { PrismaClient } from "@calcom/prisma/client";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { refreshAccessToken } from "../lib/helpers";
|
||||
import type { BasecampToken } from "../lib/types";
|
||||
|
||||
interface ProjectsHandlerOptions {
|
||||
ctx: {
|
||||
prisma: PrismaClient;
|
||||
+1
-1
@@ -1,3 +1,4 @@
|
||||
import getConnectedForms from "@calcom/app-store/routing-forms/lib/getConnectedForms";
|
||||
import { entityPrismaWhereClause } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -5,7 +6,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import getConnectedForms from "../lib/getConnectedForms";
|
||||
import type { TDeleteFormInputSchema } from "./deleteForm.schema";
|
||||
import { checkPermissionOnExistingRoutingForm } from "./permissions";
|
||||
|
||||
+9
-10
@@ -1,6 +1,13 @@
|
||||
import type { App_RoutingForms_Form } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { createFallbackRoute } from "@calcom/app-store/routing-forms/lib/createFallbackRoute";
|
||||
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
||||
import { isFallbackRoute } from "@calcom/app-store/routing-forms/lib/isFallbackRoute";
|
||||
import isRouter from "@calcom/app-store/routing-forms/lib/isRouter";
|
||||
import isRouterLinkedField from "@calcom/app-store/routing-forms/lib/isRouterLinkedField";
|
||||
import type { SerializableForm } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { zodFields, zodRouterRoute, zodRoutes } from "@calcom/app-store/routing-forms/zod";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { entityPrismaWhereClause, canEditEntity } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
@@ -9,14 +16,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { createFallbackRoute } from "../lib/createFallbackRoute";
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
import { isFallbackRoute } from "../lib/isFallbackRoute";
|
||||
import { isFormCreateEditAllowed } from "../lib/isFormCreateEditAllowed";
|
||||
import isRouter from "../lib/isRouter";
|
||||
import isRouterLinkedField from "../lib/isRouterLinkedField";
|
||||
import type { SerializableForm } from "../types/types";
|
||||
import { zodFields, zodRouterRoute, zodRoutes } from "../zod";
|
||||
import type { TFormMutationInputSchema } from "./formMutation.schema";
|
||||
import { checkPermissionOnExistingRoutingForm } from "./permissions";
|
||||
|
||||
@@ -69,8 +68,8 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
|
||||
|
||||
inputFields = inputFields || [];
|
||||
inputRoutes = inputRoutes || [];
|
||||
type InputFields = typeof inputFields;
|
||||
type InputRoutes = typeof inputRoutes;
|
||||
type InputFields = NonNullable<typeof inputFields>;
|
||||
type InputRoutes = NonNullable<typeof inputRoutes>;
|
||||
let routes: InputRoutes;
|
||||
let fields: InputFields;
|
||||
type DuplicateFrom = NonNullable<typeof duplicateFrom>;
|
||||
+1
-2
@@ -1,9 +1,8 @@
|
||||
import z from "zod";
|
||||
|
||||
import { zodFields, zodRoutes } from "@calcom/app-store/routing-forms/zod";
|
||||
import { RoutingFormSettings } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { zodFields, zodRoutes } from "../zod";
|
||||
|
||||
export const ZFormMutationInputSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
+1
-3
@@ -1,11 +1,9 @@
|
||||
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
||||
import { entityPrismaWhereClause } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
import type { TFormQueryInputSchema } from "./formQuery.schema";
|
||||
import { checkPermissionOnExistingRoutingForm } from "./permissions";
|
||||
|
||||
+2
-2
@@ -1,5 +1,7 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
||||
import type { zodFields, zodRoutes } from "@calcom/app-store/routing-forms/zod";
|
||||
import { hasFilter } from "@calcom/features/filters/lib/hasFilter";
|
||||
import { entityPrismaWhereClause, canEditEntity } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import logger from "@calcom/lib/logger";
|
||||
@@ -9,8 +11,6 @@ import type { Prisma } from "@calcom/prisma/client";
|
||||
import { entries } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
import type { zodFields, zodRoutes } from "../zod";
|
||||
import type { TFormSchema } from "./forms.schema";
|
||||
|
||||
interface FormsHandlerOptions {
|
||||
+1
-1
@@ -1,10 +1,10 @@
|
||||
import { enabledIncompleteBookingApps } from "@calcom/app-store/routing-forms/lib/enabledIncompleteBookingApps";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { safeCredentialSelect } from "@calcom/prisma/selects/credential";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { enabledIncompleteBookingApps } from "../lib/enabledIncompleteBookingApps";
|
||||
import type { TGetIncompleteBookingSettingsInputSchema } from "./getIncompleteBookingSettings.schema";
|
||||
|
||||
interface GetIncompleteBookingSettingsOptions {
|
||||
+3
-3
@@ -1,5 +1,8 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
|
||||
import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm";
|
||||
import type { FormResponse } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { canAccessEntity } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
@@ -7,9 +10,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { enrichFormWithMigrationData } from "../enrichFormWithMigrationData";
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
import type { FormResponse } from "../types/types";
|
||||
import type { ZFormByResponseIdInputSchema } from "./_router";
|
||||
|
||||
type GetResponseWithFormFieldsOptions = {
|
||||
+1
-1
@@ -1,10 +1,10 @@
|
||||
import incompleteBookingActionDataSchemas from "@calcom/app-store/routing-forms/lib/incompleteBooking/actionDataSchemas";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import incompleteBookingActionDataSchemas from "../lib/incompleteBooking/actionDataSchemas";
|
||||
import type { TSaveIncompleteBookingSettingsInputSchema } from "./saveIncompleteBookingSettings.schema";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["incomplete-booking"] });
|
||||
@@ -1,5 +1,6 @@
|
||||
import { StripeService } from "@calcom/lib/server/service/stripe";
|
||||
import { StripeService } from "@calcom/features/ee/payments/server/stripe-service";
|
||||
|
||||
import { ZStripeCheckoutSessionInputSchema } from "./stripeCheckoutSession.schema";
|
||||
import type { TStripeCheckoutSessionInputSchema } from "./stripeCheckoutSession.schema";
|
||||
|
||||
type StripeCheckoutSessionOptions = {
|
||||
@@ -7,7 +8,9 @@ type StripeCheckoutSessionOptions = {
|
||||
};
|
||||
|
||||
export const stripeCheckoutSessionHandler = async ({ input }: StripeCheckoutSessionOptions) => {
|
||||
return await StripeService.getCheckoutSession(input);
|
||||
const parsedInput = ZStripeCheckoutSessionInputSchema.parse(input);
|
||||
|
||||
return await StripeService.getCheckoutSession(parsedInput);
|
||||
};
|
||||
|
||||
export default stripeCheckoutSessionHandler;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import app_Basecamp3 from "@calcom/app-store/basecamp3/trpc-router";
|
||||
import app_RoutingForms from "@calcom/app-store/routing-forms/trpc-router";
|
||||
import { userAdminRouter } from "@calcom/features/ee/users/server/trpc-router";
|
||||
import { featureFlagRouter } from "@calcom/features/flags/server/router";
|
||||
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
|
||||
|
||||
import { router } from "../../trpc";
|
||||
import { router, mergeRouters } from "../../trpc";
|
||||
import app_Basecamp3 from "../apps/basecamp3/_router";
|
||||
import app_RoutingForms from "../apps/routing-forms/_router";
|
||||
import { loggedInViewerRouter } from "../loggedInViewer/_router";
|
||||
import { publicViewerRouter } from "../publicViewer/_router";
|
||||
import { timezonesRouter } from "../publicViewer/timezones/_router";
|
||||
@@ -24,6 +24,7 @@ import { delegationCredentialRouter } from "./delegationCredential/_router";
|
||||
import { deploymentSetupRouter } from "./deploymentSetup/_router";
|
||||
import { dsyncRouter } from "./dsync/_router";
|
||||
import { eventTypesRouter } from "./eventTypes/_router";
|
||||
import { eventTypesRouter as heavyEventTypesRouter } from "./eventTypes/heavy/_router";
|
||||
import { filterSegmentsRouter } from "./filterSegments/_router";
|
||||
import { googleWorkspaceRouter } from "./googleWorkspace/_router";
|
||||
import { i18nRouter } from "./i18n/_router";
|
||||
@@ -53,7 +54,12 @@ export const viewerRouter = router({
|
||||
calendars: calendarsRouter,
|
||||
calVideo: calVideoRouter,
|
||||
credentials: credentialsRouter,
|
||||
eventTypes: eventTypesRouter,
|
||||
eventTypes: mergeRouters(
|
||||
eventTypesRouter,
|
||||
router({
|
||||
heavy: heavyEventTypesRouter,
|
||||
})
|
||||
),
|
||||
availability: availabilityRouter,
|
||||
teams: viewerTeamsRouter,
|
||||
timezones: timezonesRouter,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
|
||||
import { commonBookingSchema } from "./types";
|
||||
|
||||
|
||||
@@ -4,15 +4,12 @@ import { logP } from "@calcom/lib/perf";
|
||||
|
||||
import authedProcedure from "../../../procedures/authedProcedure";
|
||||
import { router } from "../../../trpc";
|
||||
import { ZCreateInputSchema } from "./create.schema";
|
||||
import { ZDeleteInputSchema } from "./delete.schema";
|
||||
import { ZDuplicateInputSchema } from "./duplicate.schema";
|
||||
import { ZEventTypeInputSchema, ZGetEventTypesFromGroupSchema } from "./getByViewer.schema";
|
||||
import { ZGetHashedLinkInputSchema } from "./getHashedLink.schema";
|
||||
import { ZGetHashedLinksInputSchema } from "./getHashedLinks.schema";
|
||||
import { ZGetTeamAndEventTypeOptionsSchema } from "./getTeamAndEventTypeOptions.schema";
|
||||
import { get } from "./procedures/get";
|
||||
import { ZUpdateInputSchema } from "./update.schema";
|
||||
import { eventOwnerProcedure } from "./util";
|
||||
|
||||
type BookingsRouterHandlerCache = {
|
||||
@@ -22,11 +19,8 @@ type BookingsRouterHandlerCache = {
|
||||
getTeamAndEventTypeOptions?: typeof import("./getTeamAndEventTypeOptions.handler").getTeamAndEventTypeOptions;
|
||||
list?: typeof import("./list.handler").listHandler;
|
||||
listWithTeam?: typeof import("./listWithTeam.handler").listWithTeamHandler;
|
||||
create?: typeof import("./create.handler").createHandler;
|
||||
get?: typeof import("./get.handler").getHandler;
|
||||
update?: typeof import("./update.handler").updateHandler;
|
||||
delete?: typeof import("./delete.handler").deleteHandler;
|
||||
duplicate?: typeof import("./duplicate.handler").duplicateHandler;
|
||||
bulkEventFetch?: typeof import("./bulkEventFetch.handler").bulkEventFetchHandler;
|
||||
bulkUpdateToDefaultLocation?: typeof import("./bulkUpdateToDefaultLocation.handler").bulkUpdateToDefaultLocationHandler;
|
||||
};
|
||||
@@ -115,26 +109,8 @@ export const eventTypesRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
create: authedProcedure.input(ZCreateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { createHandler } = await import("./create.handler");
|
||||
|
||||
return createHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
|
||||
get,
|
||||
|
||||
update: eventOwnerProcedure.input(ZUpdateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { updateHandler } = await import("./update.handler");
|
||||
|
||||
return updateHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
|
||||
delete: eventOwnerProcedure.input(ZDeleteInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { deleteHandler } = await import("./delete.handler");
|
||||
|
||||
@@ -144,15 +120,6 @@ export const eventTypesRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
duplicate: eventOwnerProcedure.input(ZDuplicateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { duplicateHandler } = await import("./duplicate.handler");
|
||||
|
||||
return duplicateHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
|
||||
bulkEventFetch: authedProcedure.query(async ({ ctx }) => {
|
||||
const { bulkEventFetchHandler } = await import("./bulkEventFetch.handler");
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { logP } from "@calcom/lib/perf";
|
||||
|
||||
import authedProcedure from "../../../../procedures/authedProcedure";
|
||||
import { router } from "../../../../trpc";
|
||||
import { ZCreateInputSchema } from "./create.schema";
|
||||
import { ZDuplicateInputSchema } from "./duplicate.schema";
|
||||
import { eventOwnerProcedure } from "../util";
|
||||
import { ZUpdateInputSchema } from "./update.schema";
|
||||
|
||||
type BookingsRouterHandlerCache = {
|
||||
create?: typeof import("./create.handler").createHandler;
|
||||
duplicate?: typeof import("./duplicate.handler").duplicateHandler;
|
||||
update?: typeof import("./update.handler").updateHandler;
|
||||
};
|
||||
|
||||
const UNSTABLE_HANDLER_CACHE: BookingsRouterHandlerCache = {};
|
||||
|
||||
export const eventTypesRouter = router({
|
||||
create: authedProcedure.input(ZCreateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { createHandler } = await import("./create.handler");
|
||||
|
||||
return createHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
duplicate: eventOwnerProcedure.input(ZDuplicateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { duplicateHandler } = await import("./duplicate.handler");
|
||||
|
||||
return duplicateHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
}),
|
||||
update: eventOwnerProcedure.input(ZUpdateInputSchema).mutation(async ({ ctx, input }) => {
|
||||
const { updateHandler } = await import("./update.handler");
|
||||
|
||||
return updateHandler({
|
||||
ctx,
|
||||
input,
|
||||
});
|
||||
})
|
||||
});
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { DailyLocationType } from "@calcom/app-store/constants";
|
||||
import { getDefaultLocations } from "@calcom/lib/server/getDefaultLocations";
|
||||
import { EventTypeRepository } from "@calcom/lib/server/repository/eventTypeRepository";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
@@ -9,7 +9,7 @@ import type { EventTypeLocation } from "@calcom/prisma/zod/custom/eventtype";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import type { TrpcSessionUser } from "../../../../types";
|
||||
import type { TCreateInputSchema } from "./create.schema";
|
||||
|
||||
type SessionUser = NonNullable<TrpcSessionUser>;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
|
||||
import prismaMock from "../../../../../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user