Compare commits
74
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33560ef82 | ||
|
|
84f34b2244 | ||
|
|
086bae03d3 | ||
|
|
4a0a429774 | ||
|
|
c4142ab4e7 | ||
|
|
95f8046db9 | ||
|
|
d8a87e9f04 | ||
|
|
86dcd6f0ff | ||
|
|
411a14dd3d | ||
|
|
03bc8db64a | ||
|
|
fe7fd8bc79 | ||
|
|
92eb41c117 | ||
|
|
a1fca56aac | ||
|
|
f3630399ee | ||
|
|
5375c656af | ||
|
|
1792d49634 | ||
|
|
de98578fdd | ||
|
|
c0a59a2837 | ||
|
|
29201006d5 | ||
|
|
7a1c780088 | ||
|
|
550f34c128 | ||
|
|
d6b17b76db | ||
|
|
c42e803cfa | ||
|
|
0233c3a11a | ||
|
|
71a2b6956f | ||
|
|
8282557ebe | ||
|
|
083a162d0e | ||
|
|
60487ea02e | ||
|
|
60a8841439 | ||
|
|
184d1eb751 | ||
|
|
9fea30a44d | ||
|
|
04efcbcdb6 | ||
|
|
2b29b5607d | ||
|
|
20f19c2509 | ||
|
|
5d0e66763e | ||
|
|
bcfbc24e93 | ||
|
|
37d805b007 | ||
|
|
6a246e6c57 | ||
|
|
3a0229f60d | ||
|
|
0bf95ec568 | ||
|
|
6f05142447 | ||
|
|
e02108d535 | ||
|
|
b7e668217d | ||
|
|
472ffef5cf | ||
|
|
5acdb208f5 | ||
|
|
7bf5d9cf64 | ||
|
|
34a80180ef | ||
|
|
3bbba9c6cb | ||
|
|
b052c2e029 | ||
|
|
4eb884e2a6 | ||
|
|
3f8ce8ff50 | ||
|
|
67a2371c05 | ||
|
|
80250d32bd | ||
|
|
0fadbe1ce9 | ||
|
|
9c3ee262b4 | ||
|
|
b62eba8128 | ||
|
|
ef114d7727 | ||
|
|
be7d127c9e | ||
|
|
1209150785 | ||
|
|
31940b919e | ||
|
|
c3c990db43 | ||
|
|
84a742336c | ||
|
|
ada8ffd3b9 | ||
|
|
2277d90326 | ||
|
|
cbc27a78ee | ||
|
|
a2189643bd | ||
|
|
a6101b3da9 | ||
|
|
216fb2141b | ||
|
|
afb54ca048 | ||
|
|
f4b23b3a03 | ||
|
|
d6cd593b95 | ||
|
|
2d9c1b3fd9 | ||
|
|
f2f10a06d9 | ||
|
|
e1def884f3 |
@@ -386,6 +386,11 @@ UNKEY_ROOT_KEY=
|
|||||||
# Used for Cal.ai Voice AI Agents
|
# Used for Cal.ai Voice AI Agents
|
||||||
# https://retellai.com
|
# https://retellai.com
|
||||||
RETELL_AI_KEY=
|
RETELL_AI_KEY=
|
||||||
|
# Local testing overrides for Retell AI
|
||||||
|
RETELL_AI_TEST_MODE=false
|
||||||
|
# JSON mapping of local to production event type IDs (e.g. {"50": 747280} without any string)
|
||||||
|
RETELL_AI_TEST_EVENT_TYPE_MAP=
|
||||||
|
RETELL_AI_TEST_CAL_API_KEY=
|
||||||
|
|
||||||
# Used for buying phone number for cal ai voice agent
|
# Used for buying phone number for cal ai voice agent
|
||||||
STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=
|
STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=
|
||||||
|
|||||||
@@ -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/*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
has-files-requiring-all-checks:
|
has-files-requiring-all-checks:
|
||||||
- "!(**.md|.github/CODEOWNERS|docs/**|help/**|apps/web/public/static/locales/**/common.json)"
|
- "!(**.md|**.mdx|.github/CODEOWNERS|docs/**|help/**|apps/web/public/static/locales/**/common.json|i18n.lock)"
|
||||||
- name: Get Latest Commit SHA
|
- name: Get Latest Commit SHA
|
||||||
id: get_sha
|
id: get_sha
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
Vendored
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
@@ -4,7 +4,7 @@ import type { NextApiRequest } from "next";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||||
import { canUserAccessTeamWithRole } from "~/pages/api/teams/[teamId]/_auth-middleware";
|
import { canUserAccessTeamWithRole } from "~/pages/api/teams/[teamId]/_auth-middleware";
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"@axiomhq/winston": "^1.2.0",
|
"@axiomhq/winston": "^1.2.0",
|
||||||
"@calcom/platform-constants": "*",
|
"@calcom/platform-constants": "*",
|
||||||
"@calcom/platform-enums": "*",
|
"@calcom/platform-enums": "*",
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.335",
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.347",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
@@ -55,9 +55,9 @@
|
|||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/swagger": "^7.3.0",
|
"@nestjs/swagger": "^7.3.0",
|
||||||
"@nestjs/throttler": "6.2.1",
|
"@nestjs/throttler": "6.2.1",
|
||||||
"@sentry/nestjs": "^8.37.1",
|
"@sentry/nestjs": "^9.15.0",
|
||||||
"@sentry/node": "^8.8.0",
|
"@sentry/node": "^9.15.0",
|
||||||
"@sentry/profiling-node": "^8.37.1",
|
"@sentry/profiling-node": "^9.15.0",
|
||||||
"@snyk/protect": "latest",
|
"@snyk/protect": "latest",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"bull": "^4.12.4",
|
"bull": "^4.12.4",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||||
import { CreationSource } from "@prisma/client";
|
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { NextApiRequest } from "next/types";
|
import { NextApiRequest } from "next/types";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
@@ -58,6 +57,7 @@ import {
|
|||||||
getBookingForReschedule,
|
getBookingForReschedule,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
} from "@calcom/platform-libraries";
|
} from "@calcom/platform-libraries";
|
||||||
|
import { CreationSource } from "@calcom/platform-libraries";
|
||||||
import {
|
import {
|
||||||
GetBookingsInput_2024_04_15,
|
GetBookingsInput_2024_04_15,
|
||||||
CancelBookingInput_2024_04_15,
|
CancelBookingInput_2024_04_15,
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
slug: seatedEventTypeSlug,
|
slug: seatedEventTypeSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
seatsPerTimeSlot: 3,
|
seatsPerTimeSlot: 3,
|
||||||
|
seatsShowAttendees: true,
|
||||||
},
|
},
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,8 +53,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let apiKeyString: string;
|
let apiKeyString: string;
|
||||||
|
|
||||||
let seatedEventTypeId: number;
|
let seatedEventTypeId: number;
|
||||||
|
let seatedEventTypeIdAttendeesDisabledId: number;
|
||||||
|
|
||||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||||
|
const seatedEventSlugAttendeesDisabled = `seated-bookings-event-type-attendees-disabled-${randomString()}`;
|
||||||
|
|
||||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||||
let createdSeatedBooking2: CreateSeatedBookingOutput_2024_08_13;
|
let createdSeatedBooking2: CreateSeatedBookingOutput_2024_08_13;
|
||||||
@@ -121,6 +123,28 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
);
|
);
|
||||||
seatedEventTypeId = seatedEvent.id;
|
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();
|
app = moduleRef.createNestApplication();
|
||||||
bootstrap(app as NestExpressApplication);
|
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", () => {
|
||||||
describe("cancel seated booking as attendee", () => {
|
describe("cancel seated booking as attendee", () => {
|
||||||
it("should cancel seated booking", async () => {
|
it("should cancel seated booking", async () => {
|
||||||
|
|||||||
@@ -576,10 +576,13 @@ export class BookingsService_2024_08_13 {
|
|||||||
return this.outputService.getOutputRecurringBooking(booking);
|
return this.outputService.getOutputRecurringBooking(booking);
|
||||||
}
|
}
|
||||||
if (isRecurring && isSeated) {
|
if (isRecurring && isSeated) {
|
||||||
return this.outputService.getOutputRecurringSeatedBooking(booking);
|
return this.outputService.getOutputRecurringSeatedBooking(
|
||||||
|
booking,
|
||||||
|
!!booking.eventType?.seatsShowAttendees
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (isSeated) {
|
if (isSeated) {
|
||||||
return this.outputService.getOutputSeatedBooking(booking);
|
return this.outputService.getOutputSeatedBooking(booking, !!booking.eventType?.seatsShowAttendees);
|
||||||
}
|
}
|
||||||
return this.outputService.getOutputBooking(booking);
|
return this.outputService.getOutputBooking(booking);
|
||||||
}
|
}
|
||||||
@@ -591,7 +594,10 @@ export class BookingsService_2024_08_13 {
|
|||||||
const ids = recurringBooking.map((booking) => booking.id);
|
const ids = recurringBooking.map((booking) => booking.id);
|
||||||
const isRecurringSeated = !!recurringBooking[0].eventType?.seatsPerTimeSlot;
|
const isRecurringSeated = !!recurringBooking[0].eventType?.seatsPerTimeSlot;
|
||||||
if (isRecurringSeated) {
|
if (isRecurringSeated) {
|
||||||
return this.outputService.getOutputRecurringSeatedBookings(ids);
|
return this.outputService.getOutputRecurringSeatedBookings(
|
||||||
|
ids,
|
||||||
|
!!recurringBooking[0].eventType?.seatsShowAttendees
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.outputService.getOutputRecurringBookings(ids);
|
return this.outputService.getOutputRecurringBookings(ids);
|
||||||
@@ -657,9 +663,19 @@ export class BookingsService_2024_08_13 {
|
|||||||
if (isRecurring && !isSeated) {
|
if (isRecurring && !isSeated) {
|
||||||
formattedBookings.push(this.outputService.getOutputRecurringBooking(formatted));
|
formattedBookings.push(this.outputService.getOutputRecurringBooking(formatted));
|
||||||
} else if (isRecurring && isSeated) {
|
} else if (isRecurring && isSeated) {
|
||||||
formattedBookings.push(this.outputService.getOutputRecurringSeatedBooking(formatted));
|
formattedBookings.push(
|
||||||
|
this.outputService.getOutputRecurringSeatedBooking(
|
||||||
|
formatted,
|
||||||
|
!!formatted.eventType?.seatsShowAttendees
|
||||||
|
)
|
||||||
|
);
|
||||||
} else if (isSeated) {
|
} else if (isSeated) {
|
||||||
formattedBookings.push(await this.outputService.getOutputSeatedBooking(formatted));
|
formattedBookings.push(
|
||||||
|
await this.outputService.getOutputSeatedBooking(
|
||||||
|
formatted,
|
||||||
|
!!formatted.eventType?.seatsShowAttendees
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
formattedBookings.push(await this.outputService.getOutputBooking(formatted));
|
formattedBookings.push(await this.outputService.getOutputBooking(formatted));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { UsersRepository } from "@/modules/users/users.repository";
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { CreationSource } from "@prisma/client";
|
|
||||||
import { isURL, isPhoneNumber } from "class-validator";
|
import { isURL, isPhoneNumber } from "class-validator";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
@@ -30,6 +29,7 @@ import { NextApiRequest } from "next/types";
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CreationSource } from "@calcom/platform-libraries";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/platform-libraries/event-types";
|
import { EventTypeMetaDataSchema } from "@calcom/platform-libraries/event-types";
|
||||||
import {
|
import {
|
||||||
CancelBookingInput,
|
CancelBookingInput,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type DatabaseBooking = Booking & {
|
|||||||
eventType: {
|
eventType: {
|
||||||
id: number;
|
id: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
seatsShowAttendees?: boolean | null;
|
||||||
} | null;
|
} | null;
|
||||||
attendees: {
|
attendees: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -167,6 +168,7 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
getUserDefinedMetadata(databaseMetadata: DatabaseMetadata) {
|
getUserDefinedMetadata(databaseMetadata: DatabaseMetadata) {
|
||||||
if (databaseMetadata === null) return {};
|
if (databaseMetadata === null) return {};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { videoCallUrl, ...userDefinedMetadata } = databaseMetadata;
|
const { videoCallUrl, ...userDefinedMetadata } = databaseMetadata;
|
||||||
|
|
||||||
return userDefinedMetadata;
|
return userDefinedMetadata;
|
||||||
@@ -266,11 +268,14 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
databaseBooking: DatabaseBooking,
|
databaseBooking: DatabaseBooking,
|
||||||
seatUid: string
|
seatUid: string
|
||||||
): Promise<CreateSeatedBookingOutput_2024_08_13> {
|
): Promise<CreateSeatedBookingOutput_2024_08_13> {
|
||||||
const getSeatedBookingOutput = await this.getOutputSeatedBooking(databaseBooking);
|
const getSeatedBookingOutput = await this.getOutputSeatedBooking(
|
||||||
|
databaseBooking,
|
||||||
|
!!databaseBooking.eventType?.seatsShowAttendees
|
||||||
|
);
|
||||||
return { ...getSeatedBookingOutput, seatUid };
|
return { ...getSeatedBookingOutput, seatUid };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOutputSeatedBooking(databaseBooking: DatabaseBooking) {
|
async getOutputSeatedBooking(databaseBooking: DatabaseBooking, showAttendees: boolean) {
|
||||||
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
||||||
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
||||||
const duration = dateEnd.diff(dateStart, "minutes").minutes;
|
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" });
|
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
|
// note(Lauris): I don't know why plainToClass erases booking.attendees[n].responses so attaching manually
|
||||||
parsed.attendees = databaseBooking.attendees.map((attendee) => {
|
parsed.attendees = showAttendees
|
||||||
const { responses } = safeParse(
|
? databaseBooking.attendees.map((attendee) => {
|
||||||
seatedBookingDataSchema,
|
const { responses } = safeParse(
|
||||||
attendee.bookingSeat?.data,
|
seatedBookingDataSchema,
|
||||||
defaultSeatedBookingData,
|
attendee.bookingSeat?.data,
|
||||||
false
|
defaultSeatedBookingData,
|
||||||
);
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const attendeeData = {
|
const attendeeData = {
|
||||||
name: attendee.name,
|
name: attendee.name,
|
||||||
email: attendee.email,
|
email: attendee.email,
|
||||||
timeZone: attendee.timeZone,
|
timeZone: attendee.timeZone,
|
||||||
language: attendee.locale,
|
language: attendee.locale,
|
||||||
absent: !!attendee.noShow,
|
absent: !!attendee.noShow,
|
||||||
seatUid: attendee.bookingSeat?.referenceUid,
|
seatUid: attendee.bookingSeat?.referenceUid,
|
||||||
bookingFieldsResponses: {},
|
bookingFieldsResponses: {},
|
||||||
};
|
};
|
||||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||||
attendeeParsed.metadata = safeParse(
|
attendeeParsed.metadata = safeParse(
|
||||||
seatedBookingMetadataSchema,
|
seatedBookingMetadataSchema,
|
||||||
attendee.bookingSeat?.metadata,
|
attendee.bookingSeat?.metadata,
|
||||||
defaultSeatedBookingMetadata,
|
defaultSeatedBookingMetadata,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
// note(Lauris): as of now email is not returned for privacy
|
// note(Lauris): as of now email is not returned for privacy
|
||||||
delete attendeeParsed.bookingFieldsResponses.email;
|
delete attendeeParsed.bookingFieldsResponses.email;
|
||||||
|
|
||||||
return attendeeParsed;
|
return attendeeParsed;
|
||||||
});
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOutputRecurringSeatedBookings(bookingsIds: number[]) {
|
async getOutputRecurringSeatedBookings(bookingsIds: number[], showAttendees: boolean) {
|
||||||
const transformed = [];
|
const transformed = [];
|
||||||
|
|
||||||
for (const bookingId of bookingsIds) {
|
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`);
|
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());
|
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,
|
databaseBooking: DatabaseBooking,
|
||||||
seatUid: string
|
seatUid: string
|
||||||
): CreateRecurringSeatedBookingOutput_2024_08_13 {
|
): CreateRecurringSeatedBookingOutput_2024_08_13 {
|
||||||
const getRecurringSeatedBookingOutput = this.getOutputRecurringSeatedBooking(databaseBooking);
|
const getRecurringSeatedBookingOutput = this.getOutputRecurringSeatedBooking(
|
||||||
|
databaseBooking,
|
||||||
|
!!databaseBooking.eventType?.seatsShowAttendees
|
||||||
|
);
|
||||||
return { ...getRecurringSeatedBookingOutput, seatUid };
|
return { ...getRecurringSeatedBookingOutput, seatUid };
|
||||||
}
|
}
|
||||||
|
|
||||||
getOutputRecurringSeatedBooking(databaseBooking: DatabaseBooking) {
|
getOutputRecurringSeatedBooking(databaseBooking: DatabaseBooking, showAttendees: boolean) {
|
||||||
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
||||||
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
||||||
const duration = dateEnd.diff(dateStart, "minutes").minutes;
|
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
|
// note(Lauris): I don't know why plainToClass erases booking.attendees[n].responses so attaching manually
|
||||||
parsed.attendees = databaseBooking.attendees.map((attendee) => {
|
parsed.attendees = showAttendees
|
||||||
const { responses } = safeParse(
|
? databaseBooking.attendees.map((attendee) => {
|
||||||
seatedBookingDataSchema,
|
const { responses } = safeParse(
|
||||||
attendee.bookingSeat?.data,
|
seatedBookingDataSchema,
|
||||||
defaultSeatedBookingData,
|
attendee.bookingSeat?.data,
|
||||||
false
|
defaultSeatedBookingData,
|
||||||
);
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const attendeeData = {
|
const attendeeData = {
|
||||||
name: attendee.name,
|
name: attendee.name,
|
||||||
email: attendee.email,
|
email: attendee.email,
|
||||||
timeZone: attendee.timeZone,
|
timeZone: attendee.timeZone,
|
||||||
language: attendee.locale,
|
language: attendee.locale,
|
||||||
absent: !!attendee.noShow,
|
absent: !!attendee.noShow,
|
||||||
seatUid: attendee.bookingSeat?.referenceUid,
|
seatUid: attendee.bookingSeat?.referenceUid,
|
||||||
bookingFieldsResponses: {},
|
bookingFieldsResponses: {},
|
||||||
};
|
};
|
||||||
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
const attendeeParsed = plainToClass(SeatedAttendee, attendeeData, { strategy: "excludeAll" });
|
||||||
attendeeParsed.bookingFieldsResponses = responses || {};
|
attendeeParsed.bookingFieldsResponses = responses || {};
|
||||||
attendeeParsed.metadata = safeParse(
|
attendeeParsed.metadata = safeParse(
|
||||||
seatedBookingMetadataSchema,
|
seatedBookingMetadataSchema,
|
||||||
attendee.bookingSeat?.metadata,
|
attendee.bookingSeat?.metadata,
|
||||||
defaultSeatedBookingMetadata,
|
defaultSeatedBookingMetadata,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
// note(Lauris): as of now email is not returned for privacy
|
// note(Lauris): as of now email is not returned for privacy
|
||||||
delete attendeeParsed.bookingFieldsResponses.email;
|
delete attendeeParsed.bookingFieldsResponses.email;
|
||||||
return attendeeParsed;
|
return attendeeParsed;
|
||||||
});
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -30,6 +30,7 @@ import {
|
|||||||
NoticeThresholdUnitEnum,
|
NoticeThresholdUnitEnum,
|
||||||
FrequencyInput,
|
FrequencyInput,
|
||||||
} from "@calcom/platform-enums";
|
} from "@calcom/platform-enums";
|
||||||
|
import { SchedulingType } from "@calcom/platform-libraries";
|
||||||
import {
|
import {
|
||||||
ApiSuccessResponse,
|
ApiSuccessResponse,
|
||||||
CreateEventTypeInput_2024_06_14,
|
CreateEventTypeInput_2024_06_14,
|
||||||
@@ -39,7 +40,6 @@ import {
|
|||||||
NotesDefaultFieldInput_2024_06_14,
|
NotesDefaultFieldInput_2024_06_14,
|
||||||
UpdateEventTypeInput_2024_06_14,
|
UpdateEventTypeInput_2024_06_14,
|
||||||
} from "@calcom/platform-types";
|
} from "@calcom/platform-types";
|
||||||
import { SchedulingType } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
const orderBySlug = (a: { slug: string }, b: { slug: string }) => {
|
const orderBySlug = (a: { slug: string }, b: { slug: string }) => {
|
||||||
if (a.slug < b.slug) return -1;
|
if (a.slug < b.slug) return -1;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MembershipRole } from "@prisma/client";
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export const SYSTEM_ADMIN_ROLE = "SYSADMIN";
|
export const SYSTEM_ADMIN_ROLE = "SYSADMIN";
|
||||||
export const ORG_ROLES = [
|
export const ORG_ROLES = [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Reflector } from "@nestjs/core";
|
import { Reflector } from "@nestjs/core";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export const MembershipRoles = Reflector.createDecorator<MembershipRole[]>();
|
export const MembershipRoles = Reflector.createDecorator<MembershipRole[]>();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { UserWithProfile } from "@/modules/users/users.repository";
|
|||||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||||
import { Reflector } from "@nestjs/core";
|
import { Reflector } from "@nestjs/core";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationRolesGuard implements CanActivate {
|
export class OrganizationRolesGuard implements CanActivate {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MembershipsRepository {
|
export class MembershipsRepository {
|
||||||
|
|||||||
+2
-4
@@ -1,5 +1,4 @@
|
|||||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||||
import { Locales } from "@/lib/enums/locales";
|
|
||||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
||||||
@@ -7,7 +6,6 @@ import { GetManagedUsersInput } from "@/modules/oauth-clients/controllers/oauth-
|
|||||||
import { CreateManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
import { CreateManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
||||||
import { GetManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-user.output";
|
import { GetManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-user.output";
|
||||||
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
||||||
import { ManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/managed-user.output";
|
|
||||||
import { TOKENS_DOCS } from "@/modules/oauth-clients/controllers/oauth-flow/oauth-flow.controller";
|
import { TOKENS_DOCS } from "@/modules/oauth-clients/controllers/oauth-flow/oauth-flow.controller";
|
||||||
import { KeysResponseDto } from "@/modules/oauth-clients/controllers/oauth-flow/responses/KeysResponse.dto";
|
import { KeysResponseDto } from "@/modules/oauth-clients/controllers/oauth-flow/responses/KeysResponse.dto";
|
||||||
import { OAuthClientGuard } from "@/modules/oauth-clients/guards/oauth-client-guard";
|
import { OAuthClientGuard } from "@/modules/oauth-clients/guards/oauth-client-guard";
|
||||||
@@ -34,10 +32,10 @@ import {
|
|||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { ApiOperation, ApiTags as DocsTags, ApiHeader } from "@nestjs/swagger";
|
import { ApiOperation, ApiTags as DocsTags, ApiHeader } from "@nestjs/swagger";
|
||||||
import { User, MembershipRole } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
import { plainToInstance } from "class-transformer";
|
|
||||||
|
|
||||||
import { SUCCESS_STATUS, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
path: "/v2/oauth-clients/:clientId/users",
|
path: "/v2/oauth-clients/:clientId/users",
|
||||||
|
|||||||
+2
-1
@@ -17,10 +17,11 @@ import { OAuthClientWebhooksService } from "@/modules/webhooks/services/oauth-cl
|
|||||||
import { WebhooksService } from "@/modules/webhooks/services/webhooks.service";
|
import { WebhooksService } from "@/modules/webhooks/services/webhooks.service";
|
||||||
import { Controller, Post, Body, UseGuards, Get, Param, Query, Delete, Patch } from "@nestjs/common";
|
import { Controller, Post, Body, UseGuards, Get, Param, Query, Delete, Patch } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||||
import { Webhook, MembershipRole } from "@prisma/client";
|
import { Webhook } from "@prisma/client";
|
||||||
import { plainToClass } from "class-transformer";
|
import { plainToClass } from "class-transformer";
|
||||||
|
|
||||||
import { SUCCESS_STATUS, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
import { SkipTakePagination } from "@calcom/platform-types";
|
import { SkipTakePagination } from "@calcom/platform-types";
|
||||||
|
|
||||||
import { OAuthClientGuard } from "../../guards/oauth-client-guard";
|
import { OAuthClientGuard } from "../../guards/oauth-client-guard";
|
||||||
|
|||||||
+1
-3
@@ -5,12 +5,10 @@ import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-role
|
|||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
||||||
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
||||||
import { ManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/managed-user.output";
|
|
||||||
import { CreateOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto";
|
import { CreateOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto";
|
||||||
import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto";
|
import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto";
|
||||||
import { GetOAuthClientsResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientsResponse.dto";
|
import { GetOAuthClientsResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientsResponse.dto";
|
||||||
import { OAuthClientGuard } from "@/modules/oauth-clients/guards/oauth-client-guard";
|
import { OAuthClientGuard } from "@/modules/oauth-clients/guards/oauth-client-guard";
|
||||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
|
||||||
import { OAuthClientUsersOutputService } from "@/modules/oauth-clients/services/oauth-clients-users-output.service";
|
import { OAuthClientUsersOutputService } from "@/modules/oauth-clients/services/oauth-clients-users-output.service";
|
||||||
import { OAuthClientsService } from "@/modules/oauth-clients/services/oauth-clients/oauth-clients.service";
|
import { OAuthClientsService } from "@/modules/oauth-clients/services/oauth-clients/oauth-clients.service";
|
||||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||||
@@ -37,9 +35,9 @@ import {
|
|||||||
ApiExcludeEndpoint,
|
ApiExcludeEndpoint,
|
||||||
ApiHeader,
|
ApiHeader,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { User, MembershipRole } from "@prisma/client";
|
|
||||||
|
|
||||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
import { CreateOAuthClientInput, UpdateOAuthClientInput, Pagination } from "@calcom/platform-types";
|
import { CreateOAuthClientInput, UpdateOAuthClientInput, Pagination } from "@calcom/platform-types";
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-us
|
|||||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||||
import { UsersRepository } from "@/modules/users/users.repository";
|
import { UsersRepository } from "@/modules/users/users.repository";
|
||||||
import { BadRequestException, ConflictException, Injectable, Logger } from "@nestjs/common";
|
import { BadRequestException, ConflictException, Injectable, Logger } from "@nestjs/common";
|
||||||
import { User, CreationSource, PlatformOAuthClient } from "@prisma/client";
|
import { User, PlatformOAuthClient } from "@prisma/client";
|
||||||
|
|
||||||
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
|
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
|
||||||
|
import { CreationSource } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OAuthClientUsersService {
|
export class OAuthClientUsersService {
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,5 @@
|
|||||||
import { CreateOrganizationAttributeOptionInput } from "@/modules/organizations/attributes/options/inputs/create-organization-attribute-option.input";
|
import { CreateOrganizationAttributeOptionInput } from "@/modules/organizations/attributes/options/inputs/create-organization-attribute-option.input";
|
||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { AttributeType } from "@prisma/client";
|
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
@@ -12,6 +11,8 @@ import {
|
|||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
|
|
||||||
|
import { AttributeType } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateOrganizationAttributeInput {
|
export class CreateOrganizationAttributeInput {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { AttributeType } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsEnum, IsOptional, IsString } from "class-validator";
|
import { IsBoolean, IsEnum, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { AttributeType } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class UpdateOrganizationAttributeInput {
|
export class UpdateOrganizationAttributeInput {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,7 @@ import { UsersModule } from "@/modules/users/users.module";
|
|||||||
import { INestApplication } from "@nestjs/common";
|
import { INestApplication } from "@nestjs/common";
|
||||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
import { Test } from "@nestjs/testing";
|
import { Test } from "@nestjs/testing";
|
||||||
import { SchedulingType, User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
import * as request from "supertest";
|
import * as request from "supertest";
|
||||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
ConfirmationPolicyEnum,
|
ConfirmationPolicyEnum,
|
||||||
NoticeThresholdUnitEnum,
|
NoticeThresholdUnitEnum,
|
||||||
} from "@calcom/platform-enums";
|
} from "@calcom/platform-enums";
|
||||||
|
import { SchedulingType } from "@calcom/platform-libraries";
|
||||||
import {
|
import {
|
||||||
ApiSuccessResponse,
|
ApiSuccessResponse,
|
||||||
CreateTeamEventTypeInput_2024_06_14,
|
CreateTeamEventTypeInput_2024_06_14,
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
|||||||
import { UsersRepository } from "@/modules/users/users.repository";
|
import { UsersRepository } from "@/modules/users/users.repository";
|
||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { SchedulingType } from "@calcom/platform-libraries";
|
||||||
import {
|
import {
|
||||||
CreateTeamEventTypeInput_2024_06_14,
|
CreateTeamEventTypeInput_2024_06_14,
|
||||||
UpdateTeamEventTypeInput_2024_06_14,
|
UpdateTeamEventTypeInput_2024_06_14,
|
||||||
HostPriority,
|
HostPriority,
|
||||||
} from "@calcom/platform-types";
|
} from "@calcom/platform-types";
|
||||||
import { SchedulingType } from "@calcom/prisma/client";
|
|
||||||
|
|
||||||
export type TransformedCreateTeamEventTypeInput = Awaited<
|
export type TransformedCreateTeamEventTypeInput = Awaited<
|
||||||
ReturnType<InstanceType<typeof InputOrganizationsEventTypesService>["transformInputCreateTeamEventType"]>
|
ReturnType<InstanceType<typeof InputOrganizationsEventTypesService>["transformInputCreateTeamEventType"]>
|
||||||
|
|||||||
@@ -2,9 +2,17 @@ import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types
|
|||||||
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
|
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
|
||||||
import { UsersRepository } from "@/modules/users/users.repository";
|
import { UsersRepository } from "@/modules/users/users.repository";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import type { EventType, User, Schedule, Host, DestinationCalendar, CalVideoSettings } from "@prisma/client";
|
import type {
|
||||||
import { SchedulingType, Team } from "@prisma/client";
|
Team,
|
||||||
|
EventType,
|
||||||
|
User,
|
||||||
|
Schedule,
|
||||||
|
Host,
|
||||||
|
DestinationCalendar,
|
||||||
|
CalVideoSettings,
|
||||||
|
} from "@prisma/client";
|
||||||
|
|
||||||
|
import { SchedulingType } from "@calcom/platform-libraries";
|
||||||
import { HostPriority, TeamEventTypeResponseHost } from "@calcom/platform-types";
|
import { HostPriority, TeamEventTypeResponseHost } from "@calcom/platform-types";
|
||||||
|
|
||||||
type EventTypeRelations = {
|
type EventTypeRelations = {
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateOrgMembershipDto {
|
export class CreateOrgMembershipDto {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class UpdateOrgMembershipDto {
|
export class UpdateOrgMembershipDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
+1
-1
@@ -1,9 +1,9 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { Expose, Type } from "class-transformer";
|
import { Expose, Type } from "class-transformer";
|
||||||
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
import { OrgTeamOutputDto } from "@calcom/platform-types";
|
import { OrgTeamOutputDto } from "@calcom/platform-types";
|
||||||
|
|
||||||
export class OrgMeTeamOutputDto extends OrgTeamOutputDto {
|
export class OrgMeTeamOutputDto extends OrgTeamOutputDto {
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateOrgTeamMembershipDto {
|
export class CreateOrgTeamMembershipDto {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class UpdateOrgTeamMembershipDto {
|
export class UpdateOrgTeamMembershipDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
|
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
|
||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsString, IsOptional, IsBoolean, IsEnum } from "class-validator";
|
import { IsString, IsOptional, IsBoolean, IsEnum } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateOrganizationUserInput extends CreateUserInput {
|
export class CreateOrganizationUserInput extends CreateUserInput {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
+2
-1
@@ -4,10 +4,11 @@ import { UpdateOrganizationUserInput } from "@/modules/organizations/users/index
|
|||||||
import { OrganizationsUsersRepository } from "@/modules/organizations/users/index/organizations-users.repository";
|
import { OrganizationsUsersRepository } from "@/modules/organizations/users/index/organizations-users.repository";
|
||||||
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
|
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
|
||||||
import { Injectable, ConflictException, ForbiddenException } from "@nestjs/common";
|
import { Injectable, ConflictException, ForbiddenException } from "@nestjs/common";
|
||||||
import { Team, CreationSource } from "@prisma/client";
|
import { Team } from "@prisma/client";
|
||||||
import { plainToInstance } from "class-transformer";
|
import { plainToInstance } from "class-transformer";
|
||||||
|
|
||||||
import { createNewUsersConnectToOrgIfExists } from "@calcom/platform-libraries";
|
import { createNewUsersConnectToOrgIfExists } from "@calcom/platform-libraries";
|
||||||
|
import { CreationSource } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationsUsersService {
|
export class OrganizationsUsersService {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum, IsInt } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateTeamMembershipInput {
|
export class CreateTeamMembershipInput {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
import { IsBoolean, IsOptional, IsEnum } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class UpdateTeamMembershipInput {
|
export class UpdateTeamMembershipInput {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { Expose, Transform, Type } from "class-transformer";
|
import { Expose, Transform, Type } from "class-transformer";
|
||||||
import { IsBoolean, IsInt, IsObject, IsOptional, IsString, ValidateNested } from "class-validator";
|
import { IsBoolean, IsInt, IsObject, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
class MembershipUserOutputDto {
|
class MembershipUserOutputDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { Expose, Type } from "class-transformer";
|
import { Expose, Type } from "class-transformer";
|
||||||
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
import { IsEnum, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
import { TeamOutputDto } from "@calcom/platform-types";
|
import { TeamOutputDto } from "@calcom/platform-types";
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { Expose, Type } from "class-transformer";
|
import { Expose, Type } from "class-transformer";
|
||||||
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
import { IsEnum, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
import { TeamOutputDto } from "@calcom/platform-types";
|
import { TeamOutputDto } from "@calcom/platform-types";
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { MembershipRole } from "@prisma/client";
|
|
||||||
import { Expose, Type } from "class-transformer";
|
import { Expose, Type } from "class-transformer";
|
||||||
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
import { IsEnum, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
import { TeamOutputDto } from "@calcom/platform-types";
|
import { TeamOutputDto } from "@calcom/platform-types";
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-us
|
|||||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import type { Profile, User, Team, Prisma } from "@prisma/client";
|
import type { Profile, User, Team, Prisma } from "@prisma/client";
|
||||||
import { CreationSource } from "@prisma/client";
|
|
||||||
|
import { CreationSource } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export type UserWithProfile = User & {
|
export type UserWithProfile = User & {
|
||||||
movedToProfile?: (Profile & { organization: Pick<Team, "isPlatform" | "id" | "slug" | "name"> }) | null;
|
movedToProfile?: (Profile & { organization: Pick<Team, "isPlatform" | "id" | "slug" | "name"> }) | null;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional, PartialType } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional, PartialType } from "@nestjs/swagger";
|
||||||
import { WebhookTriggerEvents } from "@prisma/client";
|
|
||||||
import { IsArray, IsBoolean, IsEnum, IsOptional, IsString } from "class-validator";
|
import { IsArray, IsBoolean, IsEnum, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { WebhookTriggerEvents } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class CreateWebhookInputDto {
|
export class CreateWebhookInputDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { WebhookTriggerEvents } from "@prisma/client";
|
|
||||||
import { Expose, Type } from "class-transformer";
|
import { Expose, Type } from "class-transformer";
|
||||||
import { IsBoolean, IsEnum, IsInt, IsString, ValidateNested, IsArray } from "class-validator";
|
import { IsBoolean, IsEnum, IsInt, IsString, ValidateNested, IsArray } from "class-validator";
|
||||||
|
|
||||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||||
|
import { WebhookTriggerEvents } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class WebhookOutputDto {
|
export class WebhookOutputDto {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ import {
|
|||||||
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||||
BaseWorkflowTriggerDto,
|
BaseWorkflowTriggerDto,
|
||||||
BEFORE_EVENT,
|
BEFORE_EVENT,
|
||||||
|
BOOKING_NO_SHOW_UPDATED,
|
||||||
|
BOOKING_PAID,
|
||||||
|
BOOKING_PAYMENT_INITIATED,
|
||||||
|
BOOKING_REJECTED,
|
||||||
|
BOOKING_REQUESTED,
|
||||||
EVENT_CANCELLED,
|
EVENT_CANCELLED,
|
||||||
NEW_EVENT,
|
NEW_EVENT,
|
||||||
OnAfterCalVideoGuestsNoShowTriggerDto,
|
OnAfterCalVideoGuestsNoShowTriggerDto,
|
||||||
@@ -41,6 +46,11 @@ import {
|
|||||||
OnBeforeEventTriggerDto,
|
OnBeforeEventTriggerDto,
|
||||||
OnCancelTriggerDto,
|
OnCancelTriggerDto,
|
||||||
OnCreationTriggerDto,
|
OnCreationTriggerDto,
|
||||||
|
OnNoShowUpdateTriggerDto,
|
||||||
|
OnPaidTriggerDto,
|
||||||
|
OnPaymentInitiatedTriggerDto,
|
||||||
|
OnRejectedTriggerDto,
|
||||||
|
OnRequestedTriggerDto,
|
||||||
OnRescheduleTriggerDto,
|
OnRescheduleTriggerDto,
|
||||||
RESCHEDULE_EVENT,
|
RESCHEDULE_EVENT,
|
||||||
} from "./workflow-trigger.input";
|
} from "./workflow-trigger.input";
|
||||||
@@ -73,6 +83,11 @@ export type TriggerDtoType =
|
|||||||
| OnRescheduleTriggerDto
|
| OnRescheduleTriggerDto
|
||||||
| OnCancelTriggerDto
|
| OnCancelTriggerDto
|
||||||
| OnAfterCalVideoGuestsNoShowTriggerDto
|
| OnAfterCalVideoGuestsNoShowTriggerDto
|
||||||
|
| OnRejectedTriggerDto
|
||||||
|
| OnRequestedTriggerDto
|
||||||
|
| OnPaymentInitiatedTriggerDto
|
||||||
|
| OnPaidTriggerDto
|
||||||
|
| OnNoShowUpdateTriggerDto
|
||||||
| OnAfterCalVideoHostsNoShowTriggerDto;
|
| OnAfterCalVideoHostsNoShowTriggerDto;
|
||||||
|
|
||||||
@ApiExtraModels(
|
@ApiExtraModels(
|
||||||
@@ -81,6 +96,11 @@ export type TriggerDtoType =
|
|||||||
OnCancelTriggerDto,
|
OnCancelTriggerDto,
|
||||||
OnCreationTriggerDto,
|
OnCreationTriggerDto,
|
||||||
OnRescheduleTriggerDto,
|
OnRescheduleTriggerDto,
|
||||||
|
OnNoShowUpdateTriggerDto,
|
||||||
|
OnRejectedTriggerDto,
|
||||||
|
OnRequestedTriggerDto,
|
||||||
|
OnPaymentInitiatedTriggerDto,
|
||||||
|
OnPaidTriggerDto,
|
||||||
OnAfterCalVideoGuestsNoShowTriggerDto,
|
OnAfterCalVideoGuestsNoShowTriggerDto,
|
||||||
OnAfterCalVideoHostsNoShowTriggerDto,
|
OnAfterCalVideoHostsNoShowTriggerDto,
|
||||||
WorkflowEmailAddressStepDto,
|
WorkflowEmailAddressStepDto,
|
||||||
@@ -112,6 +132,11 @@ export class CreateWorkflowDto {
|
|||||||
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
|
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
|
||||||
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
|
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
|
||||||
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
|
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnRejectedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnRequestedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnPaidTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnPaymentInitiatedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnNoShowUpdateTriggerDto) },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@@ -126,6 +151,11 @@ export class CreateWorkflowDto {
|
|||||||
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
|
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
|
||||||
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
|
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
|
||||||
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
|
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
|
||||||
|
{ value: OnRequestedTriggerDto, name: BOOKING_REQUESTED },
|
||||||
|
{ value: OnRejectedTriggerDto, name: BOOKING_REJECTED },
|
||||||
|
{ value: OnPaymentInitiatedTriggerDto, name: BOOKING_PAYMENT_INITIATED },
|
||||||
|
{ value: OnPaidTriggerDto, name: BOOKING_PAID },
|
||||||
|
{ value: OnNoShowUpdateTriggerDto, name: BOOKING_NO_SHOW_UPDATED },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -135,6 +165,11 @@ export class CreateWorkflowDto {
|
|||||||
| OnCreationTriggerDto
|
| OnCreationTriggerDto
|
||||||
| OnRescheduleTriggerDto
|
| OnRescheduleTriggerDto
|
||||||
| OnCancelTriggerDto
|
| OnCancelTriggerDto
|
||||||
|
| OnRejectedTriggerDto
|
||||||
|
| OnRequestedTriggerDto
|
||||||
|
| OnPaidTriggerDto
|
||||||
|
| OnPaymentInitiatedTriggerDto
|
||||||
|
| OnNoShowUpdateTriggerDto
|
||||||
| OnAfterCalVideoGuestsNoShowTriggerDto
|
| OnAfterCalVideoGuestsNoShowTriggerDto
|
||||||
| OnAfterCalVideoHostsNoShowTriggerDto;
|
| OnAfterCalVideoHostsNoShowTriggerDto;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ import {
|
|||||||
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||||
OnAfterCalVideoHostsNoShowTriggerDto,
|
OnAfterCalVideoHostsNoShowTriggerDto,
|
||||||
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||||
|
OnNoShowUpdateTriggerDto,
|
||||||
|
OnRejectedTriggerDto,
|
||||||
|
OnRequestedTriggerDto,
|
||||||
|
OnPaymentInitiatedTriggerDto,
|
||||||
|
OnPaidTriggerDto,
|
||||||
|
BOOKING_REQUESTED,
|
||||||
|
BOOKING_REJECTED,
|
||||||
|
BOOKING_PAYMENT_INITIATED,
|
||||||
|
BOOKING_PAID,
|
||||||
|
BOOKING_NO_SHOW_UPDATED,
|
||||||
} from "./workflow-trigger.input";
|
} from "./workflow-trigger.input";
|
||||||
|
|
||||||
export type UpdateWorkflowStepDto =
|
export type UpdateWorkflowStepDto =
|
||||||
@@ -119,6 +129,11 @@ export class UpdateWhatsAppAttendeePhoneWorkflowStepDto extends WorkflowPhoneWha
|
|||||||
OnCancelTriggerDto,
|
OnCancelTriggerDto,
|
||||||
OnCreationTriggerDto,
|
OnCreationTriggerDto,
|
||||||
OnRescheduleTriggerDto,
|
OnRescheduleTriggerDto,
|
||||||
|
OnNoShowUpdateTriggerDto,
|
||||||
|
OnRejectedTriggerDto,
|
||||||
|
OnRequestedTriggerDto,
|
||||||
|
OnPaymentInitiatedTriggerDto,
|
||||||
|
OnPaidTriggerDto,
|
||||||
OnAfterCalVideoGuestsNoShowTriggerDto,
|
OnAfterCalVideoGuestsNoShowTriggerDto,
|
||||||
OnAfterCalVideoHostsNoShowTriggerDto,
|
OnAfterCalVideoHostsNoShowTriggerDto,
|
||||||
UpdateEmailAddressWorkflowStepDto,
|
UpdateEmailAddressWorkflowStepDto,
|
||||||
@@ -155,6 +170,11 @@ export class UpdateWorkflowDto {
|
|||||||
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
|
{ $ref: getSchemaPath(OnRescheduleTriggerDto) },
|
||||||
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
|
{ $ref: getSchemaPath(OnAfterCalVideoGuestsNoShowTriggerDto) },
|
||||||
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
|
{ $ref: getSchemaPath(OnAfterCalVideoHostsNoShowTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnRejectedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnRequestedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnPaidTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnPaymentInitiatedTriggerDto) },
|
||||||
|
{ $ref: getSchemaPath(OnNoShowUpdateTriggerDto) },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -170,6 +190,11 @@ export class UpdateWorkflowDto {
|
|||||||
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
|
{ value: OnRescheduleTriggerDto, name: RESCHEDULE_EVENT },
|
||||||
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
|
{ value: OnAfterCalVideoGuestsNoShowTriggerDto, name: AFTER_GUESTS_CAL_VIDEO_NO_SHOW },
|
||||||
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
|
{ value: OnAfterCalVideoHostsNoShowTriggerDto, name: AFTER_HOSTS_CAL_VIDEO_NO_SHOW },
|
||||||
|
{ value: OnRequestedTriggerDto, name: BOOKING_REQUESTED },
|
||||||
|
{ value: OnRejectedTriggerDto, name: BOOKING_REJECTED },
|
||||||
|
{ value: OnPaymentInitiatedTriggerDto, name: BOOKING_PAYMENT_INITIATED },
|
||||||
|
{ value: OnPaidTriggerDto, name: BOOKING_PAID },
|
||||||
|
{ value: OnNoShowUpdateTriggerDto, name: BOOKING_NO_SHOW_UPDATED },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -179,6 +204,11 @@ export class UpdateWorkflowDto {
|
|||||||
| OnCreationTriggerDto
|
| OnCreationTriggerDto
|
||||||
| OnRescheduleTriggerDto
|
| OnRescheduleTriggerDto
|
||||||
| OnCancelTriggerDto
|
| OnCancelTriggerDto
|
||||||
|
| OnRejectedTriggerDto
|
||||||
|
| OnRequestedTriggerDto
|
||||||
|
| OnPaidTriggerDto
|
||||||
|
| OnPaymentInitiatedTriggerDto
|
||||||
|
| OnNoShowUpdateTriggerDto
|
||||||
| OnAfterCalVideoGuestsNoShowTriggerDto
|
| OnAfterCalVideoGuestsNoShowTriggerDto
|
||||||
| OnAfterCalVideoHostsNoShowTriggerDto;
|
| OnAfterCalVideoHostsNoShowTriggerDto;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { WorkflowActions, WorkflowTemplates } from "@prisma/client";
|
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { IsNumber, IsBoolean, IsString, ValidateNested, IsIn } from "class-validator";
|
import { IsNumber, IsBoolean, IsString, ValidateNested, IsIn } from "class-validator";
|
||||||
|
|
||||||
|
import { WorkflowActions, WorkflowTemplates } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export const EMAIL_HOST = "email_host";
|
export const EMAIL_HOST = "email_host";
|
||||||
export const EMAIL_ATTENDEE = "email_attendee";
|
export const EMAIL_ATTENDEE = "email_attendee";
|
||||||
export const EMAIL_ADDRESS = "email_address";
|
export const EMAIL_ADDRESS = "email_address";
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { TimeUnit, WorkflowTriggerEvents } from "@prisma/client";
|
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator";
|
import { IsIn, IsNumber, IsString, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
import { TimeUnit, WorkflowTriggerEvents } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export const BEFORE_EVENT = "beforeEvent";
|
export const BEFORE_EVENT = "beforeEvent";
|
||||||
export const EVENT_CANCELLED = "eventCancelled";
|
export const EVENT_CANCELLED = "eventCancelled";
|
||||||
export const NEW_EVENT = "newEvent";
|
export const NEW_EVENT = "newEvent";
|
||||||
@@ -10,6 +11,11 @@ export const AFTER_EVENT = "afterEvent";
|
|||||||
export const RESCHEDULE_EVENT = "rescheduleEvent";
|
export const RESCHEDULE_EVENT = "rescheduleEvent";
|
||||||
export const AFTER_HOSTS_CAL_VIDEO_NO_SHOW = "afterHostsCalVideoNoShow";
|
export const AFTER_HOSTS_CAL_VIDEO_NO_SHOW = "afterHostsCalVideoNoShow";
|
||||||
export const AFTER_GUESTS_CAL_VIDEO_NO_SHOW = "afterGuestsCalVideoNoShow";
|
export const AFTER_GUESTS_CAL_VIDEO_NO_SHOW = "afterGuestsCalVideoNoShow";
|
||||||
|
export const BOOKING_REJECTED = "bookingRejected";
|
||||||
|
export const BOOKING_REQUESTED = "bookingRequested";
|
||||||
|
export const BOOKING_PAYMENT_INITIATED = "bookingPaymentInitiated";
|
||||||
|
export const BOOKING_PAID = "bookingPaid";
|
||||||
|
export const BOOKING_NO_SHOW_UPDATED = "bookingNoShowUpdated";
|
||||||
export const WORKFLOW_TRIGGER_TYPES = [
|
export const WORKFLOW_TRIGGER_TYPES = [
|
||||||
BEFORE_EVENT,
|
BEFORE_EVENT,
|
||||||
EVENT_CANCELLED,
|
EVENT_CANCELLED,
|
||||||
@@ -18,6 +24,11 @@ export const WORKFLOW_TRIGGER_TYPES = [
|
|||||||
RESCHEDULE_EVENT,
|
RESCHEDULE_EVENT,
|
||||||
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||||
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||||
|
BOOKING_REJECTED,
|
||||||
|
BOOKING_REQUESTED,
|
||||||
|
BOOKING_PAYMENT_INITIATED,
|
||||||
|
BOOKING_PAID,
|
||||||
|
BOOKING_NO_SHOW_UPDATED,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const WORKFLOW_TRIGGER_TO_ENUM = {
|
export const WORKFLOW_TRIGGER_TO_ENUM = {
|
||||||
@@ -28,6 +39,11 @@ export const WORKFLOW_TRIGGER_TO_ENUM = {
|
|||||||
[RESCHEDULE_EVENT]: WorkflowTriggerEvents.RESCHEDULE_EVENT,
|
[RESCHEDULE_EVENT]: WorkflowTriggerEvents.RESCHEDULE_EVENT,
|
||||||
[AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
[AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||||
[AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
[AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||||
|
[BOOKING_REJECTED]: WorkflowTriggerEvents.BOOKING_REJECTED,
|
||||||
|
[BOOKING_REQUESTED]: WorkflowTriggerEvents.BOOKING_REQUESTED,
|
||||||
|
[BOOKING_PAYMENT_INITIATED]: WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED,
|
||||||
|
[BOOKING_NO_SHOW_UPDATED]: WorkflowTriggerEvents.BOOKING_NO_SHOW_UPDATED,
|
||||||
|
[BOOKING_PAID]: WorkflowTriggerEvents.BOOKING_PAID,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ENUM_TO_WORKFLOW_TRIGGER = {
|
export const ENUM_TO_WORKFLOW_TRIGGER = {
|
||||||
@@ -38,6 +54,11 @@ export const ENUM_TO_WORKFLOW_TRIGGER = {
|
|||||||
[WorkflowTriggerEvents.RESCHEDULE_EVENT]: RESCHEDULE_EVENT,
|
[WorkflowTriggerEvents.RESCHEDULE_EVENT]: RESCHEDULE_EVENT,
|
||||||
[WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
[WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: AFTER_HOSTS_CAL_VIDEO_NO_SHOW,
|
||||||
[WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
[WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: AFTER_GUESTS_CAL_VIDEO_NO_SHOW,
|
||||||
|
[WorkflowTriggerEvents.BOOKING_REJECTED]: BOOKING_REJECTED,
|
||||||
|
[WorkflowTriggerEvents.BOOKING_REQUESTED]: BOOKING_REQUESTED,
|
||||||
|
[WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED]: BOOKING_PAYMENT_INITIATED,
|
||||||
|
[WorkflowTriggerEvents.BOOKING_PAID]: BOOKING_PAID,
|
||||||
|
[WorkflowTriggerEvents.BOOKING_NO_SHOW_UPDATED]: BOOKING_NO_SHOW_UPDATED,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const HOUR = "hour";
|
export const HOUR = "hour";
|
||||||
@@ -108,6 +129,41 @@ export class OnCancelTriggerDto {
|
|||||||
type: typeof EVENT_CANCELLED = EVENT_CANCELLED;
|
type: typeof EVENT_CANCELLED = EVENT_CANCELLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OnRejectedTriggerDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Trigger type for the workflow",
|
||||||
|
})
|
||||||
|
type: typeof BOOKING_REJECTED = BOOKING_REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OnRequestedTriggerDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Trigger type for the workflow",
|
||||||
|
})
|
||||||
|
type: typeof BOOKING_REQUESTED = BOOKING_REQUESTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OnPaymentInitiatedTriggerDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Trigger type for the workflow",
|
||||||
|
})
|
||||||
|
type: typeof BOOKING_PAYMENT_INITIATED = BOOKING_PAYMENT_INITIATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OnPaidTriggerDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Trigger type for the workflow",
|
||||||
|
})
|
||||||
|
type: typeof BOOKING_PAID = BOOKING_PAID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OnNoShowUpdateTriggerDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Trigger type for the workflow",
|
||||||
|
})
|
||||||
|
type: typeof BOOKING_NO_SHOW_UPDATED = BOOKING_NO_SHOW_UPDATED;
|
||||||
|
}
|
||||||
|
|
||||||
export class TriggerOffsetDTO {
|
export class TriggerOffsetDTO {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: "Offset before/after the trigger time; required for BEFORE_EVENT and AFTER_EVENT only",
|
description: "Offset before/after the trigger time; required for BEFORE_EVENT and AFTER_EVENT only",
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
|||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { TimeUnit, Workflow, WorkflowStep, WorkflowTriggerEvents } from "@prisma/client";
|
|
||||||
|
|
||||||
|
import { TimeUnit, WorkflowTriggerEvents } from "@calcom/platform-libraries";
|
||||||
import { TUpdateInputSchema } from "@calcom/platform-libraries/workflows";
|
import { TUpdateInputSchema } from "@calcom/platform-libraries/workflows";
|
||||||
import { updateWorkflow } from "@calcom/platform-libraries/workflows";
|
import { updateWorkflow } from "@calcom/platform-libraries/workflows";
|
||||||
import { PrismaClient } from "@calcom/prisma";
|
import { PrismaClient } from "@calcom/prisma";
|
||||||
|
import type { Workflow, WorkflowStep } from "@calcom/prisma/client";
|
||||||
|
|
||||||
export type WorkflowType = Workflow & { activeOn: { eventTypeId: number }[]; steps: WorkflowStep[] };
|
export type WorkflowType = Workflow & { activeOn: { eventTypeId: number }[]; steps: WorkflowStep[] };
|
||||||
|
|
||||||
|
|||||||
@@ -9132,6 +9132,8 @@
|
|||||||
{
|
{
|
||||||
"name": "credentialId",
|
"name": "credentialId",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"description": "The ID of the user's calendar credential used to access the calendar. Retrieve this from your connected calendar credentials via the `/v2/credentials` endpoint.",
|
||||||
|
"example": 135,
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@@ -9140,6 +9142,8 @@
|
|||||||
{
|
{
|
||||||
"name": "externalId",
|
"name": "externalId",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"description": "The external calendar identifier from the calendar provider (e.g., email address for Google Calendar). This is returned when listing connected calendars.",
|
||||||
|
"example": "user@gmail.com",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -22554,7 +22558,12 @@
|
|||||||
"afterEvent",
|
"afterEvent",
|
||||||
"rescheduleEvent",
|
"rescheduleEvent",
|
||||||
"afterHostsCalVideoNoShow",
|
"afterHostsCalVideoNoShow",
|
||||||
"afterGuestsCalVideoNoShow"
|
"afterGuestsCalVideoNoShow",
|
||||||
|
"bookingRejected",
|
||||||
|
"bookingRequested",
|
||||||
|
"bookingPaymentInitiated",
|
||||||
|
"bookingPaid",
|
||||||
|
"bookingNoShowUpdated"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"offset": {
|
"offset": {
|
||||||
@@ -22923,6 +22932,71 @@
|
|||||||
"type"
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"OnNoShowUpdateTriggerDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "bookingNoShowUpdated",
|
||||||
|
"description": "Trigger type for the workflow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OnRejectedTriggerDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "bookingRejected",
|
||||||
|
"description": "Trigger type for the workflow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OnRequestedTriggerDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "bookingRequested",
|
||||||
|
"description": "Trigger type for the workflow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OnPaymentInitiatedTriggerDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "bookingPaymentInitiated",
|
||||||
|
"description": "Trigger type for the workflow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OnPaidTriggerDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "bookingPaid",
|
||||||
|
"description": "Trigger type for the workflow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
"OnAfterCalVideoGuestsNoShowTriggerDto": {
|
"OnAfterCalVideoGuestsNoShowTriggerDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -23642,6 +23716,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OnAfterCalVideoHostsNoShowTriggerDto"
|
"$ref": "#/components/schemas/OnAfterCalVideoHostsNoShowTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnRejectedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnRequestedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnPaidTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnPaymentInitiatedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnNoShowUpdateTriggerDto"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -24298,6 +24387,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OnAfterCalVideoHostsNoShowTriggerDto"
|
"$ref": "#/components/schemas/OnAfterCalVideoHostsNoShowTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnRejectedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnRequestedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnPaidTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnPaymentInitiatedTriggerDto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OnNoShowUpdateTriggerDto"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||||
import { TestingModule } from "@nestjs/testing";
|
import { TestingModule } from "@nestjs/testing";
|
||||||
import { Membership, MembershipRole, Prisma, Team, User } from "@prisma/client";
|
import { Membership, Prisma, Team, User } from "@prisma/client";
|
||||||
|
|
||||||
|
import { MembershipRole } from "@calcom/platform-libraries";
|
||||||
|
|
||||||
export class MembershipRepositoryFixture {
|
export class MembershipRepositoryFixture {
|
||||||
private prismaReadClient: PrismaReadService["prisma"];
|
private prismaReadClient: PrismaReadService["prisma"];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { _generateMetadata, getTranslate } from "app/_utils";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProvider } from "@calcom/prisma/client";
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
import { Button } from "@calcom/ui/components/button";
|
import { Button } from "@calcom/ui/components/button";
|
||||||
import { Icon } from "@calcom/ui/components/icon";
|
import { Icon } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import type { PageProps as _PageProps } from "app/_types";
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { StripeService } from "@calcom/lib/server/service/stripe";
|
|
||||||
|
|
||||||
import VerifyPage from "~/auth/verify-view";
|
import VerifyPage from "~/auth/verify-view";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
@@ -14,18 +12,10 @@ const querySchema = z.object({
|
|||||||
|
|
||||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||||
const p = { ...(await params), ...(await searchParams) };
|
const p = { ...(await params), ...(await searchParams) };
|
||||||
const { sessionId, stripeCustomerId } = querySchema.parse(p);
|
const { sessionId } = querySchema.parse(p);
|
||||||
|
|
||||||
const data = await StripeService.getCheckoutSession({
|
|
||||||
stripeCustomerId,
|
|
||||||
checkoutSessionId: sessionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { hasPaymentFailed } = data;
|
|
||||||
|
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() =>
|
() => (sessionId ? "Payment Page" : `Verify your email`),
|
||||||
hasPaymentFailed ? "Your payment failed" : sessionId ? "Payment successful!" : `Verify your email`,
|
|
||||||
() => "",
|
() => "",
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
+1
@@ -117,6 +117,7 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
|
|||||||
{
|
{
|
||||||
name: "admin_api",
|
name: "admin_api",
|
||||||
href: "https://cal.com/docs/enterprise-features/api/api-reference/bookings#admin-access",
|
href: "https://cal.com/docs/enterprise-features/api/api-reference/bookings#admin-access",
|
||||||
|
isExternalLink: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const resolveEndpoint = (links: any) => {
|
|||||||
// to the correct API endpoints.
|
// to the correct API endpoints.
|
||||||
// - viewer.me - 2 segment paths like this are for logged in requests
|
// - viewer.me - 2 segment paths like this are for logged in requests
|
||||||
// - viewer.public.i18n - 3 segments paths can be public or authed
|
// - 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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (ctx: any) => {
|
return (ctx: any) => {
|
||||||
const parts = ctx.op.path.split(".");
|
const parts = ctx.op.path.split(".");
|
||||||
@@ -23,7 +24,11 @@ const resolveEndpoint = (links: any) => {
|
|||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
endpoint = parts[0] as keyof typeof links;
|
endpoint = parts[0] as keyof typeof links;
|
||||||
path = parts[1];
|
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;
|
endpoint = parts[1] as keyof typeof links;
|
||||||
path = parts.splice(2, parts.length - 2).join(".");
|
path = parts.splice(2, parts.length - 2).join(".");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
|||||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||||
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { IdentityProvider } from "@calcom/prisma/client";
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const token = randomBytes(32).toString("hex");
|
const token = randomBytes(32).toString("hex");
|
||||||
|
|
||||||
const res = NextResponse.json({ csrfToken: token });
|
const res = NextResponse.json({ csrfToken: token });
|
||||||
|
|
||||||
|
// We need this cookie to be accessible from embeds where the booking flow is displayed within an iframe on a different origin.
|
||||||
|
// For third‑party iframe contexts (embeds on other sites), browsers require SameSite=None and Secure to make the cookie available.
|
||||||
|
// For local development on http://localhost we fall back to SameSite=Lax to avoid requiring https during development.
|
||||||
|
const useSecureCookies = WEBAPP_URL.startsWith("https://");
|
||||||
res.cookies.set("calcom.csrf_token", token, {
|
res.cookies.set("calcom.csrf_token", token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: useSecureCookies,
|
||||||
sameSite: "lax",
|
sameSite: useSecureCookies ? "none" : "lax",
|
||||||
path: "/",
|
path: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { useRouter } from "next/navigation";
|
|||||||
import type { IframeHTMLAttributes } from "react";
|
import type { IframeHTMLAttributes } from "react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { AppDependencyComponent } from "@calcom/app-store/AppDependencyComponent";
|
||||||
|
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
|
||||||
import { isRedirectApp } from "@calcom/app-store/_utils/redirectApps";
|
import { isRedirectApp } from "@calcom/app-store/_utils/redirectApps";
|
||||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||||
import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
|
|
||||||
import { doesAppSupportTeamInstall, isConferencing } from "@calcom/app-store/utils";
|
import { doesAppSupportTeamInstall, isConferencing } from "@calcom/app-store/utils";
|
||||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||||
import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, Suspense } from "react";
|
import { useEffect, Suspense } from "react";
|
||||||
|
|
||||||
import { InstallAppButton } from "@calcom/app-store/components";
|
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
|
||||||
import { DestinationCalendarSettingsWebWrapper } from "@calcom/atoms/destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper";
|
import { DestinationCalendarSettingsWebWrapper } from "@calcom/atoms/destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper";
|
||||||
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/atoms/selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper";
|
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/atoms/selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper";
|
||||||
import AppListCard from "@calcom/features/apps/components/AppListCard";
|
import AppListCard from "@calcom/features/apps/components/AppListCard";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Locations from "@calcom/features/eventtypes/components/locations/Location
|
|||||||
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
import type { LocationFormValues } from "@calcom/features/eventtypes/lib/types";
|
||||||
import type { SingleValueLocationOption } from "@calcom/features/form/components/LocationSelect";
|
import type { SingleValueLocationOption } from "@calcom/features/form/components/LocationSelect";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { SchedulingType } from "@calcom/prisma/client";
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Label } from "@calcom/ui/components/form";
|
import { Label } from "@calcom/ui/components/form";
|
||||||
import { Skeleton, SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
|
import { Skeleton, SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { useRefreshData } from "@calcom/lib/hooks/useRefreshData";
|
import { useRefreshData } from "@calcom/lib/hooks/useRefreshData";
|
||||||
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
|
||||||
|
import { shouldChargeNoShowCancellationFee } from "@calcom/lib/payment/shouldChargeNoShowCancellationFee";
|
||||||
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
|
||||||
import type { RecurringEvent } from "@calcom/types/Calendar";
|
import type { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
import { Button } from "@calcom/ui/components/button";
|
import { Button } from "@calcom/ui/components/button";
|
||||||
import { Label, Select, TextArea } from "@calcom/ui/components/form";
|
import { Label, Select, TextArea, CheckboxField } from "@calcom/ui/components/form";
|
||||||
import { Icon } from "@calcom/ui/components/icon";
|
import { Icon } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
interface InternalNotePresetsSelectProps {
|
interface InternalNotePresetsSelectProps {
|
||||||
@@ -76,6 +77,12 @@ type Props = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
uid?: string;
|
uid?: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
startTime: Date;
|
||||||
|
payment?: {
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
appId: string | null;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
profile: {
|
profile: {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
@@ -100,6 +107,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
isHost: boolean;
|
isHost: boolean;
|
||||||
internalNotePresets: { id: number; name: string; cancellationReason: string | null }[];
|
internalNotePresets: { id: number; name: string; cancellationReason: string | null }[];
|
||||||
|
eventTypeMetadata?: Record<string, unknown> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CancelBooking(props: Props) {
|
export default function CancelBooking(props: Props) {
|
||||||
@@ -112,13 +120,48 @@ export default function CancelBooking(props: Props) {
|
|||||||
seatReferenceUid,
|
seatReferenceUid,
|
||||||
bookingCancelledEventProps,
|
bookingCancelledEventProps,
|
||||||
currentUserEmail,
|
currentUserEmail,
|
||||||
teamId,
|
eventTypeMetadata,
|
||||||
} = props;
|
} = props;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
||||||
const [internalNote, setInternalNote] = useState<{ id: number; name: string } | null>(null);
|
const [internalNote, setInternalNote] = useState<{ id: number; name: string } | null>(null);
|
||||||
|
const [acknowledgeCancellationNoShowFee, setAcknowledgeCancellationNoShowFee] = useState(false);
|
||||||
|
|
||||||
|
const getAppMetadata = (appId: string): Record<string, unknown> | null => {
|
||||||
|
if (!eventTypeMetadata?.apps || !appId) return null;
|
||||||
|
const apps = eventTypeMetadata.apps as Record<string, unknown>;
|
||||||
|
return (apps[appId] as Record<string, unknown>) || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeValue = booking?.payment?.appId
|
||||||
|
? (getAppMetadata(booking.payment.appId) as Record<string, unknown> | null)?.autoChargeNoShowFeeTimeValue
|
||||||
|
: null;
|
||||||
|
const timeUnit = booking?.payment?.appId
|
||||||
|
? (getAppMetadata(booking.payment.appId) as Record<string, unknown> | null)?.autoChargeNoShowFeeTimeUnit
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const autoChargeNoShowFee = () => {
|
||||||
|
if (props.isHost) return false; // Hosts/organizers are exempt
|
||||||
|
|
||||||
|
if (!booking?.startTime) return false;
|
||||||
|
|
||||||
|
if (!booking?.payment) return false;
|
||||||
|
|
||||||
|
return shouldChargeNoShowCancellationFee({
|
||||||
|
eventTypeMetadata: eventTypeMetadata || null,
|
||||||
|
booking,
|
||||||
|
payment: booking.payment,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancellationNoShowFeeWarning = autoChargeNoShowFee();
|
||||||
|
|
||||||
|
const hostMissingCancellationReason =
|
||||||
|
props.isHost &&
|
||||||
|
(!cancellationReason?.trim() || (props.internalNotePresets.length > 0 && !internalNote?.id));
|
||||||
|
const cancellationNoShowFeeNotAcknowledged =
|
||||||
|
!props.isHost && cancellationNoShowFeeWarning && !acknowledgeCancellationNoShowFee;
|
||||||
const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => {
|
const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => {
|
||||||
if (node !== null) {
|
if (node !== null) {
|
||||||
// eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- CancelBooking is not usually used in embed mode
|
// eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- CancelBooking is not usually used in embed mode
|
||||||
@@ -187,6 +230,23 @@ export default function CancelBooking(props: Props) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{cancellationNoShowFeeWarning && booking?.payment && (
|
||||||
|
<div>
|
||||||
|
<div className="bg-attention mb-5 rounded-md p-3">
|
||||||
|
<CheckboxField
|
||||||
|
description={t("cancel_booking_acknowledge_no_show_fee", {
|
||||||
|
timeValue,
|
||||||
|
timeUnit,
|
||||||
|
amount: booking.payment.amount / 100,
|
||||||
|
formatParams: { amount: { currency: booking.payment.currency } },
|
||||||
|
})}
|
||||||
|
onChange={(e) => setAcknowledgeCancellationNoShowFee(e.target.checked)}
|
||||||
|
descriptionClassName="text-info font-semibold"
|
||||||
|
/>
|
||||||
|
<p className="text-subtle ml-9 mt-2 text-sm">{t("contact_organizer")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
||||||
<div className="ml-auto flex w-full space-x-4 ">
|
<div className="ml-auto flex w-full space-x-4 ">
|
||||||
<Button
|
<Button
|
||||||
@@ -197,10 +257,7 @@ export default function CancelBooking(props: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
data-testid="confirm_cancel"
|
data-testid="confirm_cancel"
|
||||||
disabled={
|
disabled={hostMissingCancellationReason || cancellationNoShowFeeNotAcknowledged}
|
||||||
props.isHost &&
|
|
||||||
(!cancellationReason?.trim() || (props.internalNotePresets.length > 0 && !internalNote?.id))
|
|
||||||
}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { describe, expect, it, vi, beforeAll } from "vitest";
|
||||||
|
|
||||||
|
import * as shouldChargeModule from "@calcom/lib/payment/shouldChargeNoShowCancellationFee";
|
||||||
|
|
||||||
|
import CancelBooking from "../CancelBooking";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
Element.prototype.scrollIntoView = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("@calcom/trpc", () => ({
|
||||||
|
trpc: {
|
||||||
|
viewer: {
|
||||||
|
bookings: {
|
||||||
|
requestReschedule: {
|
||||||
|
useMutation: () => ({
|
||||||
|
mutate: vi.fn(),
|
||||||
|
isLoading: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next-i18next", () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string, options?: Record<string, unknown>) => {
|
||||||
|
if (key === "cancellation_fee_warning_cancel") {
|
||||||
|
const opts = options as {
|
||||||
|
time?: string;
|
||||||
|
unit?: string;
|
||||||
|
amount?: number;
|
||||||
|
formatParams?: { amount?: { currency?: string } };
|
||||||
|
};
|
||||||
|
return `Cancelling within ${opts?.time} ${opts?.unit} will result in a ${opts?.amount} ${opts?.formatParams?.amount?.currency} cancellation fee being charged to your card.`;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/hooks/useLocale", () => ({
|
||||||
|
useLocale: () => ({
|
||||||
|
t: (key: string, options?: Record<string, unknown>) => {
|
||||||
|
if (key === "cancel_booking_acknowledge_no_show_fee") {
|
||||||
|
const opts = options as {
|
||||||
|
timeValue?: number;
|
||||||
|
timeUnit?: string;
|
||||||
|
amount?: number;
|
||||||
|
formatParams?: { amount?: { currency?: string } };
|
||||||
|
};
|
||||||
|
return `I acknowledge that cancelling within ${opts?.timeValue} ${opts?.timeUnit} will result in a ${opts?.amount} ${opts?.formatParams?.amount?.currency} cancellation fee being charged to my card.`;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/hooks/useTelemetry", () => ({
|
||||||
|
useTelemetry: () => ({
|
||||||
|
event: vi.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/hooks/useRefreshData", () => ({
|
||||||
|
useRefreshData: () => vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("next/router", () => ({
|
||||||
|
useRouter: () => ({
|
||||||
|
push: vi.fn(),
|
||||||
|
query: {},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/payment/shouldChargeNoShowCancellationFee", () => ({
|
||||||
|
shouldChargeNoShowCancellationFee: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockBookingWithCancellationFee = {
|
||||||
|
uid: "test-booking-uid",
|
||||||
|
title: "Test Meeting",
|
||||||
|
id: 123,
|
||||||
|
startTime: new Date(Date.now() + 30 * 60 * 1000),
|
||||||
|
payment: {
|
||||||
|
amount: 1000,
|
||||||
|
currency: "usd",
|
||||||
|
appId: "stripe",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockEventTypeMetadataWithFee = {
|
||||||
|
apps: {
|
||||||
|
stripe: {
|
||||||
|
autoChargeNoShowFeeIfCancelled: true,
|
||||||
|
autoChargeNoShowFeeTimeValue: 1,
|
||||||
|
autoChargeNoShowFeeTimeUnit: "hours" as const,
|
||||||
|
paymentOption: "HOLD" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockEventTypeMetadataWithoutFee = {
|
||||||
|
apps: {
|
||||||
|
stripe: {
|
||||||
|
autoChargeNoShowFeeIfCancelled: true,
|
||||||
|
autoChargeNoShowFeeTimeValue: 1,
|
||||||
|
autoChargeNoShowFeeTimeUnit: "hours" as const,
|
||||||
|
paymentOption: "HOLD" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockProps = {
|
||||||
|
recurringEvent: null,
|
||||||
|
setIsCancellationMode: vi.fn(),
|
||||||
|
theme: "light",
|
||||||
|
allRemainingBookings: false,
|
||||||
|
seatReferenceUid: undefined,
|
||||||
|
currentUserEmail: "test@example.com",
|
||||||
|
bookingCancelledEventProps: {
|
||||||
|
booking: {},
|
||||||
|
organizer: {
|
||||||
|
name: "Test Organizer",
|
||||||
|
email: "organizer@example.com",
|
||||||
|
timeZone: "UTC",
|
||||||
|
},
|
||||||
|
eventType: {},
|
||||||
|
},
|
||||||
|
internalNotePresets: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBookingWithoutCancellationFee = {
|
||||||
|
uid: "test-booking-uid-2",
|
||||||
|
title: "Test Meeting 2",
|
||||||
|
id: 124,
|
||||||
|
startTime: new Date(Date.now() + 2 * 60 * 60 * 1000),
|
||||||
|
payment: {
|
||||||
|
amount: 1000,
|
||||||
|
currency: "usd",
|
||||||
|
appId: "stripe",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CancelBooking Cancellation Fee Warning", () => {
|
||||||
|
it("should show cancellation fee warning when booking is within time threshold", () => {
|
||||||
|
vi.mocked(shouldChargeModule.shouldChargeNoShowCancellationFee).mockReturnValue(true);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CancelBooking
|
||||||
|
booking={mockBookingWithCancellationFee}
|
||||||
|
profile={{ name: "Test User", slug: "test-user" }}
|
||||||
|
team={null}
|
||||||
|
isHost={false}
|
||||||
|
eventTypeMetadata={mockEventTypeMetadataWithFee}
|
||||||
|
{...mockProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(/I acknowledge that cancelling within 1 hours will result in a/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show cancellation fee warning when booking is outside time threshold", () => {
|
||||||
|
vi.mocked(shouldChargeModule.shouldChargeNoShowCancellationFee).mockReturnValue(false);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CancelBooking
|
||||||
|
booking={mockBookingWithoutCancellationFee}
|
||||||
|
profile={{ name: "Test User", slug: "test-user" }}
|
||||||
|
team={null}
|
||||||
|
isHost={false}
|
||||||
|
eventTypeMetadata={mockEventTypeMetadataWithoutFee}
|
||||||
|
{...mockProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText(/I acknowledge that cancelling within/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show cancellation fee warning when user is host", () => {
|
||||||
|
vi.mocked(shouldChargeModule.shouldChargeNoShowCancellationFee).mockReturnValue(false);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CancelBooking
|
||||||
|
booking={mockBookingWithCancellationFee}
|
||||||
|
profile={{ name: "Test User", slug: "test-user" }}
|
||||||
|
team={null}
|
||||||
|
isHost={true}
|
||||||
|
eventTypeMetadata={mockEventTypeMetadataWithFee}
|
||||||
|
{...mockProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText(/I acknowledge that cancelling within/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show cancellation fee warning when cancellation fee is disabled", () => {
|
||||||
|
const eventTypeMetadataWithDisabledFee = {
|
||||||
|
apps: {
|
||||||
|
stripe: {
|
||||||
|
autoChargeNoShowFeeIfCancelled: false,
|
||||||
|
autoChargeNoShowFeeTimeValue: 1,
|
||||||
|
autoChargeNoShowFeeTimeUnit: "hours" as const,
|
||||||
|
paymentOption: "HOLD" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(shouldChargeModule.shouldChargeNoShowCancellationFee).mockReturnValue(false);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CancelBooking
|
||||||
|
booking={mockBookingWithCancellationFee}
|
||||||
|
profile={{ name: "Test User", slug: "test-user" }}
|
||||||
|
team={null}
|
||||||
|
isHost={false}
|
||||||
|
eventTypeMetadata={eventTypeMetadataWithDisabledFee}
|
||||||
|
{...mockProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText(/I acknowledge that cancelling within/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show cancellation fee warning when payment option is not HOLD", () => {
|
||||||
|
const eventTypeMetadataWithOnBookingPayment = {
|
||||||
|
apps: {
|
||||||
|
stripe: {
|
||||||
|
autoChargeNoShowFeeIfCancelled: true,
|
||||||
|
autoChargeNoShowFeeTimeValue: 1,
|
||||||
|
autoChargeNoShowFeeTimeUnit: "hours" as const,
|
||||||
|
paymentOption: "ON_BOOKING" as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(shouldChargeModule.shouldChargeNoShowCancellationFee).mockReturnValue(false);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CancelBooking
|
||||||
|
booking={mockBookingWithCancellationFee}
|
||||||
|
profile={{ name: "Test User", slug: "test-user" }}
|
||||||
|
team={null}
|
||||||
|
isHost={false}
|
||||||
|
eventTypeMetadata={eventTypeMetadataWithOnBookingPayment}
|
||||||
|
{...mockProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText(/I acknowledge that cancelling within/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -475,6 +475,34 @@ describe("Booking Actions", () => {
|
|||||||
expect(isActionDisabled("cancel", context)).toBe(true);
|
expect(isActionDisabled("cancel", context)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should disable cancelling all past bookings", () => {
|
||||||
|
const pastConfirmedContext = createMockContext({
|
||||||
|
isBookingInPast: true,
|
||||||
|
isPending: false,
|
||||||
|
isConfirmed: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pastPendingContext = createMockContext({
|
||||||
|
isBookingInPast: true,
|
||||||
|
isPending: true,
|
||||||
|
isConfirmed: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Current implementation blocks ALL past bookings
|
||||||
|
expect(isActionDisabled("cancel", pastConfirmedContext)).toBe(true);
|
||||||
|
expect(isActionDisabled("cancel", pastPendingContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow cancelling future bookings when cancelling is not disabled", () => {
|
||||||
|
const futureContext = createMockContext({
|
||||||
|
isBookingInPast: false,
|
||||||
|
isPending: true,
|
||||||
|
isConfirmed: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(isActionDisabled("cancel", futureContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("should disable video actions for non-past bookings", () => {
|
it("should disable video actions for non-past bookings", () => {
|
||||||
const context = createMockContext({ isBookingInPast: false });
|
const context = createMockContext({ isBookingInPast: false });
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export function isActionDisabled(actionId: string, context: BookingActionContext
|
|||||||
case "reschedule_request":
|
case "reschedule_request":
|
||||||
return (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling;
|
return (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling;
|
||||||
case "cancel":
|
case "cancel":
|
||||||
return isDisabledCancelling || (isBookingInPast && isPending && !isConfirmed);
|
return isDisabledCancelling || isBookingInPast;
|
||||||
case "view_recordings":
|
case "view_recordings":
|
||||||
return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation);
|
return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation);
|
||||||
case "meeting_session_details":
|
case "meeting_session_details":
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/InstallAppButtonWithoutPlanCheck";
|
||||||
import type { TDependencyData } from "@calcom/app-store/_appRegistry";
|
import type { TDependencyData } from "@calcom/app-store/_appRegistry";
|
||||||
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/components";
|
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const UserProfile = ({ user }: UserProfileProps) => {
|
|||||||
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const createEventType = trpc.viewer.eventTypes.create.useMutation();
|
const createEventType = trpc.viewer.eventTypes.heavy.create.useMutation();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { getRecentImpersonations, type RecentImpersonation } from "@calcom/lib/recentImpersonations";
|
||||||
|
import { Button } from "@calcom/ui/components/button";
|
||||||
|
import { PanelCard } from "@calcom/ui/components/card";
|
||||||
|
import {
|
||||||
|
TableNew,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@calcom/ui/components/table";
|
||||||
|
|
||||||
|
interface RecentImpersonationsListProps {
|
||||||
|
onImpersonate?: (username: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RecentImpersonationsList({ onImpersonate }: RecentImpersonationsListProps) {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const [recentImpersonations] = useState<RecentImpersonation[]>(() => getRecentImpersonations());
|
||||||
|
|
||||||
|
const handleQuickImpersonate = (username: string) => {
|
||||||
|
if (onImpersonate) {
|
||||||
|
onImpersonate(username);
|
||||||
|
} else {
|
||||||
|
signIn("impersonation-auth", {
|
||||||
|
username: username.toLowerCase(),
|
||||||
|
callbackUrl: `${WEBAPP_URL}/event-types`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (recentImpersonations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelCard title={t("recent_impersonations")}>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<TableNew className="border-0">
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-[40%]">User</TableHead>
|
||||||
|
<TableHead>Impersonated At</TableHead>
|
||||||
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{recentImpersonations.map((impersonation) => (
|
||||||
|
<TableRow key={impersonation.username}>
|
||||||
|
<TableCell className="font-medium">{impersonation.username}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">
|
||||||
|
{new Date(impersonation.timestamp).toLocaleDateString()}{" "}
|
||||||
|
{new Date(impersonation.timestamp).toLocaleTimeString()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickImpersonate(impersonation.username)}
|
||||||
|
data-testid={`quick-impersonate-${impersonation.username}`}>
|
||||||
|
{t("quick_impersonate")}
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</TableNew>
|
||||||
|
</div>
|
||||||
|
</PanelCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import type { ParsedUrlQuery } from "querystring";
|
|||||||
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
|
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
import * as constants from "@calcom/lib/constants";
|
import * as constants from "@calcom/lib/constants";
|
||||||
import { RedirectType } from "@calcom/prisma/client";
|
import { RedirectType } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { handleOrgRedirect, getRedirectWithOriginAndSearchString } from "./handleOrgRedirect";
|
import { handleOrgRedirect, getRedirectWithOriginAndSearchString } from "./handleOrgRedirect";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { organizationScenarios } from "@calcom/lib/server/repository/__mocks__/o
|
|||||||
|
|
||||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import { CreationSource } from "@calcom/prisma/enums";
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
import { inviteMembersWithNoInviterPermissionCheck } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler";
|
import { inviteMembersWithNoInviterPermissionCheck } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler";
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
|||||||
import { IS_STRIPE_ENABLED } from "@calcom/lib/constants";
|
import { IS_STRIPE_ENABLED } from "@calcom/lib/constants";
|
||||||
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import { CreationSource } from "@calcom/prisma/enums";
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
import { userMetadata } from "@calcom/prisma/zod-utils";
|
import { userMetadata } from "@calcom/prisma/zod-utils";
|
||||||
import { inviteMembersWithNoInviterPermissionCheck } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler";
|
import { inviteMembersWithNoInviterPermissionCheck } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { getSafe } from "@calcom/lib/getSafe";
|
|||||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
||||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { BookingStatus } from "@calcom/prisma/client";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
uid: z.string(),
|
uid: z.string(),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { shouldHideBrandingForTeamEvent } from "@calcom/lib/hideBranding";
|
|||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { User } from "@calcom/prisma/client";
|
import type { User } from "@calcom/prisma/client";
|
||||||
import { BookingStatus, RedirectType, SchedulingType } from "@calcom/prisma/client";
|
import { BookingStatus, RedirectType, SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { handleOrgRedirect } from "@lib/handleOrgRedirect";
|
import { handleOrgRedirect } from "@lib/handleOrgRedirect";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import slugify from "@calcom/lib/slugify";
|
|||||||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Team, OrganizationSettings } from "@calcom/prisma/client";
|
import type { Team, OrganizationSettings } from "@calcom/prisma/client";
|
||||||
import { RedirectType } from "@calcom/prisma/client";
|
import { RedirectType } from "@calcom/prisma/enums";
|
||||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { handleOrgRedirect } from "@lib/handleOrgRedirect";
|
import { handleOrgRedirect } from "@lib/handleOrgRedirect";
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ const OnboardingPage = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [eventTypeGroups]);
|
}, [eventTypeGroups]);
|
||||||
|
|
||||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
const updateMutation = trpc.viewer.eventTypes.heavy.update.useMutation({
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
showToast(t("event_type_updated_successfully", { eventTypeTitle: data.eventType?.title }), "success");
|
showToast(t("event_type_updated_successfully", { eventTypeTitle: data.eventType?.title }), "success");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
bookingId: bookingInfo.id,
|
bookingId: bookingInfo.id,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
appId: true,
|
||||||
success: true,
|
success: true,
|
||||||
refunded: true,
|
refunded: true,
|
||||||
currency: true,
|
currency: true,
|
||||||
|
|||||||
@@ -839,13 +839,13 @@ export default function Success(props: PageProps) {
|
|||||||
{t("reschedule")}
|
{t("reschedule")}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
{canCancelAndReschedule && (
|
{!isBookingInPast && canCancel && (
|
||||||
<span className="mx-2">{t("or_lowercase")}</span>
|
<span className="mx-2">{t("or_lowercase")}</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canCancel && (
|
{!isBookingInPast && canCancel && (
|
||||||
<button
|
<button
|
||||||
data-testid="cancel"
|
data-testid="cancel"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -867,7 +867,10 @@ export default function Success(props: PageProps) {
|
|||||||
uid: bookingInfo?.uid,
|
uid: bookingInfo?.uid,
|
||||||
title: bookingInfo?.title,
|
title: bookingInfo?.title,
|
||||||
id: bookingInfo?.id,
|
id: bookingInfo?.id,
|
||||||
|
startTime: bookingInfo?.startTime,
|
||||||
|
payment: props.paymentStatus,
|
||||||
}}
|
}}
|
||||||
|
eventTypeMetadata={eventType.metadata}
|
||||||
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
||||||
recurringEvent={eventType.recurringEvent}
|
recurringEvent={eventType.recurringEvent}
|
||||||
team={eventType?.team?.name}
|
team={eventType?.team?.name}
|
||||||
|
|||||||
@@ -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) => {
|
onMutate: async (data) => {
|
||||||
await utils.viewer.eventTypes.getEventTypesFromGroup.cancel();
|
await utils.viewer.eventTypes.getEventTypesFromGroup.cancel();
|
||||||
const previousValue = utils.viewer.eventTypes.getEventTypesFromGroup.getInfiniteData({
|
const previousValue = utils.viewer.eventTypes.getEventTypesFromGroup.getInfiniteData({
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ const OnboardingPage = (props: PageProps) => {
|
|||||||
};
|
};
|
||||||
const currentStepIndex = steps.indexOf(currentStep);
|
const currentStepIndex = steps.indexOf(currentStep);
|
||||||
|
|
||||||
|
const goToStep = (step: number) => {
|
||||||
|
const newStep = steps[step];
|
||||||
|
startTransition(() => {
|
||||||
|
router.push(`/getting-started/${stepTransform(newStep)}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const goToNextStep = () => {
|
const goToNextStep = () => {
|
||||||
const nextIndex = currentStepIndex + 1;
|
const nextIndex = currentStepIndex + 1;
|
||||||
const newStep = steps[nextIndex];
|
const newStep = steps[nextIndex];
|
||||||
@@ -159,7 +166,7 @@ const OnboardingPage = (props: PageProps) => {
|
|||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</header>
|
</header>
|
||||||
<Steps maxSteps={steps.length} currentStep={currentStepIndex + 1} nextStep={goToNextStep} />
|
<Steps maxSteps={steps.length} currentStep={currentStepIndex + 1} navigateToStep={goToStep} />
|
||||||
</div>
|
</div>
|
||||||
<StepCard>
|
<StepCard>
|
||||||
<Suspense fallback={<Icon name="loader" />}>
|
<Suspense fallback={<Icon name="loader" />}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { SMSLockState } from "@calcom/prisma/client";
|
import { SMSLockState } from "@calcom/prisma/enums";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Avatar } from "@calcom/ui/components/avatar";
|
import { Avatar } from "@calcom/ui/components/avatar";
|
||||||
import type { IconName } from "@calcom/ui/components/icon";
|
import type { IconName } from "@calcom/ui/components/icon";
|
||||||
|
|||||||
@@ -2,23 +2,29 @@
|
|||||||
|
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { TextField } from "@calcom/ui/components/form";
|
import { addRecentImpersonation } from "@calcom/lib/recentImpersonations";
|
||||||
import { Button } from "@calcom/ui/components/button";
|
import { Button } from "@calcom/ui/components/button";
|
||||||
|
import { PanelCard } from "@calcom/ui/components/card";
|
||||||
|
import { TextField } from "@calcom/ui/components/form";
|
||||||
|
|
||||||
|
import RecentImpersonationsList from "../../../components/settings/admin/RecentImpersonationsList";
|
||||||
|
|
||||||
const ImpersonationView = () => {
|
const ImpersonationView = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const usernameRef = useRef<HTMLInputElement>(null);
|
const usernameRef = useRef<HTMLInputElement>(null);
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
const username = searchParams?.get("username")?.toLowerCase();
|
const username = searchParams?.get("username")?.toLowerCase();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (username) {
|
if (username) {
|
||||||
const enteredUsername = username.toLowerCase();
|
const enteredUsername = username.toLowerCase();
|
||||||
|
addRecentImpersonation(enteredUsername);
|
||||||
signIn("impersonation-auth", {
|
signIn("impersonation-auth", {
|
||||||
username: enteredUsername,
|
username: enteredUsername,
|
||||||
callbackUrl: `${WEBAPP_URL}/event-types`,
|
callbackUrl: `${WEBAPP_URL}/event-types`,
|
||||||
@@ -26,32 +32,52 @@ const ImpersonationView = () => {
|
|||||||
}
|
}
|
||||||
}, [username]);
|
}, [username]);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const enteredUsername = usernameRef.current?.value.toLowerCase();
|
||||||
|
if (enteredUsername) {
|
||||||
|
addRecentImpersonation(enteredUsername);
|
||||||
|
setRefreshKey((prev) => prev + 1);
|
||||||
|
signIn("impersonation-auth", {
|
||||||
|
username: enteredUsername,
|
||||||
|
callbackUrl: `${WEBAPP_URL}/event-types`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuickImpersonate = (username: string) => {
|
||||||
|
if (usernameRef.current) {
|
||||||
|
usernameRef.current.value = username;
|
||||||
|
}
|
||||||
|
addRecentImpersonation(username);
|
||||||
|
setRefreshKey((prev) => prev + 1);
|
||||||
|
signIn("impersonation-auth", {
|
||||||
|
username: username.toLowerCase(),
|
||||||
|
callbackUrl: `${WEBAPP_URL}/event-types`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<div className="flex flex-col gap-4">
|
||||||
className="mb-6 w-full"
|
<PanelCard title={t("user_impersonation_heading")} subtitle={t("impersonate_user_tip")}>
|
||||||
onSubmit={(e) => {
|
<form className="p-4" onSubmit={handleSubmit}>
|
||||||
e.preventDefault();
|
<div className="flex items-center space-x-2 rtl:space-x-reverse">
|
||||||
const enteredUsername = usernameRef.current?.value.toLowerCase();
|
<TextField
|
||||||
signIn("impersonation-auth", {
|
containerClassName="w-full"
|
||||||
username: enteredUsername,
|
name=""
|
||||||
callbackUrl: `${WEBAPP_URL}/event-types`,
|
addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
|
||||||
});
|
ref={usernameRef}
|
||||||
}}>
|
defaultValue={undefined}
|
||||||
<div className="flex items-center space-x-2 rtl:space-x-reverse">
|
data-testid="admin-impersonation-input"
|
||||||
<TextField
|
/>
|
||||||
containerClassName="w-full [&_input:-webkit-autofill]:!shadow-[0_0_0_1000px_white_inset]"
|
<Button type="submit" data-testid="impersonation-submit">
|
||||||
name={t("user_impersonation_heading")}
|
{t("impersonate")}
|
||||||
addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
|
</Button>
|
||||||
ref={usernameRef}
|
</div>
|
||||||
hint={t("impersonate_user_tip")}
|
</form>
|
||||||
defaultValue={undefined}
|
</PanelCard>
|
||||||
data-testid="admin-impersonation-input"
|
<RecentImpersonationsList key={refreshKey} onImpersonate={handleQuickImpersonate} />
|
||||||
/>
|
</div>
|
||||||
<Button type="submit" data-testid="impersonation-submit" className="mt-[-8px]">
|
|
||||||
{t("impersonate")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -511,12 +511,12 @@ const ProfileForm = ({
|
|||||||
.max(FULL_NAME_LENGTH_MAX_LIMIT, {
|
.max(FULL_NAME_LENGTH_MAX_LIMIT, {
|
||||||
message: t("max_limit_allowed_hint", { limit: FULL_NAME_LENGTH_MAX_LIMIT }),
|
message: t("max_limit_allowed_hint", { limit: FULL_NAME_LENGTH_MAX_LIMIT }),
|
||||||
}),
|
}),
|
||||||
email: emailSchema,
|
email: emailSchema.toLowerCase(),
|
||||||
bio: z.string(),
|
bio: z.string(),
|
||||||
secondaryEmails: z.array(
|
secondaryEmails: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
email: emailSchema,
|
email: emailSchema.toLowerCase(),
|
||||||
emailVerified: z.union([z.string(), z.null()]).optional(),
|
emailVerified: z.union([z.string(), z.null()]).optional(),
|
||||||
emailPrimary: z.boolean().optional(),
|
emailPrimary: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ const {
|
|||||||
orgUserTypeRoutePath,
|
orgUserTypeRoutePath,
|
||||||
orgUserTypeEmbedRoutePath,
|
orgUserTypeEmbedRoutePath,
|
||||||
} = require("./pagesAndRewritePaths");
|
} = require("./pagesAndRewritePaths");
|
||||||
|
|
||||||
|
adjustEnvVariables();
|
||||||
|
|
||||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||||
const isOrganizationsEnabled =
|
const isOrganizationsEnabled =
|
||||||
@@ -715,4 +718,22 @@ const nextConfig = (phase) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function adjustEnvVariables() {
|
||||||
|
if (process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG) {
|
||||||
|
if (process.env.RESERVED_SUBDOMAINS) {
|
||||||
|
// It is better to ignore it completely so that accidentally if the org slug is itself in Reserved Subdomain that doesn't cause the booking pages to start giving 404s
|
||||||
|
console.warn(
|
||||||
|
`⚠️ WARNING: RESERVED_SUBDOMAINS is ignored when SINGLE_ORG_SLUG is set. Single org mode doesn't need to use reserved subdomain validation.`
|
||||||
|
);
|
||||||
|
delete process.env.RESERVED_SUBDOMAINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.ORGANIZATIONS_ENABLED) {
|
||||||
|
// This is basically a consent to add rewrites related to organizations. So, if single org slug mode is there, we have the consent already.
|
||||||
|
console.log("Auto-enabling ORGANIZATIONS_ENABLED because SINGLE_ORG_SLUG is set");
|
||||||
|
process.env.ORGANIZATIONS_ENABLED = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = (phase) => plugins.reduce((acc, next) => next(acc), nextConfig(phase));
|
module.exports = (phase) => plugins.reduce((acc, next) => next(acc), nextConfig(phase));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.6.11",
|
"version": "5.6.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"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 { defaultHandler } from "@calcom/lib/server/defaultHandler";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import { getRoutedUrl } from "@calcom/lib/server/getRoutedUrl";
|
|
||||||
|
|
||||||
export default defaultHandler({
|
export default defaultHandler({
|
||||||
OPTIONS: Promise.resolve({
|
OPTIONS: Promise.resolve({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import appBasecamp3 from "@calcom/app-store/basecamp3/trpc-router";
|
|
||||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import appBasecamp3 from "@calcom/trpc/server/routers/apps/basecamp3/_router";
|
||||||
|
|
||||||
export default createNextApiHandler(appBasecamp3);
|
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 { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import appRoutingForms from "@calcom/trpc/server/routers/apps/routing-forms/_router";
|
||||||
|
|
||||||
export default createNextApiHandler(appRoutingForms);
|
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);
|
||||||
@@ -9,7 +9,7 @@ import dayjs from "@calcom/dayjs";
|
|||||||
import { intervalLimitKeyToUnit } from "@calcom/lib/intervalLimits/intervalLimit";
|
import { intervalLimitKeyToUnit } from "@calcom/lib/intervalLimits/intervalLimit";
|
||||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { BookingStatus } from "@calcom/prisma/client";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { entries } from "@calcom/prisma/zod-utils";
|
import { entries } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
import { test } from "./lib/fixtures";
|
import { test } from "./lib/fixtures";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { JSDOM } from "jsdom";
|
|||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
|
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
|
||||||
import { randomString } from "@calcom/lib/random";
|
import { randomString } from "@calcom/lib/random";
|
||||||
import { SchedulingType } from "@calcom/prisma/client";
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import type { Schedule, TimeRange } from "@calcom/types/schedule";
|
import type { Schedule, TimeRange } from "@calcom/types/schedule";
|
||||||
|
|
||||||
import { test, todo } from "./lib/fixtures";
|
import { test, todo } from "./lib/fixtures";
|
||||||
@@ -766,3 +766,31 @@ test.describe("GTM container", () => {
|
|||||||
expect(scriptContent).toContain("googletagmanager");
|
expect(scriptContent).toContain("googletagmanager");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Past booking cancellation", () => {
|
||||||
|
test("Cancel button should be hidden for past bookings", async ({ page, users, bookings }) => {
|
||||||
|
const user = await users.create({
|
||||||
|
name: "Test User",
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.apiLogin();
|
||||||
|
|
||||||
|
const pastDate = new Date();
|
||||||
|
pastDate.setDate(pastDate.getDate() - 1);
|
||||||
|
const endDate = new Date(pastDate.getTime() + 30 * 60 * 1000);
|
||||||
|
|
||||||
|
const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id, {
|
||||||
|
title: "Past Meeting",
|
||||||
|
startTime: pastDate,
|
||||||
|
endTime: endDate,
|
||||||
|
status: "ACCEPTED",
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("/bookings/past");
|
||||||
|
await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click();
|
||||||
|
await expect(page.locator('[data-testid="cancel"]')).toBeDisabled();
|
||||||
|
|
||||||
|
await page.goto(`/booking/${booking.uid}`);
|
||||||
|
await expect(page.locator('[data-testid="cancel"]')).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { expect } from "@playwright/test";
|
|||||||
import type { Page } from "@playwright/test";
|
import type { Page } from "@playwright/test";
|
||||||
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { BookingStatus } from "@calcom/prisma/client";
|
import { BookingStatus, MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
import { addFilter } from "./filter-helpers";
|
import { addFilter } from "./filter-helpers";
|
||||||
import { createTeamEventType } from "./fixtures/users";
|
import { createTeamEventType } from "./fixtures/users";
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { expect } from "@playwright/test";
|
||||||
|
import type Prisma from "@prisma/client";
|
||||||
|
|
||||||
|
import { test } from "./lib/fixtures";
|
||||||
|
import { IS_STRIPE_ENABLED, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
|
||||||
|
|
||||||
|
test.describe.configure({ mode: "parallel" });
|
||||||
|
test.afterEach(({ users }) => users.deleteAll());
|
||||||
|
|
||||||
|
test.describe("Cancellation Fee Warning E2E", () => {
|
||||||
|
test("Should display cancellation fee warning during booking submission", async ({ page, users }) => {
|
||||||
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
|
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
|
||||||
|
const user = await users.create();
|
||||||
|
const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType;
|
||||||
|
await user.apiLogin();
|
||||||
|
await page.goto("/apps/installed");
|
||||||
|
|
||||||
|
await user.installStripePersonal({ skip: true });
|
||||||
|
await user.setupEventWithPrice(eventType, "stripe");
|
||||||
|
|
||||||
|
await page.goto(`/event-types/${eventType?.id}?tabName=advanced`);
|
||||||
|
|
||||||
|
await page.locator('[data-testid="cancellation-fee-toggle"]').click();
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-value"]').fill("2");
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-unit"]').selectOption("hours");
|
||||||
|
|
||||||
|
await page.getByTestId("update-eventtype").click();
|
||||||
|
|
||||||
|
await page.goto(`${user.username}/${eventType?.slug}`);
|
||||||
|
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||||
|
|
||||||
|
await page.fill('[name="name"]', "Test Booker");
|
||||||
|
await page.fill('[name="email"]', "booker@example.com");
|
||||||
|
|
||||||
|
await expect(page.getByText(/I acknowledge that if I cancel within 2 hours/)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should display cancellation fee warning during cancellation", async ({ page, users }) => {
|
||||||
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
|
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
|
||||||
|
const user = await users.create();
|
||||||
|
const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType;
|
||||||
|
await user.apiLogin();
|
||||||
|
await page.goto("/apps/installed");
|
||||||
|
|
||||||
|
await user.installStripePersonal({ skip: true });
|
||||||
|
await user.setupEventWithPrice(eventType, "stripe");
|
||||||
|
|
||||||
|
await page.goto(`/event-types/${eventType?.id}?tabName=advanced`);
|
||||||
|
|
||||||
|
await page.locator('[data-testid="cancellation-fee-toggle"]').click();
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-value"]').fill("24");
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-unit"]').selectOption("hours");
|
||||||
|
|
||||||
|
await page.getByTestId("update-eventtype").click();
|
||||||
|
|
||||||
|
await user.bookAndPayEvent(eventType);
|
||||||
|
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||||
|
|
||||||
|
await page.goto("/bookings/upcoming");
|
||||||
|
await page.click('[data-testid="cancel"]');
|
||||||
|
|
||||||
|
await expect(page.getByText(/Cancelling within 24 hours will result in a/)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should not display cancellation fee warning when outside time threshold", async ({ page, users }) => {
|
||||||
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
|
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
|
||||||
|
const user = await users.create();
|
||||||
|
const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType;
|
||||||
|
await user.apiLogin();
|
||||||
|
await page.goto("/apps/installed");
|
||||||
|
|
||||||
|
await user.installStripePersonal({ skip: true });
|
||||||
|
await user.setupEventWithPrice(eventType, "stripe");
|
||||||
|
|
||||||
|
await page.goto(`/event-types/${eventType?.id}?tabName=advanced`);
|
||||||
|
|
||||||
|
await page.locator('[data-testid="cancellation-fee-toggle"]').click();
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-value"]').fill("1");
|
||||||
|
await page.locator('[data-testid="cancellation-fee-time-unit"]').selectOption("minutes");
|
||||||
|
|
||||||
|
await page.getByTestId("update-eventtype").click();
|
||||||
|
|
||||||
|
await page.goto(`${user.username}/${eventType?.slug}`);
|
||||||
|
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||||
|
|
||||||
|
await page.fill('[name="name"]', "Test Booker");
|
||||||
|
await page.fill('[name="email"]', "booker@example.com");
|
||||||
|
|
||||||
|
await expect(page.getByText(/I acknowledge that if I cancel within/)).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@ test.describe("Change App Theme Test", () => {
|
|||||||
const pro = await users.create();
|
const pro = await users.create();
|
||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
await page.click('[data-testid="appTheme-dark"]');
|
await page.click('[data-testid="appTheme-dark"]');
|
||||||
await page.click('[data-testid="update-app-theme-btn"]');
|
await page.click('[data-testid="update-app-theme-btn"]');
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ test.describe("Change App Theme Test", () => {
|
|||||||
const pro = await users.create();
|
const pro = await users.create();
|
||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
await page.click('[data-testid="appTheme-light"]');
|
await page.click('[data-testid="appTheme-light"]');
|
||||||
await page.click('[data-testid="update-app-theme-btn"]');
|
await page.click('[data-testid="update-app-theme-btn"]');
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ test.describe("Change App Theme Test", () => {
|
|||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
|
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
await page.click('[data-testid="appTheme-light"]');
|
await page.click('[data-testid="appTheme-light"]');
|
||||||
await page.click('[data-testid="update-app-theme-btn"]');
|
await page.click('[data-testid="update-app-theme-btn"]');
|
||||||
const toast1 = await page.waitForSelector('[data-testid="toast-success"]');
|
const toast1 = await page.waitForSelector('[data-testid="toast-success"]');
|
||||||
@@ -72,7 +72,7 @@ test.describe("Change Booking Page Theme Test", () => {
|
|||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
|
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
|
|
||||||
//Click the "Dark" theme label
|
//Click the "Dark" theme label
|
||||||
await page.click('[data-testid="theme-dark"]');
|
await page.click('[data-testid="theme-dark"]');
|
||||||
@@ -94,7 +94,7 @@ test.describe("Change Booking Page Theme Test", () => {
|
|||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
|
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
|
|
||||||
//Click the "Light" theme label
|
//Click the "Light" theme label
|
||||||
await page.click('[data-testid="theme-light"]');
|
await page.click('[data-testid="theme-light"]');
|
||||||
@@ -114,7 +114,7 @@ test.describe("Change Booking Page Theme Test", () => {
|
|||||||
await pro.apiLogin();
|
await pro.apiLogin();
|
||||||
|
|
||||||
await page.goto("/settings/my-account/appearance");
|
await page.goto("/settings/my-account/appearance");
|
||||||
await expect(page.locator('text="Dashboard theme"')).toBeVisible();
|
await expect(page.getByTestId("dashboard-shell").getByText("Dashboard theme")).toBeVisible();
|
||||||
|
|
||||||
await page.click('[data-testid="theme-light"]');
|
await page.click('[data-testid="theme-light"]');
|
||||||
await page.click('[data-testid="update-theme-btn"]');
|
await page.click('[data-testid="update-theme-btn"]');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from "@playwright/test";
|
import { expect } from "@playwright/test";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { test } from "./lib/fixtures";
|
import { test } from "./lib/fixtures";
|
||||||
import {
|
import {
|
||||||
@@ -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.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||||
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
||||||
await page.locator('[data-testid="update-eventtype"]').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 page.goto(`/${user.username}/multiple-duration`);
|
await page.goto(`/${user.username}/multiple-duration`);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { parse } from "node-html-parser";
|
|||||||
|
|
||||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { test } from "./lib/fixtures";
|
import { test } from "./lib/fixtures";
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ test.describe("Event Types tests", () => {
|
|||||||
expect(formTitle).toBe(firstTitle);
|
expect(formTitle).toBe(firstTitle);
|
||||||
expect(formSlug).toContain(firstSlug);
|
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();
|
await page.getByTestId("continue").click();
|
||||||
const response = await submitPromise;
|
const response = await submitPromise;
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
@@ -144,7 +144,7 @@ test.describe("Event Types tests", () => {
|
|||||||
await page.waitForURL((url) => {
|
await page.waitForURL((url) => {
|
||||||
return !!url.pathname.match(/\/event-types\/.+/);
|
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(),
|
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 page.locator("[data-testid=add-location]").click();
|
||||||
await fillLocation(page, locationData[2], 2);
|
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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ test.describe("Event Types tests", () => {
|
|||||||
const locationAddress = "New Delhi";
|
const locationAddress = "New Delhi";
|
||||||
|
|
||||||
await fillLocation(page, locationAddress, 0, false);
|
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(),
|
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.locator('[aria-label="Timezone Select"]').fill("New York");
|
||||||
await page.keyboard.press("Enter");
|
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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
await page.goto("/event-types");
|
await page.goto("/event-types");
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function createBookingPageFixture(page: Page) {
|
|||||||
await page.goto("/event-types");
|
await page.goto("/event-types");
|
||||||
},
|
},
|
||||||
updateEventType: async () => {
|
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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { expect, type Page } from "@playwright/test";
|
|||||||
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
import { WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
||||||
|
import type { Fixtures } from "@calcom/web/playwright/lib/fixtures";
|
||||||
|
|
||||||
import { localize } from "../lib/localize";
|
import { localize } from "../lib/localize";
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ type CreateWorkflowProps = {
|
|||||||
trigger?: WorkflowTriggerEvents;
|
trigger?: WorkflowTriggerEvents;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const subjectPattern = /^Reminder: /i;
|
||||||
|
|
||||||
export function createWorkflowPageFixture(page: Page) {
|
export function createWorkflowPageFixture(page: Page) {
|
||||||
const createWorkflow = async (props: CreateWorkflowProps) => {
|
const createWorkflow = async (props: CreateWorkflowProps) => {
|
||||||
const { name, isTeam, trigger } = props;
|
const { name, isTeam, trigger } = props;
|
||||||
@@ -25,8 +28,8 @@ export function createWorkflowPageFixture(page: Page) {
|
|||||||
await fillNameInput(name);
|
await fillNameInput(name);
|
||||||
}
|
}
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
page.locator("div").filter({ hasText: WorkflowTriggerEvents.BEFORE_EVENT }).nth(1);
|
await page.locator("#trigger-select").click();
|
||||||
page.getByText(trigger);
|
await page.getByTestId(`select-option-${trigger ?? WorkflowTriggerEvents.BEFORE_EVENT}`).click();
|
||||||
await selectEventType("30 min");
|
await selectEventType("30 min");
|
||||||
}
|
}
|
||||||
const workflow = await saveWorkflow();
|
const workflow = await saveWorkflow();
|
||||||
@@ -112,6 +115,20 @@ export function createWorkflowPageFixture(page: Page) {
|
|||||||
expect(deleteButton.isDisabled()).toBeTruthy();
|
expect(deleteButton.isDisabled()).toBeTruthy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const assertWorkflowWasTriggered = async (emails: Fixtures["emails"], emailsToBeReceived: string[]) => {
|
||||||
|
const message = await emails.messages();
|
||||||
|
emailsToBeReceived.forEach((email) => {
|
||||||
|
expect(message?.items).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
subject: expect.stringMatching(subjectPattern),
|
||||||
|
to: email,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const assertWorkflowReminders = async (eventTypeId: number, count: number) => {
|
const assertWorkflowReminders = async (eventTypeId: number, count: number) => {
|
||||||
const booking = await prisma.booking.findFirst({
|
const booking = await prisma.booking.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -143,5 +160,6 @@ export function createWorkflowPageFixture(page: Page) {
|
|||||||
selectedWorkflowPage,
|
selectedWorkflowPage,
|
||||||
workflowOptionsAreDisabled,
|
workflowOptionsAreDisabled,
|
||||||
assertWorkflowReminders,
|
assertWorkflowReminders,
|
||||||
|
assertWorkflowWasTriggered,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ test.describe("private links creation and usage", () => {
|
|||||||
const $url = await page.locator('[data-testid="private-link-url"]').inputValue();
|
const $url = await page.locator('[data-testid="private-link-url"]').inputValue();
|
||||||
|
|
||||||
// click update
|
// 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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
// book using generated url hash
|
// 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 page.locator("[data-testid=event-slug]").first().fill("somethingrandom");
|
||||||
await expect(page.locator('[data-testid="event-slug"]').first()).toHaveValue("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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
await page.locator("[data-testid=vertical-tab-event_advanced_tab_title]").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.locator('[data-testid="private-link-expiration-settings-save"]').click();
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
// click update
|
// 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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
// book using generated url hash
|
// 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.locator('[data-testid="private-link-expiration-settings-save"]').click();
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
// click update
|
// 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(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
});
|
});
|
||||||
// book using generated url hash
|
// book using generated url hash
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { safeStringify } from "@calcom/lib/safeStringify";
|
|||||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Team, User } from "@calcom/prisma/client";
|
import type { Team, User } from "@calcom/prisma/client";
|
||||||
import { RedirectType } from "@calcom/prisma/client";
|
|
||||||
import { Prisma } from "@calcom/prisma/client";
|
import { Prisma } from "@calcom/prisma/client";
|
||||||
|
import { RedirectType } from "@calcom/prisma/enums";
|
||||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import { teamMetadataSchema, teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
|
import { teamMetadataSchema, teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user