fix: v2 rescheduled booking with confirmation (#19833)
* fix: old bookings controller * fix: new bookings controller * fix: BookerPlatformWrapper pass rescheduledBy to BookerComponent * rescheduleBy email * fix test * remove log * centralize getOAuthUserEmail, examples app rescheduledBy * revert * finalize * fix: old bookings rescheduling * unify email validation * update input description * chore: move booker platform wrapper types --------- Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
co-authored by
Morgan Vernay
parent
ff5c6de5da
commit
76b9b9b0fe
@@ -14,8 +14,10 @@ import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { BillingService } from "@/modules/billing/services/billing.service";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
@@ -101,7 +103,8 @@ export class BookingsController_2024_04_15 {
|
||||
private readonly billingService: BillingService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly apiKeyRepository: ApiKeysRepository,
|
||||
private readonly platformBookingsService: PlatformBookingsService
|
||||
private readonly platformBookingsService: PlatformBookingsService,
|
||||
private readonly usersRepository: UsersRepository
|
||||
) {}
|
||||
|
||||
@Get("/")
|
||||
@@ -352,6 +355,30 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
}
|
||||
|
||||
private async getOwnerIdRescheduledBooking(
|
||||
request: Request,
|
||||
platformClientId?: string
|
||||
): Promise<number | undefined> {
|
||||
if (
|
||||
platformClientId &&
|
||||
request.body.rescheduledBy &&
|
||||
!request.body.rescheduledBy.includes(platformClientId)
|
||||
) {
|
||||
request.body.rescheduledBy = OAuthClientUsersService.getOAuthUserEmail(
|
||||
platformClientId,
|
||||
request.body.rescheduledBy
|
||||
);
|
||||
}
|
||||
|
||||
if (request.body.rescheduledBy) {
|
||||
if (request.body.rescheduledBy !== request.body.responses.email) {
|
||||
return (await this.usersRepository.findByEmail(request.body.rescheduledBy))?.id;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getOAuthClientIdFromEventType(eventTypeId: number): Promise<string | undefined> {
|
||||
if (!eventTypeId) {
|
||||
return undefined;
|
||||
@@ -396,7 +423,9 @@ export class BookingsController_2024_04_15 {
|
||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||
const requestId = req.get("X-Request-Id");
|
||||
const clone = { ...req };
|
||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||
const userId = clone.body.rescheduleUid
|
||||
? await this.getOwnerIdRescheduledBooking(req, oAuthClientId)
|
||||
: await this.getOwnerId(req);
|
||||
const oAuthParams = oAuthClientId
|
||||
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||
: DEFAULT_PLATFORM_PARAMS;
|
||||
|
||||
+10
-8
@@ -12,6 +12,7 @@ import {
|
||||
CreateManagedUserData,
|
||||
CreateManagedUserOutput,
|
||||
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
@@ -140,7 +141,9 @@ describe("Managed user bookings 2024-04-15", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -193,7 +196,9 @@ describe("Managed user bookings 2024-04-15", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -220,7 +225,9 @@ describe("Managed user bookings 2024-04-15", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -444,11 +451,6 @@ describe("Managed user bookings 2024-04-15", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
|
||||
const [username, emailDomain] = userEmail.split("@");
|
||||
return `${username}+${oAuthClientId}@${emailDomain}`;
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
||||
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
Validate,
|
||||
} from "class-validator";
|
||||
|
||||
import { RESCHEDULED_BY_DOCS } from "@calcom/platform-types";
|
||||
|
||||
class Location {
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
@@ -76,6 +78,13 @@ export class CreateBookingInput_2024_04_15 {
|
||||
@ApiPropertyOptional()
|
||||
rescheduleUid?: string;
|
||||
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({ description: RESCHEDULED_BY_DOCS })
|
||||
@Validate((value: string) => !value || isEmail(value), {
|
||||
message: "Invalid rescheduledBy email format",
|
||||
})
|
||||
rescheduledBy?: string;
|
||||
|
||||
@IsTimeZone()
|
||||
@ApiProperty()
|
||||
timeZone!: string;
|
||||
|
||||
+172
-114
@@ -94,6 +94,8 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = `confirm-emails-2024-08-13-admin-${randomString()}@api.com`;
|
||||
let userEmailsEnabled = "";
|
||||
const userEmailsDisabled = `confirm-emails-2024-08-13-user-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -141,8 +143,12 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
async function setupEnabledEmails() {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
userEmailsEnabled = `confirm-emails-2024-08-13-user-${randomString()}+${
|
||||
oAuthClientEmailsEnabled.id
|
||||
}@api.com`;
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
email: userEmailsEnabled,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -179,7 +185,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
email: userEmailsDisabled,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -282,9 +288,6 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeScheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
|
||||
expect(OrganizerScheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
emailsDisabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -338,10 +341,6 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeDeclinedEmail.prototype.getHtml).not.toHaveBeenCalled();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
emailsDisabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -351,117 +350,176 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should send an email when creating a booking that requires confirmation", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: emailsEnabledSetup.eventTypeId,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
bookingFieldsResponses: {
|
||||
customField: "customValue",
|
||||
},
|
||||
metadata: {
|
||||
userId: "100",
|
||||
},
|
||||
};
|
||||
describe("confirming booking that requires confirmation and rescheduling it", () => {
|
||||
it("should send an email when creating a booking that requires confirmation", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: emailsEnabledSetup.eventTypeId,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
bookingFieldsResponses: {
|
||||
customField: "customValue",
|
||||
},
|
||||
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);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("pending");
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
);
|
||||
}
|
||||
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);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("pending");
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should send an email when confirming a booking", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/confirm`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeScheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
expect(OrganizerScheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("rescheduling emails", () => {
|
||||
it("should send confirmation emails when organizer reschedules a booking that requires confirmation", async () => {
|
||||
const body: RescheduleBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 14, 30, 0)).toISOString(),
|
||||
rescheduledBy: userEmailsEnabled,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/reschedule`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("accepted");
|
||||
expect(AttendeeRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
expect(OrganizerRescheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).not.toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).not.toHaveBeenCalled();
|
||||
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should send requested rescheduling emails when attendee rescheduling a booking that requires confirmation", async () => {
|
||||
const body: RescheduleBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 9, 0, 0)).toISOString(),
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.rescheduledBookingUid}/reschedule`)
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("pending");
|
||||
expect(AttendeeRescheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
|
||||
expect(OrganizerRescheduledEmail.prototype.getHtml).not.toHaveBeenCalled();
|
||||
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send an email when confirming a booking", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/confirm`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeScheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
expect(OrganizerScheduledEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
});
|
||||
});
|
||||
describe("declining booking that requires confirmation", () => {
|
||||
it("should send an email when creating a booking that requires confirmation", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
|
||||
eventTypeId: emailsEnabledSetup.eventTypeId,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
bookingFieldsResponses: {
|
||||
customField: "customValue",
|
||||
},
|
||||
metadata: {
|
||||
userId: "100",
|
||||
},
|
||||
};
|
||||
|
||||
it("should send an email when creating a booking that requires confirmation", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
|
||||
eventTypeId: emailsEnabledSetup.eventTypeId,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "it",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
bookingFieldsResponses: {
|
||||
customField: "customValue",
|
||||
},
|
||||
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);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("pending");
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
expect(responseBody.data.status).toEqual("pending");
|
||||
expect(AttendeeRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
expect(OrganizerRequestEmail.prototype.getHtmlRequestEmail).toHaveBeenCalled();
|
||||
emailsEnabledSetup.createdBookingUid = responseBody.data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should send an email when declining a booking", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/decline`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeDeclinedEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
emailsEnabledSetup.rescheduledBookingUid = responseBody.data.uid;
|
||||
});
|
||||
it("should send an email when declining a booking", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/decline`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(AttendeeDeclinedEmail.prototype.getHtml).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+10
-8
@@ -7,6 +7,7 @@ import {
|
||||
CreateManagedUserData,
|
||||
CreateManagedUserOutput,
|
||||
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
@@ -149,7 +150,9 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -202,7 +205,9 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -229,7 +234,9 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const responseBody: CreateManagedUserOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.accessToken).toBeDefined();
|
||||
expect(responseBody.data.refreshToken).toBeDefined();
|
||||
|
||||
@@ -470,11 +477,6 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
|
||||
const [username, emailDomain] = userEmail.split("@");
|
||||
return `${username}+${oAuthClientId}@${emailDomain}`;
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
||||
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
||||
|
||||
@@ -8,7 +8,9 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_20
|
||||
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
@@ -75,7 +77,8 @@ export class InputBookingsService_2024_08_13 {
|
||||
private readonly config: ConfigService,
|
||||
private readonly apiKeyRepository: ApiKeysRepository,
|
||||
private readonly bookingSeatRepository: BookingSeatRepository,
|
||||
private readonly platformBookingsService: PlatformBookingsService
|
||||
private readonly platformBookingsService: PlatformBookingsService,
|
||||
private readonly usersRepository: UsersRepository
|
||||
) {}
|
||||
|
||||
async createBookingRequest(
|
||||
@@ -314,7 +317,24 @@ export class InputBookingsService_2024_08_13 {
|
||||
);
|
||||
|
||||
const newRequest = { ...request };
|
||||
const userId = (await this.createBookingRequestOwnerId(request)) ?? undefined;
|
||||
let userId: number | undefined = undefined;
|
||||
|
||||
if (
|
||||
oAuthClientParams &&
|
||||
request.body.rescheduledBy &&
|
||||
!request.body.rescheduledBy.includes(oAuthClientParams.platformClientId)
|
||||
) {
|
||||
request.body.rescheduledBy = OAuthClientUsersService.getOAuthUserEmail(
|
||||
oAuthClientParams.platformClientId,
|
||||
request.body.rescheduledBy
|
||||
);
|
||||
}
|
||||
|
||||
if (request.body.rescheduledBy) {
|
||||
if (request.body.rescheduledBy !== bodyTransformed.responses.email) {
|
||||
userId = (await this.usersRepository.findByEmail(request.body.rescheduledBy))?.id;
|
||||
}
|
||||
}
|
||||
|
||||
const location = await this.getRescheduleBookingLocation(bookingUid);
|
||||
if (oAuthClientParams) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PlatformOAuthClient } from "@calcom/prisma/client";
|
||||
@Injectable()
|
||||
export class PlatformBookingsService {
|
||||
constructor(
|
||||
private readonly oAuthClientsUsersService: OAuthClientUsersService,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly oAuthClientRepository: OAuthClientRepository
|
||||
@@ -25,7 +24,7 @@ export class PlatformBookingsService {
|
||||
if (!attendeeEmail.includes(platformClientId)) {
|
||||
// note(Lauris): we need to do this when managed user is booking another managed user and the managed user doing the booking entered their email without oAuth client id
|
||||
// or if one of the guests added are managed users without oAuth client id in their email.
|
||||
const oAuthUserEmail = this.oAuthClientsUsersService.getOAuthUserEmail(platformClientId, attendeeEmail);
|
||||
const oAuthUserEmail = OAuthClientUsersService.getOAuthUserEmail(platformClientId, attendeeEmail);
|
||||
const oAuthUser = await this.usersRepository.findByEmail(oAuthUserEmail);
|
||||
if (oAuthUser) {
|
||||
return oAuthUserEmail;
|
||||
|
||||
+11
-11
@@ -7,6 +7,7 @@ import { Locales } from "@/lib/enums/locales";
|
||||
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 { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
@@ -218,7 +219,9 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(getOAuthUserEmail(oAuthClient.id, requestBody.email));
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
|
||||
expect(responseBody.data.user.name).toEqual(requestBody.name);
|
||||
expect(responseBody.data.user.weekStart).toEqual(requestBody.weekStart);
|
||||
@@ -297,7 +300,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.user.email).toEqual(
|
||||
getOAuthUserEmail(oAuthClientEventTypesDisabled.id, requestBody.email)
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClientEventTypesDisabled.id, requestBody.email)
|
||||
);
|
||||
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
|
||||
expect(responseBody.data.user.name).toEqual(requestBody.name);
|
||||
@@ -518,7 +521,9 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.email).toEqual(getOAuthUserEmail(oAuthClient.id, userEmail));
|
||||
expect(responseBody.data.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, userEmail)
|
||||
);
|
||||
});
|
||||
|
||||
it(`/PUT/:id`, async () => {
|
||||
@@ -536,7 +541,9 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data.email).toEqual(getOAuthUserEmail(oAuthClient.id, userUpdatedEmail));
|
||||
expect(responseBody.data.email).toEqual(
|
||||
OAuthClientUsersService.getOAuthUserEmail(oAuthClient.id, userUpdatedEmail)
|
||||
);
|
||||
expect(responseBody.data.locale).toEqual(Locales.PT_BR);
|
||||
});
|
||||
|
||||
@@ -548,13 +555,6 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
function getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
|
||||
const [username, emailDomain] = userEmail.split("@");
|
||||
const email = `${username}+${oAuthClientId}@${emailDomain}`;
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||
await oauthClientRepositoryFixture.delete(oAuthClientEventTypesDisabled.id);
|
||||
|
||||
@@ -36,7 +36,7 @@ export class OAuthClientUsersService {
|
||||
"You cannot create a managed user outside of an organization - the OAuth client does not belong to any organization."
|
||||
);
|
||||
} else {
|
||||
const email = this.getOAuthUserEmail(oAuthClientId, body.email);
|
||||
const email = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, body.email);
|
||||
user = (
|
||||
await createNewUsersConnectToOrgIfExists({
|
||||
invitations: [
|
||||
@@ -98,7 +98,7 @@ export class OAuthClientUsersService {
|
||||
}
|
||||
|
||||
async getExistingUserByEmail(oAuthClientId: string, email: string) {
|
||||
const oAuthEmail = this.getOAuthUserEmail(oAuthClientId, email);
|
||||
const oAuthEmail = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, email);
|
||||
return await this.userRepository.findByEmail(oAuthEmail);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export class OAuthClientUsersService {
|
||||
|
||||
async updateOAuthClientUser(oAuthClientId: string, userId: number, body: UpdateManagedUserInput) {
|
||||
if (body.email) {
|
||||
const emailWithOAuthId = this.getOAuthUserEmail(oAuthClientId, body.email);
|
||||
const emailWithOAuthId = OAuthClientUsersService.getOAuthUserEmail(oAuthClientId, body.email);
|
||||
body.email = emailWithOAuthId;
|
||||
const newUsername = slugify(emailWithOAuthId);
|
||||
await this.userRepository.updateUsername(userId, newUsername);
|
||||
@@ -130,7 +130,7 @@ export class OAuthClientUsersService {
|
||||
return this.userRepository.update(userId, body);
|
||||
}
|
||||
|
||||
getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
|
||||
static getOAuthUserEmail(oAuthClientId: string, userEmail: string) {
|
||||
const [username, emailDomain] = userEmail.split("@");
|
||||
return `${username}+${oAuthClientId}@${emailDomain}`;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BookerPlatformWrapper } from "../booker/BookerPlatformWrapper";
|
||||
import type {
|
||||
BookerPlatformWrapperAtomPropsForIndividual,
|
||||
BookerPlatformWrapperAtomPropsForTeam,
|
||||
} from "../booker/BookerPlatformWrapper";
|
||||
import { BookerPlatformWrapper } from "../booker/BookerPlatformWrapper";
|
||||
} from "../booker/types";
|
||||
import { CalProvider } from "../cal-provider/CalProvider";
|
||||
import { useGetRoutingFormUrlProps } from "./useGetRoutingFormUrlProps";
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useMemo, useEffect, useCallback, useState } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
||||
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
|
||||
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||
@@ -15,20 +14,12 @@ import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/get
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import type { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
|
||||
import type { BookingResponse } from "@calcom/platform-libraries";
|
||||
import type {
|
||||
ApiErrorResponse,
|
||||
ApiSuccessResponse,
|
||||
ApiSuccessResponseWithoutData,
|
||||
} from "@calcom/platform-types";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import {
|
||||
transformApiEventTypeForAtom,
|
||||
transformApiTeamEventTypeForAtom,
|
||||
} from "../event-types/atom-api-transformers/transformApiEventTypeForAtom";
|
||||
import type { UseCreateBookingInput } from "../hooks/bookings/useCreateBooking";
|
||||
import { useCreateBooking } from "../hooks/bookings/useCreateBooking";
|
||||
import { useCreateInstantBooking } from "../hooks/bookings/useCreateInstantBooking";
|
||||
import { useCreateRecurringBooking } from "../hooks/bookings/useCreateRecurringBooking";
|
||||
@@ -46,59 +37,10 @@ import { useConnectedCalendars } from "../hooks/useConnectedCalendars";
|
||||
import { useMe } from "../hooks/useMe";
|
||||
import { useSlots } from "../hooks/useSlots";
|
||||
import { AtomsWrapper } from "../src/components/atoms-wrapper";
|
||||
|
||||
export type BookerPlatformWrapperAtomProps = Omit<
|
||||
BookerProps,
|
||||
"username" | "entity" | "isTeamEvent" | "teamId"
|
||||
> & {
|
||||
rescheduleUid?: string;
|
||||
rescheduledBy?: string;
|
||||
bookingUid?: string;
|
||||
entity?: BookerProps["entity"];
|
||||
// values for the booking form and booking fields
|
||||
defaultFormValues?: {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
guests?: string[];
|
||||
name?: string;
|
||||
email?: string;
|
||||
notes?: string;
|
||||
rescheduleReason?: string;
|
||||
} & Record<string, string | string[]>;
|
||||
handleCreateBooking?: (input: UseCreateBookingInput) => void;
|
||||
onCreateBookingSuccess?: (data: ApiSuccessResponse<BookingResponse>) => void;
|
||||
onCreateBookingError?: (data: ApiErrorResponse | Error) => void;
|
||||
onCreateRecurringBookingSuccess?: (data: ApiSuccessResponse<BookingResponse[]>) => void;
|
||||
onCreateRecurringBookingError?: (data: ApiErrorResponse | Error) => void;
|
||||
onCreateInstantBookingSuccess?: (data: ApiSuccessResponse<BookingResponse>) => void;
|
||||
onCreateInstantBookingError?: (data: ApiErrorResponse | Error) => void;
|
||||
onReserveSlotSuccess?: (data: ApiSuccessResponse<string>) => void;
|
||||
onReserveSlotError?: (data: ApiErrorResponse) => void;
|
||||
onDeleteSlotSuccess?: (data: ApiSuccessResponseWithoutData) => void;
|
||||
onDeleteSlotError?: (data: ApiErrorResponse) => void;
|
||||
locationUrl?: string;
|
||||
view?: VIEW_TYPE;
|
||||
metadata?: Record<string, string>;
|
||||
bannerUrl?: string;
|
||||
onDryRunSuccess?: () => void;
|
||||
hostsLimit?: number;
|
||||
preventEventTypeRedirect?: boolean;
|
||||
};
|
||||
|
||||
type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||
|
||||
export type BookerPlatformWrapperAtomPropsForIndividual = BookerPlatformWrapperAtomProps & {
|
||||
username: string | string[];
|
||||
isTeamEvent?: false;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomProps & {
|
||||
username?: string | string[];
|
||||
isTeamEvent: true;
|
||||
teamId: number;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
import type {
|
||||
BookerPlatformWrapperAtomPropsForIndividual,
|
||||
BookerPlatformWrapperAtomPropsForTeam,
|
||||
} from "./types";
|
||||
|
||||
export const BookerPlatformWrapper = (
|
||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||
@@ -504,7 +446,7 @@ export const BookerPlatformWrapper = (
|
||||
}
|
||||
}
|
||||
rescheduleUid={props.rescheduleUid ?? null}
|
||||
rescheduledBy={null}
|
||||
rescheduledBy={props.rescheduledBy}
|
||||
bookingUid={props.bookingUid ?? null}
|
||||
isRedirect={false}
|
||||
fromUserNameRedirected=""
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ApiSuccessResponse,
|
||||
ApiErrorResponse,
|
||||
ApiSuccessResponseWithoutData,
|
||||
RoutingFormSearchParams,
|
||||
} from "@calcom/platform-types";
|
||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
@@ -48,3 +49,16 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
||||
};
|
||||
|
||||
type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||
|
||||
export type BookerPlatformWrapperAtomPropsForIndividual = BookerPlatformWrapperAtomProps & {
|
||||
username: string | string[];
|
||||
isTeamEvent?: false;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomProps & {
|
||||
username?: string | string[];
|
||||
isTeamEvent: true;
|
||||
teamId: number;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ReactElement } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { BookerEmbed } from "../booker-embed";
|
||||
import type { BookerPlatformWrapperAtomPropsForTeam } from "../booker/BookerPlatformWrapper";
|
||||
import type { BookerPlatformWrapperAtomPropsForTeam } from "../booker/types";
|
||||
|
||||
/**
|
||||
* Renders the Router component with predefined props.
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!!booking.bookingFieldsResponses?.notes && (
|
||||
{"bookingFieldsResponses" in booking && !!booking.bookingFieldsResponses?.notes && (
|
||||
<div className="flex gap-[70px]">
|
||||
<div className="w-[40px]">
|
||||
<h4>Additional notes</h4>
|
||||
@@ -140,7 +140,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
className="underline"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/booking?rescheduleUid=${booking?.uid}&eventTypeSlug=${booking?.eventType.slug}`
|
||||
`/booking?rescheduleUid=${booking?.uid}&eventTypeSlug=${booking?.eventType.slug}&rescheduledBy=${props.calEmail}`
|
||||
);
|
||||
}}>
|
||||
Reschedule
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
const { data: teams } = useTeams();
|
||||
const { isLoading: isLoadingTeamEvents, data: teamEventTypes } = useTeamEventTypes(teams?.[0]?.id || 0);
|
||||
const rescheduleUid = (router.query.rescheduleUid as string) ?? "";
|
||||
const rescheduledBy = (router.query.rescheduledBy as string) ?? "";
|
||||
const eventTypeSlugQueryParam = (router.query.eventTypeSlug as string) ?? "";
|
||||
|
||||
return (
|
||||
@@ -125,6 +126,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
{!bookingTitle && rescheduleUid && eventTypeSlugQueryParam && (
|
||||
<Booker
|
||||
rescheduleUid={rescheduleUid}
|
||||
rescheduledBy={rescheduledBy}
|
||||
eventSlug={eventTypeSlugQueryParam}
|
||||
username={props.calUsername ?? ""}
|
||||
onCreateBookingSuccess={(data) => {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsDateString, IsOptional, IsString } from "class-validator";
|
||||
import { IsDateString, IsOptional, IsString, Validate, isEmail } from "class-validator";
|
||||
|
||||
export const RESCHEDULED_BY_DOCS = `Email of the person who is rescheduling the booking - only needed when rescheduling a booking that requires a confirmation.
|
||||
If event type owner email is provided then rescheduled booking will be automatically confirmed. If attendee email or no email is passed then the event type
|
||||
owner will have to confirm the rescheduled booking.`;
|
||||
|
||||
export class RescheduleBookingInput_2024_08_13 {
|
||||
@IsDateString()
|
||||
@@ -9,6 +13,13 @@ export class RescheduleBookingInput_2024_08_13 {
|
||||
})
|
||||
start!: string;
|
||||
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({ description: RESCHEDULED_BY_DOCS })
|
||||
@Validate((value: string) => !value || isEmail(value), {
|
||||
message: "Invalid rescheduledBy email format",
|
||||
})
|
||||
rescheduledBy?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
@@ -26,6 +37,13 @@ export class RescheduleSeatedBookingInput_2024_08_13 {
|
||||
})
|
||||
start!: string;
|
||||
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({ description: RESCHEDULED_BY_DOCS })
|
||||
@Validate((value: string) => !value || isEmail(value), {
|
||||
message: "Invalid rescheduledBy email format",
|
||||
})
|
||||
rescheduledBy?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
example: "3be561a9-31f1-4b8e-aefc-9d9a085f0dd1",
|
||||
|
||||
Reference in New Issue
Block a user