refactor: v2 event-types (#15457)
* refactor: UserAvatarGroupWithOrg.tsx remove unecessary profile prop * chore: define event-type types in platform/types * feat: event-type transformes in lib and re-exported through platform-libraries * chore: define user types in platform/types * chore: add users endpoint to platform-constants * refactor: app-store use narrowed down booker event type * chore: version old event-types module * fix: old event-types e2e test * fix: libraries add missing export * fix: reset libraries version to 0 * feat: new event-types module and users endpoints * feat: make booker atom work with v2 event-types * updating event type * refactor: remove guard for get event-types * refactor: move private hook to public * Revert "refactor: remove guard for get event-types" This reverts commit d41204069f1d5bb1305b388ce53399f9175f4249. * Revert "refactor: move private hook to public" This reverts commit 09322e8fba102e57104959f79ed6bfa9a677dc9d. * refactor: get by username and slug in /event-types * remove console log * feat: locations handle displayEventPublicly * test e2e event-types * revert: user output from types * refactor: event-types hooks have private and public folders * refactor: require event-type.slug in input * refactor: demand that all labels of booking fields are unique * refactor: remove unused import * refactor: only have email and name by default in booker * refactor: add booking field slug * fix: display event-type location publicly / hide it in booker * fix: display event-type location publicly / hide it in booker * fix: packages/lib tests * fix: typescript in e2e test * fix: dynamic event types input * refactor: use IsIn instead of IsEnum * refactor: simplify getEventTypes * fix: use ApiAuthGuard instead of AccessTokenGuard * chore: export more stuff from libraries * refactor: SchedulingTypeEnum and Type --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
co-authored by
Morgan
parent
c7a96e1b43
commit
0072aba55d
@@ -25,6 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-libraries-0.0.13": "npm:@calcom/platform-libraries@0.0.13",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-libraries-0.0.4": "npm:@calcom/platform-libraries@0.0.4",
|
||||
"@calcom/platform-types": "*",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { EventTypesController } from "@/ee/event-types/controllers/event-types.controller";
|
||||
import { EventTypesRepository } from "@/ee/event-types/event-types.repository";
|
||||
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, MembershipsModule, TokensModule, UsersModule, SelectedCalendarsModule],
|
||||
providers: [EventTypesRepository, EventTypesService],
|
||||
controllers: [EventTypesController],
|
||||
exports: [EventTypesService, EventTypesRepository],
|
||||
})
|
||||
export class EventTypesModule {}
|
||||
+15
-15
@@ -1,13 +1,13 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { EventTypesModule } from "@/ee/event-types/event-types.module";
|
||||
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { Editable } from "@/ee/event-types/inputs/enums/editable";
|
||||
import { BaseField } from "@/ee/event-types/inputs/enums/field-type";
|
||||
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output";
|
||||
import { Editable } from "@/ee/event-types/event-types_2024_04_15//inputs/enums/editable";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
|
||||
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
@@ -40,7 +40,7 @@ describe("Event types Endpoints", () => {
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule, TokensModule],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_04_15, TokensModule],
|
||||
})
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
@@ -83,7 +83,7 @@ describe("Event types Endpoints", () => {
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule, TokensModule],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_04_15, TokensModule],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
@@ -132,7 +132,7 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const body: CreateEventTypeInput = {
|
||||
const body: CreateEventTypeInput_2024_04_15 = {
|
||||
title: "Test Event Type",
|
||||
slug: "test-event-type",
|
||||
description: "A description of the test event type.",
|
||||
@@ -173,7 +173,7 @@ describe("Event types Endpoints", () => {
|
||||
it("should update event type", async () => {
|
||||
const newTitle = "Updated title";
|
||||
|
||||
const body: UpdateEventTypeInput = {
|
||||
const body: UpdateEventTypeInput_2024_04_15 = {
|
||||
title: newTitle,
|
||||
disableGuests: false,
|
||||
slotInterval: 30,
|
||||
@@ -207,7 +207,7 @@ describe("Event types Endpoints", () => {
|
||||
it("should return 400 if param event type id is null", async () => {
|
||||
const locations = [{ type: "inPerson", address: "123 Main St" }];
|
||||
|
||||
const body: UpdateEventTypeInput = {
|
||||
const body: UpdateEventTypeInput_2024_04_15 = {
|
||||
locations,
|
||||
};
|
||||
|
||||
@@ -217,7 +217,7 @@ describe("Event types Endpoints", () => {
|
||||
it("should update event type locations", async () => {
|
||||
const locations = [{ type: "inPerson", address: "123 Main St" }];
|
||||
|
||||
const body: UpdateEventTypeInput = {
|
||||
const body: UpdateEventTypeInput_2024_04_15 = {
|
||||
locations,
|
||||
};
|
||||
|
||||
@@ -266,7 +266,7 @@ describe("Event types Endpoints", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const body: UpdateEventTypeInput = {
|
||||
const body: UpdateEventTypeInput_2024_04_15 = {
|
||||
bookingFields,
|
||||
};
|
||||
|
||||
+24
-23
@@ -1,16 +1,19 @@
|
||||
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { EventTypeIdParams } from "@/ee/event-types/inputs/event-type-id.input";
|
||||
import { GetPublicEventTypeQueryParams } from "@/ee/event-types/inputs/get-public-event-type-query-params.input";
|
||||
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
|
||||
import { CreateEventTypeOutput } from "@/ee/event-types/outputs/create-event-type.output";
|
||||
import { DeleteEventTypeOutput } from "@/ee/event-types/outputs/delete-event-type.output";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output";
|
||||
import { GetEventTypesData, GetEventTypesOutput } from "@/ee/event-types/outputs/get-event-types.output";
|
||||
import { UpdateEventTypeOutput } from "@/ee/event-types/outputs/update-event-type.output";
|
||||
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { EventTypeIdParams_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-id.input";
|
||||
import { GetPublicEventTypeQueryParams_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/get-public-event-type-query-params.input";
|
||||
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { CreateEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/create-event-type.output";
|
||||
import { DeleteEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/delete-event-type.output";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
|
||||
import {
|
||||
GetEventTypesData,
|
||||
GetEventTypesOutput,
|
||||
} from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types.output";
|
||||
import { UpdateEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/update-event-type.output";
|
||||
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
|
||||
import { VERSION_2024_04_15_VALUE } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
@@ -42,13 +45,13 @@ import { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/event-types",
|
||||
version: API_VERSIONS_VALUES,
|
||||
version: VERSION_2024_04_15_VALUE,
|
||||
})
|
||||
@UseGuards(PermissionsGuard)
|
||||
@DocsTags("Event types")
|
||||
export class EventTypesController {
|
||||
export class EventTypesController_2024_04_15 {
|
||||
constructor(
|
||||
private readonly eventTypesService: EventTypesService,
|
||||
private readonly eventTypesService: EventTypesService_2024_04_15,
|
||||
private readonly prismaReadService: PrismaReadService
|
||||
) {}
|
||||
|
||||
@@ -56,7 +59,7 @@ export class EventTypesController {
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async createEventType(
|
||||
@Body() body: CreateEventTypeInput,
|
||||
@Body() body: CreateEventTypeInput_2024_04_15,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<CreateEventTypeOutput> {
|
||||
const eventType = await this.eventTypesService.createUserEventType(user, body);
|
||||
@@ -71,9 +74,7 @@ export class EventTypesController {
|
||||
@Permissions([EVENT_TYPE_READ])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async getEventType(
|
||||
@Param() params: EventTypeIdParams,
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<GetEventTypeOutput> {
|
||||
const eventType = await this.eventTypesService.getUserEventTypeForAtom(user, Number(eventTypeId));
|
||||
@@ -109,7 +110,7 @@ export class EventTypesController {
|
||||
async getPublicEventType(
|
||||
@Param("username") username: string,
|
||||
@Param("eventSlug") eventSlug: string,
|
||||
@Query() queryParams: GetPublicEventTypeQueryParams
|
||||
@Query() queryParams: GetPublicEventTypeQueryParams_2024_04_15
|
||||
): Promise<GetEventTypePublicOutput> {
|
||||
try {
|
||||
const event = await getPublicEvent(
|
||||
@@ -149,9 +150,9 @@ export class EventTypesController {
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async updateEventType(
|
||||
@Param() params: EventTypeIdParams,
|
||||
@Param() params: EventTypeIdParams_2024_04_15,
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||
@Body() body: UpdateEventTypeInput,
|
||||
@Body() body: UpdateEventTypeInput_2024_04_15,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<UpdateEventTypeOutput> {
|
||||
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
|
||||
@@ -166,7 +167,7 @@ export class EventTypesController {
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async deleteEventType(
|
||||
@Param() params: EventTypeIdParams,
|
||||
@Param() params: EventTypeIdParams_2024_04_15,
|
||||
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
|
||||
@GetUser("id") userId: number
|
||||
): Promise<DeleteEventTypeOutput> {
|
||||
@@ -0,0 +1,17 @@
|
||||
import { EventTypesController_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/controllers/event-types.controller";
|
||||
import { EventTypesRepository_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.repository";
|
||||
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, MembershipsModule, TokensModule, UsersModule, SelectedCalendarsModule],
|
||||
providers: [EventTypesRepository_2024_04_15, EventTypesService_2024_04_15],
|
||||
controllers: [EventTypesController_2024_04_15],
|
||||
exports: [EventTypesService_2024_04_15, EventTypesRepository_2024_04_15],
|
||||
})
|
||||
export class EventTypesModule_2024_04_15 {}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
@@ -8,12 +8,12 @@ import { getEventTypeById } from "@calcom/platform-libraries-0.0.2";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository {
|
||||
export class EventTypesRepository_2024_04_15 {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createUserEventType(
|
||||
userId: number,
|
||||
body: Pick<CreateEventTypeInput, "title" | "slug" | "length" | "hidden">
|
||||
body: Pick<CreateEventTypeInput_2024_04_15, "title" | "slug" | "length" | "hidden">
|
||||
) {
|
||||
return this.dbWrite.prisma.eventType.create({
|
||||
data: {
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
|
||||
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
|
||||
import { ApiProperty as DocsProperty, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
@@ -20,7 +20,7 @@ export const CREATE_EVENT_DESCRIPTION_EXAMPLE =
|
||||
|
||||
// note(Lauris): We will gradually expose more properties if any customer needs them.
|
||||
// Just uncomment any below when requested.
|
||||
export class CreateEventTypeInput {
|
||||
export class CreateEventTypeInput_2024_04_15 {
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
|
||||
@@ -46,9 +46,9 @@ export class CreateEventTypeInput {
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EventTypeLocation)
|
||||
@Type(() => EventTypeLocation_2024_04_15)
|
||||
@IsArray()
|
||||
locations?: EventTypeLocation[];
|
||||
locations?: EventTypeLocation_2024_04_15[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { IsNumberString } from "class-validator";
|
||||
|
||||
export class EventTypeIdParams {
|
||||
export class EventTypeIdParams_2024_04_15 {
|
||||
@IsNumberString()
|
||||
eventTypeId!: number;
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { IsString, IsNumber, IsBoolean, IsOptional, IsUrl } from "class-validato
|
||||
// note(Lauris): We will gradually expose more properties if any customer needs them.
|
||||
// Just uncomment any below when requested.
|
||||
|
||||
export class EventTypeLocation {
|
||||
export class EventTypeLocation_2024_04_15 {
|
||||
@IsString()
|
||||
@DocsProperty({ example: "link" })
|
||||
type!: string;
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsBoolean, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class GetPublicEventTypeQueryParams {
|
||||
export class GetPublicEventTypeQueryParams_2024_04_15 {
|
||||
@Transform(({ value }: { value: string }) => value === "true")
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
import { Editable } from "@/ee/event-types/inputs/enums/editable";
|
||||
import { BaseField } from "@/ee/event-types/inputs/enums/field-type";
|
||||
import { Frequency } from "@/ee/event-types/inputs/enums/frequency";
|
||||
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
|
||||
import { Editable } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/editable";
|
||||
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
|
||||
import { Frequency } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/frequency";
|
||||
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsString,
|
||||
@@ -105,7 +105,7 @@ class VariantsConfig {
|
||||
variants!: Record<string, Variant>;
|
||||
}
|
||||
|
||||
export class BookingField {
|
||||
export class BookingField_2024_04_15 {
|
||||
@IsEnum(BaseField)
|
||||
type!: BaseField;
|
||||
|
||||
@@ -179,7 +179,7 @@ export class BookingField {
|
||||
sources?: Source[];
|
||||
}
|
||||
|
||||
export class RecurringEvent {
|
||||
export class RecurringEvent_2024_04_15 {
|
||||
@IsDate()
|
||||
@IsOptional()
|
||||
dtstart?: Date;
|
||||
@@ -202,7 +202,7 @@ export class RecurringEvent {
|
||||
tzid?: string;
|
||||
}
|
||||
|
||||
export class IntervalLimits {
|
||||
export class IntervalLimits_2024_04_15 {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
PER_DAY?: number;
|
||||
@@ -220,7 +220,7 @@ export class IntervalLimits {
|
||||
PER_YEAR?: number;
|
||||
}
|
||||
|
||||
export class UpdateEventTypeInput {
|
||||
export class UpdateEventTypeInput_2024_04_15 {
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@IsOptional()
|
||||
@@ -243,9 +243,9 @@ export class UpdateEventTypeInput {
|
||||
hidden?: boolean;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EventTypeLocation)
|
||||
@Type(() => EventTypeLocation_2024_04_15)
|
||||
@IsOptional()
|
||||
locations?: EventTypeLocation[];
|
||||
locations?: EventTypeLocation_2024_04_15[];
|
||||
|
||||
// @IsInt()
|
||||
// @IsOptional()
|
||||
@@ -278,8 +278,8 @@ export class UpdateEventTypeInput {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => BookingField)
|
||||
bookingFields?: BookingField[];
|
||||
@Type(() => BookingField_2024_04_15)
|
||||
bookingFields?: BookingField_2024_04_15[];
|
||||
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
|
||||
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
CREATE_EVENT_LENGTH_EXAMPLE,
|
||||
CREATE_EVENT_SLUG_EXAMPLE,
|
||||
CREATE_EVENT_TITLE_EXAMPLE,
|
||||
} from "@/ee/event-types/inputs/create-event-type.input";
|
||||
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
+18
-18
@@ -3,15 +3,15 @@ import {
|
||||
CREATE_EVENT_LENGTH_EXAMPLE,
|
||||
CREATE_EVENT_SLUG_EXAMPLE,
|
||||
CREATE_EVENT_TITLE_EXAMPLE,
|
||||
} from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { PeriodType } from "@/ee/event-types/inputs/enums/period-type";
|
||||
import { SchedulingType } from "@/ee/event-types/inputs/enums/scheduling-type";
|
||||
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
|
||||
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { PeriodType } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/period-type";
|
||||
import { SchedulingType } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/scheduling-type";
|
||||
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
|
||||
import {
|
||||
BookingField,
|
||||
IntervalLimits,
|
||||
RecurringEvent,
|
||||
} from "@/ee/event-types/inputs/update-event-type.input";
|
||||
BookingField_2024_04_15,
|
||||
IntervalLimits_2024_04_15,
|
||||
RecurringEvent_2024_04_15,
|
||||
} from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { ApiProperty as DocsProperty, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
@@ -53,9 +53,9 @@ export class EventTypeOutput {
|
||||
hidden!: boolean;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EventTypeLocation)
|
||||
@Type(() => EventTypeLocation_2024_04_15)
|
||||
@IsArray()
|
||||
locations!: EventTypeLocation[] | null;
|
||||
locations!: EventTypeLocation_2024_04_15[] | null;
|
||||
|
||||
@IsInt()
|
||||
@ApiHideProperty()
|
||||
@@ -91,9 +91,9 @@ export class EventTypeOutput {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => BookingField)
|
||||
@Type(() => BookingField_2024_04_15)
|
||||
@ApiHideProperty()
|
||||
bookingFields!: BookingField[] | null;
|
||||
bookingFields!: BookingField_2024_04_15[] | null;
|
||||
|
||||
@IsString()
|
||||
@ApiHideProperty()
|
||||
@@ -132,10 +132,10 @@ export class EventTypeOutput {
|
||||
requiresBookerEmailVerification!: boolean;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => RecurringEvent)
|
||||
@Type(() => RecurringEvent_2024_04_15)
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
recurringEvent!: RecurringEvent | null;
|
||||
recurringEvent!: RecurringEvent_2024_04_15 | null;
|
||||
|
||||
@IsBoolean()
|
||||
@ApiHideProperty()
|
||||
@@ -203,15 +203,15 @@ export class EventTypeOutput {
|
||||
successRedirectUrl!: string | null;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => IntervalLimits)
|
||||
@Type(() => IntervalLimits_2024_04_15)
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
bookingLimits!: IntervalLimits;
|
||||
bookingLimits!: IntervalLimits_2024_04_15;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => IntervalLimits)
|
||||
@Type(() => IntervalLimits_2024_04_15)
|
||||
@ApiHideProperty()
|
||||
durationLimits!: IntervalLimits;
|
||||
durationLimits!: IntervalLimits_2024_04_15;
|
||||
|
||||
@IsBoolean()
|
||||
@ApiHideProperty()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
|
||||
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, ValidateNested } from "class-validator";
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
||||
CREATE_EVENT_LENGTH_EXAMPLE,
|
||||
CREATE_EVENT_SLUG_EXAMPLE,
|
||||
CREATE_EVENT_TITLE_EXAMPLE,
|
||||
} from "@/ee/event-types/inputs/create-event-type.input";
|
||||
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
|
||||
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsEnum, ValidateNested } from "class-validator";
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
|
||||
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
|
||||
+13
-10
@@ -1,8 +1,8 @@
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/constants/constants";
|
||||
import { EventTypesRepository } from "@/ee/event-types/event-types.repository";
|
||||
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
|
||||
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_04_15/constants/constants";
|
||||
import { EventTypesRepository_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.repository";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
@@ -14,16 +14,19 @@ import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-librarie
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesService {
|
||||
export class EventTypesService_2024_04_15 {
|
||||
constructor(
|
||||
private readonly eventTypesRepository: EventTypesRepository,
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_04_15,
|
||||
private readonly membershipsRepository: MembershipsRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
|
||||
private readonly dbWrite: PrismaWriteService
|
||||
) {}
|
||||
|
||||
async createUserEventType(user: UserWithProfile, body: CreateEventTypeInput): Promise<EventTypeOutput> {
|
||||
async createUserEventType(
|
||||
user: UserWithProfile,
|
||||
body: CreateEventTypeInput_2024_04_15
|
||||
): Promise<EventTypeOutput> {
|
||||
await this.checkCanCreateEventType(user.id, body);
|
||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||
const { eventType } = await createEventType({
|
||||
@@ -38,7 +41,7 @@ export class EventTypesService {
|
||||
return eventType as EventTypeOutput;
|
||||
}
|
||||
|
||||
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput) {
|
||||
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput_2024_04_15) {
|
||||
const existsWithSlug = await this.eventTypesRepository.getUserEventTypeBySlug(userId, body.slug);
|
||||
if (existsWithSlug) {
|
||||
throw new BadRequestException("User already has an event type with this slug.");
|
||||
@@ -114,7 +117,7 @@ export class EventTypesService {
|
||||
return defaultEventTypes;
|
||||
}
|
||||
|
||||
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput, user: UserWithProfile) {
|
||||
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_04_15, user: UserWithProfile) {
|
||||
this.checkCanUpdateEventType(user.id, eventTypeId);
|
||||
const eventTypeUser = await this.getUserToUpdateEvent(user);
|
||||
await updateEventType({
|
||||
@@ -0,0 +1,16 @@
|
||||
export const DEFAULT_EVENT_TYPES = {
|
||||
thirtyMinutes: { length: 30, slug: "thirty-minutes", title: "30 Minutes" },
|
||||
thirtyMinutesVideo: {
|
||||
length: 30,
|
||||
slug: "thirty-minutes-video",
|
||||
title: "30 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
},
|
||||
sixtyMinutes: { length: 60, slug: "sixty-minutes", title: "60 Minutes" },
|
||||
sixtyMinutesVideo: {
|
||||
length: 60,
|
||||
slug: "sixty-minutes-video",
|
||||
title: "60 Minutes",
|
||||
locations: [{ type: "integrations:daily" }],
|
||||
},
|
||||
};
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { PlatformOAuthClient, Team, User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
describe("Event types Endpoints", () => {
|
||||
describe("Not authenticated", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
|
||||
})
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it(`/GET/:id`, () => {
|
||||
return request(app.getHttpServer()).get("/api/v2/event-types/100").expect(401);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("User Authenticated", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let organization: Team;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
|
||||
const userEmail = "event-types-test-e2e@api.com";
|
||||
const name = "bob-the-builder";
|
||||
const username = name;
|
||||
let eventType: EventTypeOutput_2024_06_14;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
name,
|
||||
username,
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
async function createOAuthClient(organizationId: number) {
|
||||
const data = {
|
||||
logo: "logo-url",
|
||||
name: "name",
|
||||
redirectUris: ["redirect-uri"],
|
||||
permissions: 32,
|
||||
};
|
||||
const secret = "secret";
|
||||
|
||||
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
|
||||
return client;
|
||||
}
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(oauthClientRepositoryFixture).toBeDefined();
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(oAuthClient).toBeDefined();
|
||||
expect(user).toBeDefined();
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class",
|
||||
slug: "coding-class",
|
||||
description: "Let's learn how to code like a pro.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/api/v2/event-types")
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const createdEventType = responseBody.data;
|
||||
expect(createdEventType).toHaveProperty("id");
|
||||
expect(createdEventType.title).toEqual(body.title);
|
||||
expect(createdEventType.description).toEqual(body.description);
|
||||
expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes);
|
||||
expect(createdEventType.locations).toEqual(body.locations);
|
||||
expect(createdEventType.bookingFields).toEqual(body.bookingFields);
|
||||
expect(createdEventType.ownerId).toEqual(user.id);
|
||||
|
||||
eventType = responseBody.data;
|
||||
});
|
||||
});
|
||||
|
||||
it("should update event type", async () => {
|
||||
const newTitle = "Coding class in Italian!";
|
||||
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
title: newTitle,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/event-types/${eventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const updatedEventType = responseBody.data;
|
||||
expect(updatedEventType.title).toEqual(body.title);
|
||||
|
||||
expect(updatedEventType.id).toEqual(eventType.id);
|
||||
expect(updatedEventType.title).toEqual(newTitle);
|
||||
expect(updatedEventType.description).toEqual(eventType.description);
|
||||
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(updatedEventType.locations).toEqual(eventType.locations);
|
||||
expect(updatedEventType.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(updatedEventType.ownerId).toEqual(user.id);
|
||||
|
||||
eventType.title = newTitle;
|
||||
});
|
||||
});
|
||||
|
||||
it(`/GET/:id`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types/${eventType.id}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const fetchedEventType = responseBody.data;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(fetchedEventType.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType.ownerId).toEqual(user.id);
|
||||
});
|
||||
|
||||
it(`/GET/even-types by username`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseBody.data?.length).toEqual(1);
|
||||
|
||||
const fetchedEventType = responseBody.data?.[0];
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType?.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and eventSlug`, async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${username}&eventSlug=${eventType.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(200);
|
||||
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
|
||||
const fetchedEventType = responseBody.data[0];
|
||||
|
||||
expect(fetchedEventType?.id).toEqual(eventType.id);
|
||||
expect(fetchedEventType?.title).toEqual(eventType.title);
|
||||
expect(fetchedEventType?.description).toEqual(eventType.description);
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
});
|
||||
|
||||
it(`/GET/:id not existing`, async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types/1000`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
|
||||
.set("Authorization", `Bearer whatever`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should delete schedule", async () => {
|
||||
return request(app.getHttpServer()).delete(`/api/v2/event-types/${eventType.id}`).expect(200);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
try {
|
||||
await eventTypesRepositoryFixture.delete(eventType.id);
|
||||
} catch (e) {
|
||||
// Event type might have been deleted by the test
|
||||
}
|
||||
try {
|
||||
await userRepositoryFixture.delete(user.id);
|
||||
} catch (e) {
|
||||
// User might have been deleted by the test
|
||||
}
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import { CreateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/create-event-type.output";
|
||||
import { DeleteEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/delete-event-type.output";
|
||||
import { GetEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-type.output";
|
||||
import { GetEventTypesOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-types.output";
|
||||
import { UpdateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/update-event-type.output";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { VERSION_2024_06_14_VALUE } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import {
|
||||
Controller,
|
||||
UseGuards,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Body,
|
||||
NotFoundException,
|
||||
Patch,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Delete,
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
|
||||
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/event-types",
|
||||
version: VERSION_2024_06_14_VALUE,
|
||||
})
|
||||
@UseGuards(PermissionsGuard)
|
||||
@DocsTags("Event types")
|
||||
export class EventTypesController_2024_06_14 {
|
||||
constructor(private readonly eventTypesService: EventTypesService_2024_06_14) {}
|
||||
|
||||
@Post("/")
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async createEventType(
|
||||
@Body() body: CreateEventTypeInput_2024_06_14,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<CreateEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.createUserEventType(user, body);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/:eventTypeId")
|
||||
@Permissions([EVENT_TYPE_READ])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async getEventTypeById(
|
||||
@Param("eventTypeId") eventTypeId: string,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<GetEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.getUserEventType(user.id, Number(eventTypeId));
|
||||
|
||||
if (!eventType) {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/")
|
||||
@Permissions([EVENT_TYPE_READ])
|
||||
async getEventTypes(
|
||||
@Query() queryParams: GetEventTypesQuery_2024_06_14
|
||||
): Promise<GetEventTypesOutput_2024_06_14> {
|
||||
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventTypes,
|
||||
};
|
||||
}
|
||||
|
||||
@Patch("/:eventTypeId")
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async updateEventType(
|
||||
@Param("eventTypeId") eventTypeId: number,
|
||||
@Body() body: UpdateEventTypeInput_2024_06_14,
|
||||
@GetUser() user: UserWithProfile
|
||||
): Promise<UpdateEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: eventType,
|
||||
};
|
||||
}
|
||||
|
||||
@Delete("/:eventTypeId")
|
||||
@Permissions([EVENT_TYPE_WRITE])
|
||||
@UseGuards(ApiAuthGuard)
|
||||
async deleteEventType(
|
||||
@Param("eventTypeId") eventTypeId: number,
|
||||
@GetUser("id") userId: number
|
||||
): Promise<DeleteEventTypeOutput_2024_06_14> {
|
||||
const eventType = await this.eventTypesService.deleteEventType(eventTypeId, userId);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: {
|
||||
id: eventType.id,
|
||||
lengthInMinutes: eventType.length,
|
||||
slug: eventType.slug,
|
||||
title: eventType.title,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { EventTypesController_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/controllers/event-types.controller";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, MembershipsModule, TokensModule, SelectedCalendarsModule],
|
||||
providers: [
|
||||
EventTypesRepository_2024_06_14,
|
||||
EventTypesService_2024_06_14,
|
||||
InputEventTypesService_2024_06_14,
|
||||
OutputEventTypesService_2024_06_14,
|
||||
UsersRepository,
|
||||
UsersService,
|
||||
],
|
||||
controllers: [EventTypesController_2024_06_14],
|
||||
exports: [EventTypesService_2024_06_14, EventTypesRepository_2024_06_14],
|
||||
})
|
||||
export class EventTypesModule_2024_06_14 {}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
getEventTypeById,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeLocations,
|
||||
} from "@calcom/platform-libraries-0.0.13";
|
||||
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
type InputEventTransformed = Omit<
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
"lengthInMinutes" | "locations" | "bookingFields"
|
||||
> & {
|
||||
length: number;
|
||||
slug: string;
|
||||
locations?: ReturnType<typeof transformApiEventTypeLocations>;
|
||||
bookingFields?: ReturnType<typeof transformApiEventTypeBookingFields>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository_2024_06_14 {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createUserEventType(userId: number, body: InputEventTransformed) {
|
||||
return this.dbWrite.prisma.eventType.create({
|
||||
data: {
|
||||
...body,
|
||||
userId,
|
||||
locations: body.locations,
|
||||
bookingFields: body.bookingFields,
|
||||
users: { connect: { id: userId } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeWithSeats(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
select: { users: { select: { id: true } }, seatsPerTimeSlot: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventType(userId: number, eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findFirst({
|
||||
where: {
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventTypes(userId: number) {
|
||||
return this.dbRead.prisma.eventType.findMany({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventTypeForAtom(
|
||||
user: UserWithProfile,
|
||||
isUserOrganizationAdmin: boolean,
|
||||
eventTypeId: number
|
||||
) {
|
||||
return await getEventTypeById({
|
||||
currentOrganizationId: user.movedToProfile?.organizationId || user.organizationId,
|
||||
eventTypeId,
|
||||
userId: user.id,
|
||||
prisma: this.dbRead.prisma as unknown as PrismaClient,
|
||||
isUserOrganizationAdmin,
|
||||
isTrpcCall: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeById(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: { users: true, schedule: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventTypeBySlug(userId: number, slug: string) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: {
|
||||
userId_slug: {
|
||||
userId: userId,
|
||||
slug: slug,
|
||||
},
|
||||
},
|
||||
include: { users: true, schedule: true },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEventType(eventTypeId: number) {
|
||||
return this.dbWrite.prisma.eventType.delete({ where: { id: eventTypeId } });
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsIn, IsNotEmptyObject, ValidateNested } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export class CreateEventTypeOutput_2024_06_14 {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: EventTypeOutput_2024_06_14,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@ValidateNested()
|
||||
@Type(() => EventTypeOutput_2024_06_14)
|
||||
data!: EventTypeOutput_2024_06_14;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsIn, IsInt, IsString } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { CREATE_EVENT_LENGTH_EXAMPLE, CREATE_EVENT_TITLE_EXAMPLE } from "@calcom/platform-types";
|
||||
|
||||
class DeleteData_2024_06_14 {
|
||||
@IsInt()
|
||||
@DocsProperty({ example: 1 })
|
||||
id!: number;
|
||||
|
||||
@IsInt()
|
||||
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
|
||||
lengthInMinutes!: number;
|
||||
|
||||
@IsString()
|
||||
slug!: string;
|
||||
|
||||
@IsString()
|
||||
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
|
||||
title!: string;
|
||||
}
|
||||
|
||||
export class DeleteEventTypeOutput_2024_06_14 {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Type(() => DeleteData_2024_06_14)
|
||||
data!: DeleteData_2024_06_14;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsIn, ValidateNested } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export class GetEventTypeOutput_2024_06_14 {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: EventTypeOutput_2024_06_14,
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => EventTypeOutput_2024_06_14)
|
||||
data!: EventTypeOutput_2024_06_14 | null;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsIn, ValidateNested } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export class GetEventTypesOutput_2024_06_14 {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EventTypeOutput_2024_06_14)
|
||||
@IsArray()
|
||||
data!: EventTypeOutput_2024_06_14[];
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsIn, IsNotEmptyObject, ValidateNested } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export class UpdateEventTypeOutput_2024_06_14 {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({
|
||||
type: EventTypeOutput_2024_06_14,
|
||||
})
|
||||
@IsNotEmptyObject()
|
||||
@ValidateNested()
|
||||
@Type(() => EventTypeOutput_2024_06_14)
|
||||
data!: EventTypeOutput_2024_06_14;
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_06_14/constants/constants";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
|
||||
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { createEventType, updateEventType } from "@calcom/platform-libraries";
|
||||
import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-libraries";
|
||||
import { dynamicEvent } from "@calcom/platform-libraries-0.0.13";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesService_2024_06_14 {
|
||||
constructor(
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly inputEventTypesService: InputEventTypesService_2024_06_14,
|
||||
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14,
|
||||
private readonly membershipsRepository: MembershipsRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
|
||||
private readonly dbWrite: PrismaWriteService
|
||||
) {}
|
||||
|
||||
async createUserEventType(user: UserWithProfile, body: CreateEventTypeInput_2024_06_14) {
|
||||
await this.checkCanCreateEventType(user.id, body);
|
||||
const eventTypeUser = await this.getUserToCreateEvent(user);
|
||||
const bodyTransformed = this.inputEventTypesService.transformInputCreateEventType(body);
|
||||
const { eventType: eventTypeCreated } = await createEventType({
|
||||
input: bodyTransformed,
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prisma: this.dbWrite.prisma,
|
||||
},
|
||||
});
|
||||
|
||||
const eventType = await this.eventTypesRepository.getEventTypeById(eventTypeCreated.id);
|
||||
|
||||
if (!eventType) {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeCreated.id} not found`);
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
}
|
||||
|
||||
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput_2024_06_14) {
|
||||
const existsWithSlug = await this.eventTypesRepository.getUserEventTypeBySlug(userId, body.slug);
|
||||
if (existsWithSlug) {
|
||||
throw new BadRequestException("User already has an event type with this slug.");
|
||||
}
|
||||
}
|
||||
|
||||
async getEventTypeByUsernameAndSlug(username: string, eventTypeSlug: string) {
|
||||
const user = await this.usersRepository.findByUsername(username);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const eventType = await this.eventTypesRepository.getUserEventTypeBySlug(user.id, eventTypeSlug);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
}
|
||||
|
||||
async getEventTypesByUsername(username: string) {
|
||||
const user = await this.usersRepository.findByUsername(username);
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
return this.getUserEventTypes(user.id);
|
||||
}
|
||||
|
||||
async getUserToCreateEvent(user: UserWithProfile) {
|
||||
const organizationId = user.movedToProfile?.organizationId || user.organizationId;
|
||||
const isOrgAdmin = organizationId
|
||||
? await this.membershipsRepository.isUserOrganizationAdmin(user.id, organizationId)
|
||||
: false;
|
||||
const profileId = user.movedToProfile?.id || null;
|
||||
return {
|
||||
id: user.id,
|
||||
organizationId: user.organizationId,
|
||||
organization: { isOrgAdmin },
|
||||
profile: { id: profileId },
|
||||
metadata: user.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserEventType(userId: number, eventTypeId: number) {
|
||||
const eventType = await this.eventTypesRepository.getUserEventType(userId, eventTypeId);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.checkUserOwnsEventType(userId, eventType);
|
||||
return this.outputEventTypesService.getResponseEventType(userId, eventType);
|
||||
}
|
||||
|
||||
async getUserEventTypes(userId: number) {
|
||||
const eventTypes = await this.eventTypesRepository.getUserEventTypes(userId);
|
||||
|
||||
const eventTypePromises = eventTypes.map(async (eventType) => {
|
||||
return await this.outputEventTypesService.getResponseEventType(userId, eventType);
|
||||
});
|
||||
|
||||
return await Promise.all(eventTypePromises);
|
||||
}
|
||||
|
||||
async getUserEventTypeForAtom(user: UserWithProfile, eventTypeId: number) {
|
||||
const organizationId = user.movedToProfile?.organizationId || user.organizationId;
|
||||
|
||||
const isUserOrganizationAdmin = organizationId
|
||||
? await this.membershipsRepository.isUserOrganizationAdmin(user.id, organizationId)
|
||||
: false;
|
||||
|
||||
const eventType = await this.eventTypesRepository.getUserEventTypeForAtom(
|
||||
user,
|
||||
isUserOrganizationAdmin,
|
||||
eventTypeId
|
||||
);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.checkUserOwnsEventType(user.id, eventType.eventType);
|
||||
return eventType;
|
||||
}
|
||||
|
||||
async getEventTypesPublicByUsername(username: string): Promise<EventTypesPublic> {
|
||||
const user = await this.usersRepository.findByUsername(username);
|
||||
if (!user) {
|
||||
throw new NotFoundException(`User with username "${username}" not found`);
|
||||
}
|
||||
|
||||
return await getEventTypesPublic(user.id);
|
||||
}
|
||||
|
||||
async getEventTypes(queryParams: GetEventTypesQuery_2024_06_14): Promise<EventTypeOutput_2024_06_14[]> {
|
||||
const { username, eventSlug, usernames } = queryParams;
|
||||
|
||||
if (username && eventSlug) {
|
||||
const eventType = await this.getEventTypeByUsernameAndSlug(username, eventSlug);
|
||||
return eventType ? [eventType] : [];
|
||||
}
|
||||
|
||||
if (username) {
|
||||
return await this.getEventTypesByUsername(username);
|
||||
}
|
||||
|
||||
if (usernames) {
|
||||
const dynamicEventType = await this.getDynamicEventType(usernames);
|
||||
return [dynamicEventType];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDynamicEventType(usernames: string[]) {
|
||||
const users = await this.usersService.getByUsernames(usernames);
|
||||
const usersFiltered: UserWithProfile[] = [];
|
||||
for (const user of users) {
|
||||
if (user) {
|
||||
usersFiltered.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(0, {
|
||||
...dynamicEvent,
|
||||
users: usersFiltered,
|
||||
isInstantEvent: false,
|
||||
});
|
||||
}
|
||||
|
||||
async createUserDefaultEventTypes(userId: number) {
|
||||
const { sixtyMinutes, sixtyMinutesVideo, thirtyMinutes, thirtyMinutesVideo } = DEFAULT_EVENT_TYPES;
|
||||
|
||||
const defaultEventTypes = await Promise.all([
|
||||
this.eventTypesRepository.createUserEventType(userId, thirtyMinutes),
|
||||
this.eventTypesRepository.createUserEventType(userId, sixtyMinutes),
|
||||
this.eventTypesRepository.createUserEventType(userId, thirtyMinutesVideo),
|
||||
this.eventTypesRepository.createUserEventType(userId, sixtyMinutesVideo),
|
||||
]);
|
||||
|
||||
return defaultEventTypes;
|
||||
}
|
||||
|
||||
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_06_14, user: UserWithProfile) {
|
||||
this.checkCanUpdateEventType(user.id, eventTypeId);
|
||||
const eventTypeUser = await this.getUserToUpdateEvent(user);
|
||||
const bodyTransformed = this.inputEventTypesService.transformInputUpdateEventType(body);
|
||||
await updateEventType({
|
||||
input: { id: eventTypeId, ...bodyTransformed },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prisma: this.dbWrite.prisma,
|
||||
},
|
||||
});
|
||||
|
||||
const eventType = await this.eventTypesRepository.getEventTypeById(eventTypeId);
|
||||
|
||||
if (!eventType) {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
|
||||
return this.outputEventTypesService.getResponseEventType(user.id, eventType);
|
||||
}
|
||||
|
||||
async checkCanUpdateEventType(userId: number, eventTypeId: number) {
|
||||
const existingEventType = await this.getUserEventType(userId, eventTypeId);
|
||||
if (!existingEventType) {
|
||||
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
|
||||
}
|
||||
this.checkUserOwnsEventType(userId, { id: eventTypeId, userId: existingEventType.ownerId });
|
||||
}
|
||||
|
||||
async getUserToUpdateEvent(user: UserWithProfile) {
|
||||
const profileId = user.movedToProfile?.id || null;
|
||||
const selectedCalendars = await this.selectedCalendarsRepository.getUserSelectedCalendars(user.id);
|
||||
return { ...user, profile: { id: profileId }, selectedCalendars };
|
||||
}
|
||||
|
||||
async deleteEventType(eventTypeId: number, userId: number) {
|
||||
const existingEventType = await this.eventTypesRepository.getEventTypeById(eventTypeId);
|
||||
if (!existingEventType) {
|
||||
throw new NotFoundException(`Event type with ID=${eventTypeId} does not exist.`);
|
||||
}
|
||||
|
||||
this.checkUserOwnsEventType(userId, existingEventType);
|
||||
|
||||
return this.eventTypesRepository.deleteEventType(eventTypeId);
|
||||
}
|
||||
|
||||
checkUserOwnsEventType(userId: number, eventType: Pick<EventType, "id" | "userId">) {
|
||||
if (userId !== eventType.userId) {
|
||||
throw new ForbiddenException(`User with ID=${userId} does not own event type with ID=${eventType.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeLocations,
|
||||
} from "@calcom/platform-libraries-0.0.13";
|
||||
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class InputEventTypesService_2024_06_14 {
|
||||
transformInputCreateEventType(inputEventType: CreateEventTypeInput_2024_06_14) {
|
||||
const defaultLocations: CreateEventTypeInput_2024_06_14["locations"] = [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
];
|
||||
|
||||
const { lengthInMinutes, locations, bookingFields, ...rest } = inputEventType;
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: this.transformInputLocations(locations || defaultLocations),
|
||||
bookingFields: this.transformInputBookingFields(bookingFields),
|
||||
};
|
||||
|
||||
return eventType;
|
||||
}
|
||||
|
||||
transformInputUpdateEventType(inputEventType: UpdateEventTypeInput_2024_06_14) {
|
||||
const { locations, bookingFields, ...rest } = inputEventType;
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
locations: locations ? this.transformInputLocations(locations) : undefined,
|
||||
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
|
||||
};
|
||||
|
||||
return eventType;
|
||||
}
|
||||
|
||||
transformInputLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
|
||||
return transformApiEventTypeLocations(inputLocations);
|
||||
}
|
||||
|
||||
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
|
||||
return transformApiEventTypeBookingFields(inputBookingFields);
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import type { EventType, User, Schedule } from "@prisma/client";
|
||||
|
||||
import {
|
||||
EventTypeMetaDataSchema,
|
||||
userMetadata,
|
||||
getResponseEventTypeLocations,
|
||||
getResponseEventTypeBookingFields,
|
||||
parseRecurringEvent,
|
||||
TransformedLocationsSchema,
|
||||
BookingFieldsSchema,
|
||||
} from "@calcom/platform-libraries-0.0.13";
|
||||
|
||||
type EventTypeRelations = { users: User[]; schedule: Schedule | null };
|
||||
type DatabaseEventType = EventType & EventTypeRelations;
|
||||
|
||||
type Input = Pick<
|
||||
DatabaseEventType,
|
||||
| "id"
|
||||
| "length"
|
||||
| "title"
|
||||
| "description"
|
||||
| "disableGuests"
|
||||
| "slotInterval"
|
||||
| "minimumBookingNotice"
|
||||
| "beforeEventBuffer"
|
||||
| "afterEventBuffer"
|
||||
| "slug"
|
||||
| "schedulingType"
|
||||
| "requiresConfirmation"
|
||||
| "price"
|
||||
| "currency"
|
||||
| "lockTimeZoneToggleOnBookingPage"
|
||||
| "seatsPerTimeSlot"
|
||||
| "forwardParamsSuccessRedirect"
|
||||
| "successRedirectUrl"
|
||||
| "seatsShowAvailabilityCount"
|
||||
| "isInstantEvent"
|
||||
| "locations"
|
||||
| "bookingFields"
|
||||
| "recurringEvent"
|
||||
| "metadata"
|
||||
| "users"
|
||||
| "schedule"
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class OutputEventTypesService_2024_06_14 {
|
||||
async getResponseEventType(ownerId: number, databaseEventType: Input) {
|
||||
const {
|
||||
id,
|
||||
length,
|
||||
title,
|
||||
description,
|
||||
disableGuests,
|
||||
slotInterval,
|
||||
minimumBookingNotice,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
slug,
|
||||
schedulingType,
|
||||
requiresConfirmation,
|
||||
price,
|
||||
currency,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
seatsPerTimeSlot,
|
||||
forwardParamsSuccessRedirect,
|
||||
successRedirectUrl,
|
||||
seatsShowAvailabilityCount,
|
||||
isInstantEvent,
|
||||
} = databaseEventType;
|
||||
|
||||
const locations = this.transformLocations(databaseEventType.locations);
|
||||
const bookingFields = this.transformBookingFields(databaseEventType.bookingFields);
|
||||
const recurringEvent = this.transformRecurringEvent(databaseEventType.recurringEvent);
|
||||
const metadata = this.transformMetadata(databaseEventType.metadata) || {};
|
||||
const schedule = await this.getSchedule(databaseEventType);
|
||||
const users = this.transformUsers(databaseEventType.users);
|
||||
|
||||
return {
|
||||
id,
|
||||
ownerId,
|
||||
lengthInMinutes: length,
|
||||
title,
|
||||
slug,
|
||||
description: description || "",
|
||||
locations,
|
||||
bookingFields,
|
||||
recurringEvent,
|
||||
disableGuests,
|
||||
slotInterval,
|
||||
minimumBookingNotice,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
schedulingType,
|
||||
metadata,
|
||||
requiresConfirmation,
|
||||
price,
|
||||
currency,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
seatsPerTimeSlot,
|
||||
forwardParamsSuccessRedirect,
|
||||
successRedirectUrl,
|
||||
seatsShowAvailabilityCount,
|
||||
isInstantEvent,
|
||||
users,
|
||||
schedule,
|
||||
};
|
||||
}
|
||||
|
||||
transformLocations(locations: any) {
|
||||
if (!locations) return [];
|
||||
return getResponseEventTypeLocations(TransformedLocationsSchema.parse(locations));
|
||||
}
|
||||
|
||||
transformBookingFields(inputBookingFields: any) {
|
||||
if (!inputBookingFields) return [];
|
||||
return getResponseEventTypeBookingFields(BookingFieldsSchema.parse(inputBookingFields));
|
||||
}
|
||||
|
||||
transformRecurringEvent(recurringEvent: any) {
|
||||
if (!recurringEvent) return null;
|
||||
return parseRecurringEvent(recurringEvent);
|
||||
}
|
||||
|
||||
transformMetadata(metadata: any) {
|
||||
if (!metadata) return {};
|
||||
return EventTypeMetaDataSchema.parse(metadata);
|
||||
}
|
||||
|
||||
async getSchedule(databaseEventType: Input) {
|
||||
return databaseEventType.schedule || null;
|
||||
}
|
||||
|
||||
transformUsers(users: User[]) {
|
||||
return users.map((user) => {
|
||||
const metadata = user.metadata ? userMetadata.parse(user.metadata) : {};
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
avatarUrl: user.avatarUrl,
|
||||
brandColor: user.brandColor,
|
||||
darkBrandColor: user.darkBrandColor,
|
||||
weekStart: user.weekStart,
|
||||
metadata: metadata || {},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BookingsModule } from "@/ee/bookings/bookings.module";
|
||||
import { CalendarsModule } from "@/ee/calendars/calendars.module";
|
||||
import { EventTypesModule } from "@/ee/event-types/event-types.module";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { GcalModule } from "@/ee/gcal/gcal.module";
|
||||
import { MeModule } from "@/ee/me/me.module";
|
||||
import { ProviderModule } from "@/ee/provider/provider.module";
|
||||
@@ -17,7 +18,8 @@ import { Module } from "@nestjs/common";
|
||||
SchedulesModule_2024_04_15,
|
||||
SchedulesModule_2024_06_11,
|
||||
MeModule,
|
||||
EventTypesModule,
|
||||
EventTypesModule_2024_04_15,
|
||||
EventTypesModule_2024_06_14,
|
||||
CalendarsModule,
|
||||
BookingsModule,
|
||||
SlotsModule,
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service";
|
||||
import { VERSION_2024_06_11_VALUE } from "@/lib/api-versions";
|
||||
import { VERSION_2024_06_14, VERSION_2024_06_11 } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
|
||||
@Controller({
|
||||
path: "/v2/schedules",
|
||||
version: VERSION_2024_06_11_VALUE,
|
||||
version: [VERSION_2024_06_14, VERSION_2024_06_11],
|
||||
})
|
||||
@UseGuards(ApiAuthGuard, PermissionsGuard)
|
||||
@DocsTags("Schedules")
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { VersionValue } from "@nestjs/common/interfaces";
|
||||
|
||||
import { API_VERSIONS, VERSION_2024_04_15, VERSION_2024_06_11 } from "@calcom/platform-constants";
|
||||
import {
|
||||
API_VERSIONS,
|
||||
VERSION_2024_04_15,
|
||||
VERSION_2024_06_11,
|
||||
VERSION_2024_06_14,
|
||||
} from "@calcom/platform-constants";
|
||||
|
||||
export const API_VERSIONS_VALUES: VersionValue = API_VERSIONS as unknown as VersionValue;
|
||||
export const VERSION_2024_06_14_VALUE: VersionValue = VERSION_2024_06_14 as unknown as VersionValue;
|
||||
export const VERSION_2024_06_11_VALUE: VersionValue = VERSION_2024_06_11 as unknown as VersionValue;
|
||||
export const VERSION_2024_04_15_VALUE: VersionValue = VERSION_2024_04_15 as unknown as VersionValue;
|
||||
|
||||
export { VERSION_2024_06_11 };
|
||||
export { VERSION_2024_06_14 };
|
||||
|
||||
@@ -5,8 +5,10 @@ import { TimezoneModule } from "@/modules/timezones/timezones.module";
|
||||
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { UsersModule } from "./users/users.module";
|
||||
|
||||
@Module({
|
||||
imports: [OAuthClientModule, BillingModule, PlatformEndpointsModule, TimezoneModule],
|
||||
imports: [OAuthClientModule, BillingModule, PlatformEndpointsModule, TimezoneModule, UsersModule],
|
||||
})
|
||||
export class EndpointsModule implements NestModule {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/constants/constants";
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_04_15/constants/constants";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventTypesModule } from "@/ee/event-types/event-types.module";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
@@ -28,7 +28,7 @@ import { Global, Module } from "@nestjs/common";
|
||||
UsersModule,
|
||||
TokensModule,
|
||||
MembershipsModule,
|
||||
EventTypesModule,
|
||||
EventTypesModule_2024_04_15,
|
||||
OrganizationsModule,
|
||||
StripeModule,
|
||||
BillingModule,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
|
||||
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { TokensRepository } from "@/modules/tokens/tokens.repository";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
@@ -14,7 +14,7 @@ export class OAuthClientUsersService {
|
||||
constructor(
|
||||
private readonly userRepository: UsersRepository,
|
||||
private readonly tokensRepository: TokensRepository,
|
||||
private readonly eventTypesService: EventTypesService,
|
||||
private readonly eventTypesService: EventTypesService_2024_04_15,
|
||||
private readonly schedulesService: SchedulesService_2024_04_15
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { SlotsService } from "@/modules/slots/services/slots.service";
|
||||
import { Query, Body, Controller, Get, Delete, Post, Req, Res, UseGuards } from "@nestjs/common";
|
||||
import { Query, Body, Controller, Get, Delete, Post, Req, Res } from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { Response as ExpressResponse, Request as ExpressRequest } from "express";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventTypesRepository } from "@/ee/event-types/event-types.repository";
|
||||
import { EventTypesRepository_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.repository";
|
||||
import { SlotsRepository } from "@/modules/slots/slots.repository";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { v4 as uuid } from "uuid";
|
||||
@@ -8,7 +8,7 @@ import { ReserveSlotInput } from "@calcom/platform-types";
|
||||
@Injectable()
|
||||
export class SlotsService {
|
||||
constructor(
|
||||
private readonly eventTypeRepo: EventTypesRepository,
|
||||
private readonly eventTypeRepo: EventTypesRepository_2024_04_15,
|
||||
private readonly slotsRepo: SlotsRepository
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventTypesModule } from "@/ee/event-types/event-types.module";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { SlotsController } from "@/modules/slots/controllers/slots.controller";
|
||||
import { SlotsService } from "@/modules/slots/services/slots.service";
|
||||
@@ -6,7 +6,7 @@ import { SlotsRepository } from "@/modules/slots/slots.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, EventTypesModule],
|
||||
imports: [PrismaModule, EventTypesModule_2024_04_15],
|
||||
providers: [SlotsRepository, SlotsService],
|
||||
controllers: [SlotsController],
|
||||
exports: [SlotsService],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { User } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private readonly usersRepository: UsersRepository) {}
|
||||
|
||||
async getByUsernames(usernames: string[]) {
|
||||
const users = await Promise.all(
|
||||
usernames.map((username) => this.usersRepository.findByUsername(username))
|
||||
);
|
||||
const usersFiltered: User[] = [];
|
||||
|
||||
for (const user of users) {
|
||||
if (user) {
|
||||
usersFiltered.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [UsersRepository],
|
||||
exports: [UsersRepository],
|
||||
imports: [PrismaModule, EventTypesModule_2024_06_14, TokensModule],
|
||||
providers: [UsersRepository, UsersService],
|
||||
exports: [UsersRepository, UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
||||
@@ -577,14 +577,14 @@
|
||||
},
|
||||
"/v2/event-types": {
|
||||
"post": {
|
||||
"operationId": "EventTypesController_createEventType",
|
||||
"operationId": "EventTypesController_2024_04_15_createEventType",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateEventTypeInput"
|
||||
"$ref": "#/components/schemas/CreateEventTypeInput_2024_04_15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,7 +606,7 @@
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "EventTypesController_getEventTypes",
|
||||
"operationId": "EventTypesController_2024_04_15_getEventTypes",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -627,7 +627,7 @@
|
||||
},
|
||||
"/v2/event-types/{eventTypeId}": {
|
||||
"get": {
|
||||
"operationId": "EventTypesController_getEventType",
|
||||
"operationId": "EventTypesController_2024_04_15_getEventType",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "eventTypeId",
|
||||
@@ -655,7 +655,7 @@
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "EventTypesController_updateEventType",
|
||||
"operationId": "EventTypesController_2024_04_15_updateEventType",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "eventTypeId",
|
||||
@@ -671,7 +671,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateEventTypeInput"
|
||||
"$ref": "#/components/schemas/UpdateEventTypeInput_2024_04_15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -693,7 +693,7 @@
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "EventTypesController_deleteEventType",
|
||||
"operationId": "EventTypesController_2024_04_15_deleteEventType",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "eventTypeId",
|
||||
@@ -723,7 +723,7 @@
|
||||
},
|
||||
"/v2/event-types/{username}/{eventSlug}/public": {
|
||||
"get": {
|
||||
"operationId": "EventTypesController_getPublicEventType",
|
||||
"operationId": "EventTypesController_2024_04_15_getPublicEventType",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
@@ -778,7 +778,7 @@
|
||||
},
|
||||
"/v2/event-types/{username}/public": {
|
||||
"get": {
|
||||
"operationId": "EventTypesController_getPublicEventTypes",
|
||||
"operationId": "EventTypesController_2024_04_15_getPublicEventTypes",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "username",
|
||||
@@ -2146,7 +2146,179 @@
|
||||
"refreshToken"
|
||||
]
|
||||
},
|
||||
"EventTypeLocation": {
|
||||
"CreateEventTypeInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lengthInMinutes": {
|
||||
"type": "number",
|
||||
"example": 60
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Learn the secrets of masterchief!"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Discover the culinary wonders of the Argentina by making the best flan ever!"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lengthInMinutes",
|
||||
"title",
|
||||
"description"
|
||||
]
|
||||
},
|
||||
"EventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"example": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"CreateEventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"GetEventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"nullable": true,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"GetEventTypesOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"UpdateEventTypeInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"UpdateEventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"DeleteData_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"example": 1
|
||||
},
|
||||
"lengthInMinutes": {
|
||||
"type": "number",
|
||||
"example": 60
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Learn the secrets of masterchief!"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"lengthInMinutes",
|
||||
"title",
|
||||
"slug"
|
||||
]
|
||||
},
|
||||
"DeleteEventTypeOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/DeleteData_2024_06_14"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"EventTypeLocation_2024_04_15": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -2162,7 +2334,7 @@
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"CreateEventTypeInput": {
|
||||
"CreateEventTypeInput_2024_04_15": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"length": {
|
||||
@@ -2185,7 +2357,7 @@
|
||||
"locations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EventTypeLocation"
|
||||
"$ref": "#/components/schemas/EventTypeLocation_2024_04_15"
|
||||
}
|
||||
},
|
||||
"disableGuests": {
|
||||
@@ -2242,7 +2414,7 @@
|
||||
"nullable": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EventTypeLocation"
|
||||
"$ref": "#/components/schemas/EventTypeLocation_2024_04_15"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2864,7 +3036,143 @@
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"UpdateEventTypeInput": {
|
||||
"Option": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"value",
|
||||
"label"
|
||||
]
|
||||
},
|
||||
"VariantsConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variants": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"variants"
|
||||
]
|
||||
},
|
||||
"View": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"label"
|
||||
]
|
||||
},
|
||||
"BookingField_2024_04_15": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"number",
|
||||
"boolean",
|
||||
"address",
|
||||
"name",
|
||||
"text",
|
||||
"textarea",
|
||||
"email",
|
||||
"phone",
|
||||
"multiemail",
|
||||
"select",
|
||||
"multiselect",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"radioInput"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Option"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"labelAsSafeHtml": {
|
||||
"type": "string"
|
||||
},
|
||||
"defaultLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"getOptionsAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"optionsInputs": {
|
||||
"type": "object"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string"
|
||||
},
|
||||
"variantsConfig": {
|
||||
"$ref": "#/components/schemas/VariantsConfig"
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/View"
|
||||
}
|
||||
},
|
||||
"hideWhenJustOneOption": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"editable": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system",
|
||||
"system-but-optional",
|
||||
"system-but-hidden",
|
||||
"user",
|
||||
"user-readonly"
|
||||
]
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Source"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"UpdateEventTypeInput_2024_04_15": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"length": {
|
||||
@@ -2886,13 +3194,13 @@
|
||||
"locations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/EventTypeLocation"
|
||||
"$ref": "#/components/schemas/EventTypeLocation_2024_04_15"
|
||||
}
|
||||
},
|
||||
"bookingFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/BookingField"
|
||||
"$ref": "#/components/schemas/BookingField_2024_04_15"
|
||||
}
|
||||
},
|
||||
"disableGuests": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { TestingModule } from "@nestjs/testing";
|
||||
@@ -21,7 +21,10 @@ export class EventTypesRepositoryFixture {
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: Pick<CreateEventTypeInput, "title" | "slug" | "length" | "hidden">, userId: number) {
|
||||
async create(
|
||||
data: Pick<CreateEventTypeInput_2024_04_15, "title" | "slug" | "length" | "hidden">,
|
||||
userId: number
|
||||
) {
|
||||
return this.prismaWriteClient.eventType.create({
|
||||
data: {
|
||||
...data,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { EventTypeModel } from "@calcom/prisma/zod";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export type EventTypeApps = NonNullable<NonNullable<z.infer<typeof EventTypeMetaDataSchema>>["apps"]>;
|
||||
export type EventTypeAppsList = keyof EventTypeApps;
|
||||
|
||||
export const getEventTypeAppData = <T extends EventTypeAppsList>(
|
||||
eventType: Pick<z.infer<typeof EventTypeModel>, "price" | "currency" | "metadata">,
|
||||
eventType: Pick<BookerEvent, "price" | "currency" | "metadata">,
|
||||
appId: T,
|
||||
forcedGet?: boolean
|
||||
): EventTypeApps[T] => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRef } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
|
||||
import { useSlotsForAvailableDates } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate";
|
||||
import { classNames } from "@calcom/lib";
|
||||
@@ -19,7 +19,9 @@ type AvailableTimeSlotsProps = {
|
||||
isLoading: boolean;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
showAvailableSeatsCount?: boolean | null;
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?: Pick<BookerEvent, "length"> | null;
|
||||
};
|
||||
customClassNames?: {
|
||||
availableTimeSlotsContainer?: string;
|
||||
availableTimeSlotsTitle?: string;
|
||||
|
||||
@@ -4,13 +4,13 @@ import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { FieldError } from "react-hook-form";
|
||||
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { IS_CALCOM, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Alert, Button, EmptyScreen, Form } from "@calcom/ui";
|
||||
|
||||
import { useBookerStore } from "../../store";
|
||||
import type { useEventReturnType } from "../../utils/event";
|
||||
import type { UseBookingFormReturnType } from "../hooks/useBookingForm";
|
||||
import type { IUseBookingErrors, IUseBookingLoadingStates } from "../hooks/useBookings";
|
||||
import { BookingFields } from "./BookingFields";
|
||||
@@ -43,7 +43,11 @@ export const BookEventForm = ({
|
||||
extraOptions,
|
||||
isPlatform = false,
|
||||
}: Omit<BookEventFormProps, "event"> & {
|
||||
eventQuery: useEventReturnType;
|
||||
eventQuery: {
|
||||
isError: boolean;
|
||||
isPending: boolean;
|
||||
data?: Pick<BookerEvent, "price" | "currency" | "metadata" | "bookingFields" | "locations"> | null;
|
||||
};
|
||||
rescheduleUid: string | null;
|
||||
}) => {
|
||||
const eventType = eventQuery.data;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { useIsPlatform, useGetEventTypeById } from "@calcom/atoms/monorepo";
|
||||
import { useEventTypeById, useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Badge, Dialog, DialogContent } from "@calcom/ui";
|
||||
|
||||
@@ -25,14 +25,10 @@ const PlatformBookEventFormWrapper = ({
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const eventId = useBookerStore((state) => state.eventId);
|
||||
const { data } = useGetEventTypeById(eventId);
|
||||
const { data } = useEventTypeById(eventId);
|
||||
|
||||
return (
|
||||
<BookEventFormWrapperComponent
|
||||
child={children}
|
||||
eventLength={data?.eventType.length}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
<BookEventFormWrapperComponent child={children} eventLength={data?.lengthInMinutes} onCancel={onCancel} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ import { default as DatePickerComponent } from "@calcom/features/calendars/DateP
|
||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
|
||||
import { weekdayToWeekIndex } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
import { useBookerStore } from "../store";
|
||||
import type { useEventReturnType, useScheduleForEventReturnType } from "../utils/event";
|
||||
import type { useScheduleForEventReturnType } from "../utils/event";
|
||||
|
||||
export const DatePicker = ({
|
||||
event,
|
||||
@@ -16,7 +17,9 @@ export const DatePicker = ({
|
||||
classNames,
|
||||
scrollToTimeSlots,
|
||||
}: {
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?: { users: Pick<User, "weekStart">[] } | null;
|
||||
};
|
||||
schedule: useScheduleForEventReturnType;
|
||||
classNames?: {
|
||||
datePickerContainer?: string;
|
||||
|
||||
@@ -9,12 +9,12 @@ import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calc
|
||||
import { SeatsAvailabilityText } from "@calcom/features/bookings/components/SeatsAvailabilityText";
|
||||
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { fadeInUp } from "../config";
|
||||
import { useBookerStore } from "../store";
|
||||
import { FromToTime } from "../utils/dates";
|
||||
import type { useEventReturnType } from "../utils/event";
|
||||
|
||||
const WebTimezoneSelect = dynamic(
|
||||
() => import("@calcom/ui/components/form/timezone-select/TimezoneSelect").then((mod) => mod.TimezoneSelect),
|
||||
@@ -29,8 +29,27 @@ export const EventMeta = ({
|
||||
isPlatform = true,
|
||||
classNames,
|
||||
}: {
|
||||
event: useEventReturnType["data"];
|
||||
isPending: useEventReturnType["isPending"];
|
||||
event?: Pick<
|
||||
BookerEvent,
|
||||
| "lockTimeZoneToggleOnBookingPage"
|
||||
| "schedule"
|
||||
| "seatsPerTimeSlot"
|
||||
| "users"
|
||||
| "length"
|
||||
| "schedulingType"
|
||||
| "profile"
|
||||
| "entity"
|
||||
| "description"
|
||||
| "title"
|
||||
| "metadata"
|
||||
| "locations"
|
||||
| "currency"
|
||||
| "requiresConfirmation"
|
||||
| "recurringEvent"
|
||||
| "price"
|
||||
| "isDynamic"
|
||||
> | null;
|
||||
isPending: boolean;
|
||||
isPlatform?: boolean;
|
||||
classNames?: {
|
||||
eventMetaContainer?: string;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Button, UserAvatarGroupWithOrg } from "@calcom/ui";
|
||||
|
||||
interface IInstantBookingProps {
|
||||
onConnectNow: () => void;
|
||||
event: NonNullable<useEventReturnType["data"]>;
|
||||
event: Pick<BookerEvent, "entity" | "schedulingType"> & {
|
||||
users: (Pick<User, "name" | "username" | "avatarUrl"> & { bookerUrl: string })[];
|
||||
};
|
||||
}
|
||||
|
||||
export const InstantBooking = ({ onConnectNow, event }: IInstantBookingProps) => {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useMemo, useEffect } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||
import type { CalendarEvent } from "@calcom/features/calendars/weeklyview/types/events";
|
||||
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
|
||||
import { useBookerStore } from "../store";
|
||||
import type { useEventReturnType, useScheduleForEventReturnType } from "../utils/event";
|
||||
import type { useScheduleForEventReturnType } from "../utils/event";
|
||||
import { getQueryParam } from "../utils/query-param";
|
||||
import { useOverlayCalendarStore } from "./OverlayCalendar/store";
|
||||
|
||||
@@ -20,7 +21,9 @@ export const LargeCalendar = ({
|
||||
extraDays: number;
|
||||
schedule?: useScheduleForEventReturnType["data"];
|
||||
isLoading: boolean;
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?: Pick<BookerEvent, "length"> | null;
|
||||
};
|
||||
}) => {
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
import { defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||
@@ -9,13 +10,12 @@ import { defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||
import { extraDaysConfig } from "../../config";
|
||||
import { useBookerStore } from "../../store";
|
||||
import type { BookerLayout } from "../../types";
|
||||
import type { useEventReturnType } from "../../utils/event";
|
||||
import { validateLayout } from "../../utils/layout";
|
||||
import { getQueryParam } from "../../utils/query-param";
|
||||
|
||||
export type UseBookerLayoutType = ReturnType<typeof useBookerLayout>;
|
||||
|
||||
export const useBookerLayout = (event: useEventReturnType["data"]) => {
|
||||
export const useBookerLayout = (event: Pick<BookerEvent, "profile"> | undefined | null) => {
|
||||
const [_layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
|
||||
const isEmbed = useIsEmbed();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
@@ -5,14 +5,14 @@ import { z } from "zod";
|
||||
|
||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { useInitialFormValues } from "./useInitialFormValues";
|
||||
|
||||
export interface IUseBookingForm {
|
||||
event: useEventReturnType["data"];
|
||||
event?: Pick<BookerEvent, "bookingFields"> | null;
|
||||
sessionEmail?: string | null;
|
||||
sessionName?: string | null;
|
||||
sessionUsername?: string | null;
|
||||
|
||||
@@ -7,9 +7,9 @@ import { useHandleBookEvent } from "@calcom/atoms/monorepo";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
||||
import { createBooking, createRecurringBooking, createInstantBooking } from "@calcom/features/bookings/lib";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -21,7 +21,29 @@ import { showToast } from "@calcom/ui";
|
||||
import type { UseBookingFormReturnType } from "./useBookingForm";
|
||||
|
||||
export interface IUseBookings {
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?:
|
||||
| (Pick<
|
||||
BookerEvent,
|
||||
| "id"
|
||||
| "slug"
|
||||
| "hosts"
|
||||
| "requiresConfirmation"
|
||||
| "isDynamic"
|
||||
| "metadata"
|
||||
| "forwardParamsSuccessRedirect"
|
||||
| "successRedirectUrl"
|
||||
| "length"
|
||||
| "recurringEvent"
|
||||
| "schedulingType"
|
||||
> & {
|
||||
users: Pick<
|
||||
BookerEvent["users"][number],
|
||||
"name" | "username" | "avatarUrl" | "weekStart" | "profile" | "bookerUrl"
|
||||
>[];
|
||||
})
|
||||
| null;
|
||||
};
|
||||
hashedLink?: string | null;
|
||||
bookingForm: UseBookingFormReturnType["bookingForm"];
|
||||
metadata: Record<string, string>;
|
||||
|
||||
@@ -2,14 +2,14 @@ import { useEffect, useState } from "react";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEvent } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
|
||||
export type useInitialFormValuesReturnType = ReturnType<typeof useInitialFormValues>;
|
||||
|
||||
type UseInitialFormValuesProps = {
|
||||
eventType: ReturnType<typeof useEvent>["data"];
|
||||
eventType?: Pick<BookerEvent, "bookingFields"> | null;
|
||||
rescheduleUid: string | null;
|
||||
isRescheduling: boolean;
|
||||
email?: string | null;
|
||||
|
||||
@@ -4,13 +4,13 @@ import { shallow } from "zustand/shallow";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
|
||||
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
|
||||
|
||||
export const useSlots = (event: useEventReturnType) => {
|
||||
export const useSlots = (event: { data?: Pick<BookerEvent, "id" | "length"> | null }) => {
|
||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
||||
const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
|
||||
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
|
||||
|
||||
@@ -5,10 +5,8 @@ import type { UseCalendarsReturnType } from "@calcom/features/bookings/Booker/co
|
||||
import type { UseSlotsReturnType } from "@calcom/features/bookings/Booker/components/hooks/useSlots";
|
||||
import type { UseVerifyCodeReturnType } from "@calcom/features/bookings/Booker/components/hooks/useVerifyCode";
|
||||
import type { UseVerifyEmailReturnType } from "@calcom/features/bookings/Booker/components/hooks/useVerifyEmail";
|
||||
import type {
|
||||
useEventReturnType,
|
||||
useScheduleForEventReturnType,
|
||||
} from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { useScheduleForEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { BookerEventQuery } from "@calcom/features/bookings/types";
|
||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { GetBookingType } from "../lib/get-booking";
|
||||
@@ -106,7 +104,7 @@ export type WrappedBookerPropsMain = {
|
||||
slots: UseSlotsReturnType;
|
||||
calendars: UseCalendarsReturnType;
|
||||
bookerForm: UseBookingFormReturnType;
|
||||
event: useEventReturnType;
|
||||
event: BookerEventQuery;
|
||||
schedule: useScheduleForEventReturnType;
|
||||
bookerLayout: UseBookerLayoutType;
|
||||
verifyEmail: UseVerifyEmailReturnType;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import type { IOutOfOfficeData } from "@calcom/core/getUserAvailability";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { OutOfOfficeInSlots } from "@calcom/features/bookings/Booker/components/OutOfOfficeInSlots";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import type { Slots } from "@calcom/features/schedules";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -15,7 +16,6 @@ import type { IGetAvailableSlots } from "@calcom/trpc/server/routers/viewer/slot
|
||||
import { Button, Icon, SkeletonText } from "@calcom/ui";
|
||||
|
||||
import { useBookerStore } from "../Booker/store";
|
||||
import type { useEventReturnType } from "../Booker/utils/event";
|
||||
import { getQueryParam } from "../Booker/utils/query-param";
|
||||
import { useTimePreferences } from "../lib";
|
||||
import { useCheckOverlapWithOverlay } from "../lib/useCheckOverlapWithOverlay";
|
||||
@@ -36,7 +36,9 @@ type AvailableTimesProps = {
|
||||
showTimeFormatToggle?: boolean;
|
||||
className?: string;
|
||||
selectedSlots?: string[];
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?: Pick<BookerEvent, "length"> | null;
|
||||
};
|
||||
customClassNames?: string;
|
||||
};
|
||||
|
||||
@@ -54,7 +56,9 @@ const SlotItem = ({
|
||||
selectedSlots?: string[];
|
||||
onTimeSelect: TOnTimeSelect;
|
||||
showAvailableSeatsCount?: boolean | null;
|
||||
event: useEventReturnType;
|
||||
event: {
|
||||
data?: Pick<BookerEvent, "length"> | null;
|
||||
};
|
||||
customClassNames?: string;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { Fragment } from "react";
|
||||
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { PriceIcon } from "@calcom/features/bookings/components/event-meta/PriceIcon";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon, type IconName } from "@calcom/ui";
|
||||
|
||||
import type { PublicEvent } from "../../types";
|
||||
import { EventDetailBlocks } from "../../types";
|
||||
import { AvailableEventLocations } from "./AvailableEventLocations";
|
||||
import { EventDuration } from "./Duration";
|
||||
@@ -15,7 +15,17 @@ import { EventOccurences } from "./Occurences";
|
||||
import { Price } from "./Price";
|
||||
|
||||
type EventDetailsPropsBase = {
|
||||
event: PublicEvent;
|
||||
event: Pick<
|
||||
BookerEvent,
|
||||
| "currency"
|
||||
| "price"
|
||||
| "locations"
|
||||
| "requiresConfirmation"
|
||||
| "recurringEvent"
|
||||
| "length"
|
||||
| "metadata"
|
||||
| "isDynamic"
|
||||
>;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ import { useEffect } from "react";
|
||||
|
||||
import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Badge } from "@calcom/ui";
|
||||
|
||||
import type { PublicEvent } from "../../types";
|
||||
|
||||
/** Render X mins as X hours or X hours Y mins instead of in minutes once >= 60 minutes */
|
||||
export const getDurationFormatted = (mins: number | undefined, t: TFunction) => {
|
||||
if (!mins) return null;
|
||||
@@ -36,7 +35,11 @@ export const getDurationFormatted = (mins: number | undefined, t: TFunction) =>
|
||||
return hourStr || minStr;
|
||||
};
|
||||
|
||||
export const EventDuration = ({ event }: { event: PublicEvent }) => {
|
||||
export const EventDuration = ({
|
||||
event,
|
||||
}: {
|
||||
event: Pick<BookerEvent, "length" | "metadata" | "isDynamic">;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const isPlatform = useIsPlatform();
|
||||
const [selectedDuration, setSelectedDuration, state] = useBookerStore((state) => [
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import type { PublicEvent } from "bookings/types";
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
import { Examples, Example, VariantsTable, VariantRow } from "@calcom/storybook/components";
|
||||
@@ -49,9 +48,15 @@ export const AllVariants: Story = {
|
||||
<EventMembers
|
||||
users={mockEvent.users}
|
||||
schedulingType="COLLECTIVE"
|
||||
entity={{ isUnpublished: false, name: "Example", orgSlug: null }}
|
||||
entity={{
|
||||
considerUnpublished: false,
|
||||
fromRedirectOfNonOrgLink: true,
|
||||
teamSlug: null,
|
||||
name: "Example",
|
||||
orgSlug: null,
|
||||
}}
|
||||
// TODO remove type assertion
|
||||
profile={{ weekStart: "Sunday" } as PublicEvent["profile"]}
|
||||
profile={{ bookerLayouts: null }}
|
||||
/>
|
||||
<EventTitle>Quick catch-up</EventTitle>
|
||||
<EventDetails event={mockEvent} />
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Tooltip } from "@calcom/ui";
|
||||
|
||||
import type { PublicEvent } from "../../types";
|
||||
import { EventMetaBlock } from "./Details";
|
||||
|
||||
export const EventLocations = ({ event }: { event: PublicEvent }) => {
|
||||
export const EventLocations = ({ event }: { event: BookerEvent }) => {
|
||||
const { t } = useLocale();
|
||||
const locations = event.locations;
|
||||
|
||||
if (!locations?.length) return null;
|
||||
|
||||
const getLocationToDisplay = (location: PublicEvent["locations"][number]) => {
|
||||
const getLocationToDisplay = (location: BookerEvent["locations"][number]) => {
|
||||
const eventLocationType = getEventLocationType(location.type);
|
||||
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { AvatarGroup } from "@calcom/ui";
|
||||
|
||||
import type { PublicEvent } from "../../types";
|
||||
|
||||
export interface EventMembersProps {
|
||||
/**
|
||||
* Used to determine whether all members should be shown or not.
|
||||
* In case of Round Robin type, members aren't shown.
|
||||
*/
|
||||
schedulingType: PublicEvent["schedulingType"];
|
||||
users: PublicEvent["users"];
|
||||
profile: PublicEvent["profile"];
|
||||
entity: PublicEvent["entity"];
|
||||
schedulingType: BookerEvent["schedulingType"];
|
||||
users: BookerEvent["users"];
|
||||
profile: BookerEvent["profile"];
|
||||
entity: BookerEvent["entity"];
|
||||
}
|
||||
|
||||
export const EventMembers = ({ schedulingType, users, profile, entity }: EventMembersProps) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { parseRecurringDates } from "@calcom/lib/parse-dates";
|
||||
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
||||
@@ -8,9 +9,8 @@ import { Tooltip, Alert } from "@calcom/ui";
|
||||
import { Input } from "@calcom/ui";
|
||||
|
||||
import { useTimePreferences } from "../../lib";
|
||||
import type { PublicEvent } from "../../types";
|
||||
|
||||
export const EventOccurences = ({ event }: { event: PublicEvent }) => {
|
||||
export const EventOccurences = ({ event }: { event: Pick<BookerEvent, "recurringEvent"> }) => {
|
||||
const maxOccurences = event.recurringEvent?.count || null;
|
||||
const { t, i18n } = useLocale();
|
||||
const [setRecurringEventCount, recurringEventCount, setOccurenceCount, occurenceCount] = useBookerStore(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PublicEvent } from "bookings/types";
|
||||
import type { BookerEvent } from "bookings/types";
|
||||
|
||||
export const mockEvent: PublicEvent = {
|
||||
export const mockEvent: BookerEvent = {
|
||||
id: 1,
|
||||
title: "Quick check-in",
|
||||
slug: "quick-check-in",
|
||||
@@ -8,10 +8,10 @@ export const mockEvent: PublicEvent = {
|
||||
description:
|
||||
"Use this event for a quick 15 minute catchup. Visit this long url to test the component https://cal.com/averylongurlwithoutspacesthatshouldntbreaklayout",
|
||||
users: [
|
||||
{ name: "Pro example", username: "pro", weekStart: "Sunday", organizationId: null },
|
||||
{ name: "Team example", username: "team", weekStart: "Sunday", organizationId: 1 },
|
||||
{ name: "Pro example", username: "pro", weekStart: "Sunday", avatarUrl: "", profile: null },
|
||||
{ name: "Team example", username: "team", weekStart: "Sunday", avatarUrl: "", profile: null },
|
||||
],
|
||||
schedulingType: null,
|
||||
length: 30,
|
||||
locations: [{ type: "integrations:google:meet" }, { type: "integrations:zoom" }],
|
||||
} as PublicEvent; // TODO: complete mock and remove type assertion
|
||||
};
|
||||
|
||||
@@ -3,11 +3,11 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { parseRecurringDates } from "@calcom/lib/parse-dates";
|
||||
|
||||
import type { PublicEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
||||
|
||||
export type BookingOptions = {
|
||||
values: Record<string, unknown>;
|
||||
event: PublicEvent;
|
||||
event: Pick<BookerEvent, "id" | "length" | "slug" | "schedulingType" | "recurringEvent">;
|
||||
date: string;
|
||||
// @NOTE: duration is not validated in this function
|
||||
duration: number | undefined | null;
|
||||
|
||||
@@ -6,6 +6,53 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import type { AppsStatus } from "@calcom/types/Calendar";
|
||||
|
||||
export type PublicEvent = NonNullable<RouterOutputs["viewer"]["public"]["event"]>;
|
||||
|
||||
export type BookerEventQuery = {
|
||||
isSuccess: boolean;
|
||||
isError: boolean;
|
||||
isPending: boolean;
|
||||
data?: BookerEvent | null;
|
||||
};
|
||||
|
||||
type BookerEventUser = Pick<
|
||||
PublicEvent["users"][number],
|
||||
"name" | "username" | "avatarUrl" | "weekStart" | "profile"
|
||||
> & {
|
||||
metadata?: undefined;
|
||||
brandColor?: string | null;
|
||||
darkBrandColor?: string | null;
|
||||
bookerUrl: string;
|
||||
};
|
||||
|
||||
type BookerEventProfile = Pick<PublicEvent["profile"], "name" | "image" | "bookerLayouts">;
|
||||
|
||||
export type BookerEvent = Pick<
|
||||
PublicEvent,
|
||||
| "id"
|
||||
| "length"
|
||||
| "slug"
|
||||
| "schedulingType"
|
||||
| "recurringEvent"
|
||||
| "entity"
|
||||
| "locations"
|
||||
| "metadata"
|
||||
| "isDynamic"
|
||||
| "requiresConfirmation"
|
||||
| "price"
|
||||
| "currency"
|
||||
| "lockTimeZoneToggleOnBookingPage"
|
||||
| "schedule"
|
||||
| "seatsPerTimeSlot"
|
||||
| "title"
|
||||
| "description"
|
||||
| "forwardParamsSuccessRedirect"
|
||||
| "successRedirectUrl"
|
||||
| "hosts"
|
||||
| "bookingFields"
|
||||
| "seatsShowAvailabilityCount"
|
||||
| "isInstantEvent"
|
||||
> & { users: BookerEventUser[] } & { profile: BookerEventProfile };
|
||||
|
||||
export type ValidationErrors<T extends object> = { key: FieldPath<T>; error: ErrorOption }[];
|
||||
|
||||
export type EventPrice = { currency: string; price: number; displayAlternateSymbol?: boolean };
|
||||
|
||||
@@ -108,7 +108,7 @@ const commons = {
|
||||
secondaryEmail: null,
|
||||
};
|
||||
|
||||
const dynamicEvent = {
|
||||
export const dynamicEvent = {
|
||||
length: 30,
|
||||
slug: "dynamic",
|
||||
title: "Group Meeting",
|
||||
@@ -120,7 +120,7 @@ const dynamicEvent = {
|
||||
metadata: EventTypeMetaDataSchema.parse({ multipleDuration: [15, 30, 45, 60, 90] }),
|
||||
};
|
||||
|
||||
const defaultEvents = [dynamicEvent];
|
||||
export const defaultEvents = [dynamicEvent];
|
||||
|
||||
export const getDynamicEventDescription = (dynamicUsernames: string[], slug: string): string => {
|
||||
return `Book a ${slug} min event with ${dynamicUsernames.join(", ")}`;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./transformers";
|
||||
@@ -0,0 +1,540 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { BookingField_2024_06_14, Location_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
import type { CommonField, OptionsField } from "./api-request";
|
||||
import {
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformSelectOptions,
|
||||
} from "./api-request";
|
||||
|
||||
describe("transformApiEventTypeLocations", () => {
|
||||
it("should transform address", () => {
|
||||
const input: Location_2024_06_14[] = [
|
||||
{
|
||||
type: "address",
|
||||
address: "London road 10-1",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [{ type: "inPerson", address: "London road 10-1", displayLocationPublicly: true }];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform link", () => {
|
||||
const input: Location_2024_06_14[] = [
|
||||
{
|
||||
type: "link",
|
||||
link: "https://customvideo.com/join/123456",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{ type: "link", link: "https://customvideo.com/join/123456", displayLocationPublicly: true },
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform integration", () => {
|
||||
const input: Location_2024_06_14[] = [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [{ type: "integrations:daily" }];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform phone", () => {
|
||||
const input: Location_2024_06_14[] = [
|
||||
{
|
||||
type: "phone",
|
||||
phone: "+37120993151",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{ type: "userPhone", hostPhoneNumber: "+37120993151", displayLocationPublicly: true },
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformApiEventTypeBookingFields", () => {
|
||||
it("should transform name field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "name",
|
||||
slug: "name",
|
||||
label: "Your name",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform email field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "email",
|
||||
slug: "email",
|
||||
label: "Your email",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform phone field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "phone",
|
||||
slug: "phone",
|
||||
label: "Your phone number",
|
||||
required: true,
|
||||
placeholder: "123456789",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform address field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "address",
|
||||
slug: "address",
|
||||
label: "Your address",
|
||||
required: true,
|
||||
placeholder: "1234 Main St",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform text field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "text",
|
||||
slug: "text",
|
||||
label: "Your text",
|
||||
required: true,
|
||||
placeholder: "Enter your text",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform number field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "number",
|
||||
slug: "number",
|
||||
label: "Your number",
|
||||
required: true,
|
||||
placeholder: "100",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform textarea field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "textarea",
|
||||
slug: "textarea",
|
||||
label: "Your detailed information",
|
||||
required: true,
|
||||
placeholder: "Detailed description here...",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform select field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "select",
|
||||
slug: "select",
|
||||
label: "Your selection",
|
||||
required: true,
|
||||
placeholder: "Select...",
|
||||
options: ["Option 1", "Option 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: OptionsField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform multiselect field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "multiselect",
|
||||
slug: "multiselect",
|
||||
label: "Your multiple selections",
|
||||
required: true,
|
||||
options: ["Option 1", "Option 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: OptionsField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform multiemail field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "multiemail",
|
||||
slug: "multiemail",
|
||||
label: "Your multiple emails",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform checkbox field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "checkbox",
|
||||
slug: "checkbox",
|
||||
label: "Your checkboxes",
|
||||
required: true,
|
||||
options: ["Checkbox 1", "Checkbox 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: OptionsField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform radio field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "radio",
|
||||
slug: "radio",
|
||||
label: "Your radio buttons",
|
||||
required: true,
|
||||
options: ["Radio 1", "Radio 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: OptionsField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform boolean field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "boolean",
|
||||
slug: "boolean",
|
||||
label: "Agree to terms?",
|
||||
required: true,
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CommonField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,204 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { CreateEventTypeInput_2024_06_14, Integration_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
const integrationsMapping: Record<Integration_2024_06_14, string> = {
|
||||
"cal-video": "integrations:daily",
|
||||
};
|
||||
|
||||
function transformApiEventTypeLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
|
||||
if (!inputLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return inputLocations.map((location) => {
|
||||
const { type } = location;
|
||||
switch (type) {
|
||||
case "address":
|
||||
return { type: "inPerson", address: location.address, displayLocationPublicly: location.public };
|
||||
case "link":
|
||||
return { type: "link", link: location.link, displayLocationPublicly: location.public };
|
||||
case "integration":
|
||||
const integrationLabel = integrationsMapping[location.integration];
|
||||
return { type: integrationLabel };
|
||||
case "phone":
|
||||
return {
|
||||
type: "userPhone",
|
||||
hostPhoneNumber: location.phone,
|
||||
displayLocationPublicly: location.public,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported location type '${type}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const integrationsMappingSchema = {
|
||||
"cal-video": z.literal("integrations:daily"),
|
||||
};
|
||||
|
||||
const InPersonSchema = z.object({
|
||||
type: z.literal("inPerson"),
|
||||
address: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const LinkSchema = z.object({
|
||||
type: z.literal("link"),
|
||||
link: z.string().url(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const IntegrationSchema = z.object({
|
||||
type: z.union([integrationsMappingSchema["cal-video"], integrationsMappingSchema["cal-video"]]),
|
||||
});
|
||||
|
||||
const UserPhoneSchema = z.object({
|
||||
type: z.literal("userPhone"),
|
||||
hostPhoneNumber: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const TransformedLocationSchema = z.union([InPersonSchema, LinkSchema, IntegrationSchema, UserPhoneSchema]);
|
||||
export const TransformedLocationsSchema = z.array(TransformedLocationSchema);
|
||||
|
||||
function transformApiEventTypeBookingFields(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]
|
||||
) {
|
||||
if (!inputBookingFields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const customBookingFields = inputBookingFields.map((field) => {
|
||||
const commonFields: CommonField = {
|
||||
name: field.slug,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: field.required,
|
||||
placeholder: "placeholder" in field && field.placeholder ? field.placeholder : "",
|
||||
};
|
||||
|
||||
const options = "options" in field && field.options ? transformSelectOptions(field.options) : undefined;
|
||||
|
||||
if (!options) {
|
||||
return commonFields;
|
||||
}
|
||||
|
||||
return {
|
||||
...commonFields,
|
||||
options,
|
||||
};
|
||||
});
|
||||
|
||||
return customBookingFields;
|
||||
}
|
||||
|
||||
export function transformSelectOptions(options: string[]) {
|
||||
return options.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
}));
|
||||
}
|
||||
|
||||
const FieldTypeEnum = z.enum([
|
||||
"number",
|
||||
"boolean",
|
||||
"address",
|
||||
"name",
|
||||
"text",
|
||||
"textarea",
|
||||
"email",
|
||||
"phone",
|
||||
"multiemail",
|
||||
"select",
|
||||
"multiselect",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"radioInput",
|
||||
]);
|
||||
|
||||
const CommonFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: FieldTypeEnum,
|
||||
label: z.string(),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("user"),
|
||||
type: z.literal("user"),
|
||||
label: z.literal("User"),
|
||||
fieldRequired: z.literal(true),
|
||||
})
|
||||
),
|
||||
editable: z.literal("user"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string().optional(),
|
||||
});
|
||||
|
||||
const SystemFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: FieldTypeEnum,
|
||||
defaultLabel: z.string(),
|
||||
labe: z.string().optional(),
|
||||
editable: z.enum(["system-but-optional", "system"]),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("default"),
|
||||
type: z.literal("default"),
|
||||
label: z.literal("Default"),
|
||||
})
|
||||
),
|
||||
views: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.enum(["reschedule"]),
|
||||
label: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
defaultPlaceholder: z.enum(["", "share_additional_notes", "email", "reschedule_placeholder"]).optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
required: z.boolean(),
|
||||
hideWhenJustOneOption: z.boolean().optional(),
|
||||
getOptionsAt: z.enum(["locations"]).optional(),
|
||||
optionsInputs: z
|
||||
.object({
|
||||
attendeeInPerson: z.object({
|
||||
type: z.literal("address"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
phone: z.object({
|
||||
type: z.literal("phone"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type SystemField = z.infer<typeof SystemFieldsSchema>;
|
||||
|
||||
export type CommonField = z.infer<typeof CommonFieldsSchema>;
|
||||
|
||||
const OptionSchema = z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
const BookingFieldSchema = CommonFieldsSchema.extend({
|
||||
options: z.array(OptionSchema).optional(),
|
||||
});
|
||||
export type OptionsField = z.infer<typeof BookingFieldSchema>;
|
||||
|
||||
export const BookingFieldsSchema = z.array(BookingFieldSchema);
|
||||
|
||||
export { transformApiEventTypeLocations, transformApiEventTypeBookingFields };
|
||||
@@ -0,0 +1,568 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type {
|
||||
AddressLocation_2024_06_14,
|
||||
LinkLocation_2024_06_14,
|
||||
PhoneLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { CommonField, OptionsField } from "./api-request";
|
||||
import { getResponseEventTypeLocations, getResponseEventTypeBookingFields } from "./api-response";
|
||||
|
||||
describe("getResponseEventTypeLocations", () => {
|
||||
it("should reverse transform address location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "inPerson",
|
||||
address: "1234 Main St",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput: AddressLocation_2024_06_14[] = [
|
||||
{
|
||||
type: "address",
|
||||
address: "1234 Main St",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform link location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "link",
|
||||
link: "https://example.com",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput: LinkLocation_2024_06_14[] = [
|
||||
{
|
||||
type: "link",
|
||||
link: "https://example.com",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform phone location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "userPhone",
|
||||
hostPhoneNumber: "123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput: PhoneLocation_2024_06_14[] = [
|
||||
{
|
||||
type: "phone",
|
||||
phone: "123456789",
|
||||
public: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform integration location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "integrations:daily",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput: IntegrationLocation_2024_06_14[] = [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResponseEventTypeBookingFields", () => {
|
||||
it("should reverse transform name field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-name",
|
||||
type: "name",
|
||||
label: "Your name",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "name",
|
||||
slug: "your-name",
|
||||
label: "Your name",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform email field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-email",
|
||||
type: "email",
|
||||
label: "Your email",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "email",
|
||||
slug: "your-email",
|
||||
label: "Your email",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform phone field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-phone",
|
||||
type: "phone",
|
||||
label: "Your phone number",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "123456789",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "phone",
|
||||
slug: "your-phone",
|
||||
label: "Your phone number",
|
||||
required: true,
|
||||
placeholder: "123456789",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform address field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-address",
|
||||
type: "address",
|
||||
label: "Your address",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "1234 Main St",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "address",
|
||||
slug: "your-address",
|
||||
label: "Your address",
|
||||
required: true,
|
||||
placeholder: "1234 Main St",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform text field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-text",
|
||||
type: "text",
|
||||
label: "Your text",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "Enter your text",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "text",
|
||||
slug: "your-text",
|
||||
label: "Your text",
|
||||
required: true,
|
||||
placeholder: "Enter your text",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform number field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-number",
|
||||
type: "number",
|
||||
label: "Your number",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "100",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "number",
|
||||
slug: "your-number",
|
||||
label: "Your number",
|
||||
required: true,
|
||||
placeholder: "100",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform textarea field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-textarea",
|
||||
type: "textarea",
|
||||
label: "Your detailed information",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "Detailed description here...",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "textarea",
|
||||
slug: "your-textarea",
|
||||
label: "Your detailed information",
|
||||
required: true,
|
||||
placeholder: "Detailed description here...",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform select field", () => {
|
||||
const transformedField: OptionsField[] = [
|
||||
{
|
||||
name: "your-select",
|
||||
type: "select",
|
||||
label: "Your selection",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "Select...",
|
||||
options: [
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 2" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "select",
|
||||
slug: "your-select",
|
||||
label: "Your selection",
|
||||
required: true,
|
||||
placeholder: "Select...",
|
||||
options: ["Option 1", "Option 2"],
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform multiselect field", () => {
|
||||
const transformedField: OptionsField[] = [
|
||||
{
|
||||
name: "your-multiselect",
|
||||
type: "multiselect",
|
||||
label: "Your multiple selections",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
options: [
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 2" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "multiselect",
|
||||
slug: "your-multiselect",
|
||||
label: "Your multiple selections",
|
||||
required: true,
|
||||
options: ["Option 1", "Option 2"],
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform multiemail field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "your-multiemail",
|
||||
type: "multiemail",
|
||||
label: "Your multiple emails",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "multiemail",
|
||||
slug: "your-multiemail",
|
||||
label: "Your multiple emails",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform checkbox field", () => {
|
||||
const transformedField: OptionsField[] = [
|
||||
{
|
||||
name: "your-checkbox",
|
||||
type: "checkbox",
|
||||
label: "Your checkboxes",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
options: [
|
||||
{ label: "Checkbox 1", value: "Checkbox 1" },
|
||||
{ label: "Checkbox 2", value: "Checkbox 2" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "checkbox",
|
||||
slug: "your-checkbox",
|
||||
label: "Your checkboxes",
|
||||
required: true,
|
||||
options: ["Checkbox 1", "Checkbox 2"],
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform radio field", () => {
|
||||
const transformedField: OptionsField[] = [
|
||||
{
|
||||
name: "your-radio",
|
||||
type: "radio",
|
||||
label: "Your radio buttons",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
options: [
|
||||
{ label: "Radio 1", value: "Radio 1" },
|
||||
{ label: "Radio 2", value: "Radio 2" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "radio",
|
||||
slug: "your-radio",
|
||||
label: "Your radio buttons",
|
||||
required: true,
|
||||
options: ["Radio 1", "Radio 2"],
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform boolean field", () => {
|
||||
const transformedField: CommonField[] = [
|
||||
{
|
||||
name: "agree-to-terms",
|
||||
type: "boolean",
|
||||
label: "Agree to terms?",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "boolean",
|
||||
slug: "agree-to-terms",
|
||||
label: "Agree to terms?",
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,191 @@
|
||||
import type {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
LinkLocation_2024_06_14,
|
||||
PhoneLocation_2024_06_14,
|
||||
Integration_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { transformApiEventTypeBookingFields, transformApiEventTypeLocations } from "./api-request";
|
||||
|
||||
const reverseIntegrationsMapping: Record<string, Integration_2024_06_14> = {
|
||||
"integrations:daily": "cal-video",
|
||||
};
|
||||
|
||||
function getResponseEventTypeLocations(
|
||||
transformedLocations: ReturnType<typeof transformApiEventTypeLocations>
|
||||
) {
|
||||
if (!transformedLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return transformedLocations.map((location) => {
|
||||
switch (location.type) {
|
||||
case "inPerson": {
|
||||
if (!location.address) {
|
||||
throw new Error("Address location must have an address");
|
||||
}
|
||||
const addressLocation: AddressLocation_2024_06_14 = {
|
||||
type: "address",
|
||||
address: location.address,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return addressLocation;
|
||||
}
|
||||
case "link": {
|
||||
if (!location.link) {
|
||||
throw new Error("Link location must have a link");
|
||||
}
|
||||
const linkLocation: LinkLocation_2024_06_14 = {
|
||||
type: "link",
|
||||
link: location.link,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return linkLocation;
|
||||
}
|
||||
case "userPhone": {
|
||||
if (!location.hostPhoneNumber) {
|
||||
throw new Error("Phone location must have a phone number");
|
||||
}
|
||||
const phoneLocation: PhoneLocation_2024_06_14 = {
|
||||
type: "phone",
|
||||
phone: location.hostPhoneNumber,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return phoneLocation;
|
||||
}
|
||||
default: {
|
||||
const integrationType = reverseIntegrationsMapping[location.type];
|
||||
if (!integrationType) {
|
||||
throw new Error(`Unsupported integration type '${location.type}'.`);
|
||||
}
|
||||
const integration: IntegrationLocation_2024_06_14 = {
|
||||
type: "integration",
|
||||
integration: integrationType,
|
||||
};
|
||||
return integration;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getResponseEventTypeBookingFields(
|
||||
transformedBookingFields: ReturnType<typeof transformApiEventTypeBookingFields>
|
||||
) {
|
||||
if (!transformedBookingFields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return transformedBookingFields.map((field) => {
|
||||
switch (field.type) {
|
||||
case "name":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "phone":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "address":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "text":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "textarea":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "multiemail":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "boolean":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
options: "options" in field ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "multiselect":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: "options" in field ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: "options" in field ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "radio":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: "options" in field ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported booking field type '${field.type}'.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { getResponseEventTypeLocations, getResponseEventTypeBookingFields };
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./api-request";
|
||||
export * from "./api-response";
|
||||
@@ -4,9 +4,10 @@ import { getEventTypeAppData } from "@calcom/app-store/_utils/getEventTypeAppDat
|
||||
import type { appDataSchemas } from "@calcom/app-store/apps.schemas.generated";
|
||||
import type { appDataSchema, paymentOptionEnum } from "@calcom/app-store/stripepayment/zod";
|
||||
import type { EventTypeAppsList } from "@calcom/app-store/utils";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
|
||||
export default function getPaymentAppData(
|
||||
eventType: Parameters<typeof getEventTypeAppData>[0],
|
||||
eventType: Pick<BookerEvent, "price" | "currency" | "metadata">,
|
||||
forcedGet?: boolean
|
||||
) {
|
||||
const metadataApps = eventType?.metadata?.apps as unknown as EventTypeAppsList;
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from "./isBookingLimits";
|
||||
export * from "./isDurationLimits";
|
||||
export * from "./validateIntervalLimitOrder";
|
||||
export * from "./schedules";
|
||||
export * from "./event-types";
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import type {
|
||||
} from "@calcom/platform-types";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { transformApiEventTypeForAtom } from "../event-types/atom-api-transformers/transformApiEventTypeForAtom";
|
||||
import { useEventType } from "../hooks/event-types/public/useEventType";
|
||||
import { useAtomsContext } from "../hooks/useAtomsContext";
|
||||
import { useAvailableSlots } from "../hooks/useAvailableSlots";
|
||||
import { useCalendarsBusyTimes } from "../hooks/useCalendarsBusyTimes";
|
||||
@@ -36,7 +38,6 @@ import {
|
||||
} from "../hooks/useGetBookingForReschedule";
|
||||
import { useHandleBookEvent } from "../hooks/useHandleBookEvent";
|
||||
import { useMe } from "../hooks/useMe";
|
||||
import { usePublicEvent } from "../hooks/usePublicEvent";
|
||||
import { useSlots } from "../hooks/useSlots";
|
||||
import { AtomsWrapper } from "../src/components/atoms-wrapper";
|
||||
|
||||
@@ -109,6 +110,7 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
setBookingData(null);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
setSelectedDuration(props.duration ?? null);
|
||||
@@ -118,11 +120,22 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
return getUsernameList(username ?? "").length > 1;
|
||||
}, [username]);
|
||||
|
||||
const event = usePublicEvent({
|
||||
username,
|
||||
eventSlug: props.eventSlug,
|
||||
isDynamic,
|
||||
});
|
||||
const { isSuccess, isError, isPending, data } = useEventType(username, props.eventSlug);
|
||||
|
||||
const event = useMemo(() => {
|
||||
return {
|
||||
isSuccess,
|
||||
isError,
|
||||
isPending,
|
||||
data: data && data.length > 0 ? transformApiEventTypeForAtom(data[0], props.entity) : undefined,
|
||||
};
|
||||
}, [isSuccess, isError, isPending, data, props.entity]);
|
||||
|
||||
if (isDynamic && props.duration && event.data) {
|
||||
// note(Lauris): Mandatory - In case of "dynamic" event type default event duration returned by the API is 30,
|
||||
// but we are re-using the dynamic event type as a team event, so we must set the event length to whatever the event length is.
|
||||
event.data.length = props.duration;
|
||||
}
|
||||
|
||||
const bookerLayout = useBookerLayout(event.data);
|
||||
useInitializeBookerStore({
|
||||
@@ -322,7 +335,7 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
onClickOverlayContinue={function (): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
onOverlaySwitchStateChange={function (state: boolean): void {
|
||||
onOverlaySwitchStateChange={function (): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
extraOptions={extraOptions ?? {}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
|
||||
import type { API_VERSIONS_ENUM } from "@calcom/platform-constants";
|
||||
import { VERSION_2024_06_11 } from "@calcom/platform-constants";
|
||||
import { VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
|
||||
import http from "../lib/http";
|
||||
import { BaseCalProvider } from "./BaseCalProvider";
|
||||
@@ -40,7 +40,7 @@ export function CalProvider({
|
||||
children,
|
||||
autoUpdateTimezone = true,
|
||||
onTimezoneChange,
|
||||
version = VERSION_2024_06_11,
|
||||
version = VERSION_2024_06_14,
|
||||
}: CalProviderProps) {
|
||||
useEffect(() => {
|
||||
http.setVersionHeader(version);
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||
import { getFieldIdentifier } from "@calcom/features/form-builder/utils/getFieldIdentifier";
|
||||
import { defaultEvents } from "@calcom/lib/defaultEvents";
|
||||
import type { CommonField, OptionsField, SystemField } from "@calcom/lib/event-types/transformers";
|
||||
import {
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeBookingFields,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import type { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
import {
|
||||
bookerLayoutOptions,
|
||||
BookerLayouts,
|
||||
bookerLayouts as bookerLayoutsSchema,
|
||||
userMetadata as userMetadataSchema,
|
||||
eventTypeBookingFields,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
|
||||
export function transformApiEventTypeForAtom(
|
||||
eventType: Omit<EventTypeOutput_2024_06_14, "ownerId">,
|
||||
entity: BookerProps["entity"] | undefined
|
||||
) {
|
||||
const { lengthInMinutes, locations, bookingFields, users, ...rest } = eventType;
|
||||
|
||||
const isDefault = isDefaultEvent(rest.title);
|
||||
const user = users[0];
|
||||
|
||||
const defaultEventBookerLayouts = {
|
||||
enabledLayouts: [...bookerLayoutOptions],
|
||||
defaultLayout: BookerLayouts.MONTH_VIEW,
|
||||
};
|
||||
const firstUsersMetadata = userMetadataSchema.parse(user.metadata || {});
|
||||
const bookerLayouts = bookerLayoutsSchema.parse(
|
||||
firstUsersMetadata?.defaultBookerLayouts || defaultEventBookerLayouts
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: getLocations(locations),
|
||||
bookingFields: getBookingFields(bookingFields),
|
||||
isDefault,
|
||||
isDynamic: false,
|
||||
profile: {
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
weekStart: user.weekStart,
|
||||
image: "",
|
||||
brandColor: user.brandColor,
|
||||
darkBrandColor: user.darkBrandColor,
|
||||
theme: null,
|
||||
bookerLayouts,
|
||||
},
|
||||
entity: entity
|
||||
? {
|
||||
...entity,
|
||||
orgSlug: entity.orgSlug || null,
|
||||
teamSlug: entity.teamSlug || null,
|
||||
fromRedirectOfNonOrgLink: true,
|
||||
name: entity.name || null,
|
||||
logoUrl: entity.logoUrl || undefined,
|
||||
}
|
||||
: {
|
||||
fromRedirectOfNonOrgLink: true,
|
||||
considerUnpublished: false,
|
||||
orgSlug: null,
|
||||
teamSlug: null,
|
||||
name: null,
|
||||
logoUrl: undefined,
|
||||
},
|
||||
hosts: [],
|
||||
users: users.map((user) => ({
|
||||
...user,
|
||||
metadata: undefined,
|
||||
bookerUrl: getBookerBaseUrlSync(null),
|
||||
profile: {
|
||||
username: user.username || "",
|
||||
name: user.name,
|
||||
weekStart: user.weekStart,
|
||||
image: "",
|
||||
brandColor: user.brandColor,
|
||||
darkBrandColor: user.darkBrandColor,
|
||||
theme: null,
|
||||
organization: null,
|
||||
id: user.id,
|
||||
organizationId: null,
|
||||
userId: user.id,
|
||||
upId: `usr-${user.id}`,
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function isDefaultEvent(eventSlug: string) {
|
||||
const foundInDefaults = defaultEvents.find((obj) => {
|
||||
return obj.slug === eventSlug;
|
||||
});
|
||||
return !!foundInDefaults;
|
||||
}
|
||||
|
||||
function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
|
||||
const transformed = transformApiEventTypeLocations(locations);
|
||||
|
||||
const withPrivateHidden = transformed.map((location) => {
|
||||
const { displayLocationPublicly, type } = location;
|
||||
|
||||
switch (type) {
|
||||
case "address":
|
||||
return displayLocationPublicly ? location : { ...location, address: "" };
|
||||
case "link":
|
||||
return displayLocationPublicly ? location : { ...location, link: "" };
|
||||
case "phone":
|
||||
return displayLocationPublicly
|
||||
? location
|
||||
: {
|
||||
...location,
|
||||
hostPhoneNumber: "",
|
||||
};
|
||||
default:
|
||||
return location;
|
||||
}
|
||||
});
|
||||
|
||||
return withPrivateHidden;
|
||||
}
|
||||
|
||||
function getBookingFields(bookingFields: EventTypeOutput_2024_06_14["bookingFields"]) {
|
||||
const transformedBookingFields: (CommonField | SystemField | OptionsField)[] =
|
||||
transformApiEventTypeBookingFields(bookingFields);
|
||||
|
||||
// These fields should be added before other user fields
|
||||
const systemBeforeFields: SystemField[] = [
|
||||
{
|
||||
type: "name",
|
||||
// This is the `name` of the main field
|
||||
name: "name",
|
||||
editable: "system",
|
||||
// This Label is used in Email only as of now.
|
||||
defaultLabel: "your_name",
|
||||
required: true,
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultLabel: "email_address",
|
||||
type: "email",
|
||||
name: "email",
|
||||
required: true,
|
||||
editable: "system",
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultLabel: "location",
|
||||
type: "radioInput",
|
||||
name: "location",
|
||||
editable: "system",
|
||||
hideWhenJustOneOption: true,
|
||||
required: false,
|
||||
getOptionsAt: "locations",
|
||||
optionsInputs: {
|
||||
attendeeInPerson: {
|
||||
type: "address",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
phone: {
|
||||
type: "phone",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
},
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// These fields should be added after other user fields
|
||||
const systemAfterFields: SystemField[] = [
|
||||
{
|
||||
defaultLabel: "reason_for_reschedule",
|
||||
type: "textarea",
|
||||
editable: "system-but-optional",
|
||||
name: "rescheduleReason",
|
||||
defaultPlaceholder: "reschedule_placeholder",
|
||||
required: false,
|
||||
views: [
|
||||
{
|
||||
id: "reschedule",
|
||||
label: "Reschedule View",
|
||||
},
|
||||
],
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const missingSystemBeforeFields: SystemField[] = [];
|
||||
|
||||
for (const field of systemBeforeFields) {
|
||||
const existingBookingFieldIndex = transformedBookingFields.findIndex(
|
||||
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||
);
|
||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||
if (existingBookingFieldIndex === -1) {
|
||||
missingSystemBeforeFields.push(field);
|
||||
} else {
|
||||
// Adding the fields from Code first and then fields from DB. Allows, the code to push new properties to the field
|
||||
transformedBookingFields[existingBookingFieldIndex] = {
|
||||
...field,
|
||||
...transformedBookingFields[existingBookingFieldIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
transformedBookingFields.push(...missingSystemBeforeFields);
|
||||
|
||||
const missingSystemAfterFields: SystemField[] = [];
|
||||
for (const field of systemAfterFields) {
|
||||
const existingBookingFieldIndex = transformedBookingFields.findIndex(
|
||||
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||
);
|
||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||
if (existingBookingFieldIndex === -1) {
|
||||
missingSystemAfterFields.push(field);
|
||||
} else {
|
||||
transformedBookingFields[existingBookingFieldIndex] = {
|
||||
// Adding the fields from Code first and then fields from DB. Allows, the code to push new properties to the field
|
||||
...field,
|
||||
...transformedBookingFields[existingBookingFieldIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
transformedBookingFields.push(...missingSystemAfterFields);
|
||||
return eventTypeBookingFields.brand<"HAS_SYSTEM_FIELDS">().parse(transformedBookingFields);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "use-event-by-id";
|
||||
export const useEventTypeById = (id: number | null) => {
|
||||
const pathname = `/${V2_ENDPOINTS.eventTypes}/${id}`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEY, id],
|
||||
queryFn: () => {
|
||||
return http?.get<ApiResponse<EventTypeOutput_2024_06_14>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<EventTypeOutput_2024_06_14>).data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { SUCCESS_STATUS, V2_ENDPOINTS } from "@calcom/platform-constants";
|
||||
import type { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { ApiResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "use-event-type";
|
||||
export type UsePublicEventReturnType = ReturnType<typeof useEventType>;
|
||||
|
||||
export const useEventType = (username: string, eventSlug: string) => {
|
||||
const [stateUsername, stateEventSlug] = useBookerStore(
|
||||
(state) => [state.username, state.eventSlug],
|
||||
shallow
|
||||
);
|
||||
|
||||
const requestUsername = stateUsername ?? username;
|
||||
const requestEventSlug = stateEventSlug ?? eventSlug;
|
||||
|
||||
const isDynamic = useMemo(() => {
|
||||
return getUsernameList(requestUsername ?? "").length > 1;
|
||||
}, [requestUsername]);
|
||||
|
||||
const event = useQuery({
|
||||
queryKey: [QUERY_KEY, stateUsername ?? username, stateEventSlug ?? eventSlug],
|
||||
queryFn: async () => {
|
||||
if (isDynamic) {
|
||||
return http
|
||||
.get<ApiResponse<EventTypeOutput_2024_06_14[]>>(
|
||||
`/${V2_ENDPOINTS.eventTypes}?usernames=${encodeURIComponent(getUsernameList(username).join(","))}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return res.data.data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
}
|
||||
|
||||
return http
|
||||
.get<ApiResponse<EventTypeOutput_2024_06_14[]>>(
|
||||
`/${V2_ENDPOINTS.eventTypes}?username=${requestUsername}&eventSlug=${requestEventSlug}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return res.data.data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return event;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "use-event-types";
|
||||
export const useEventTypes = (username: string) => {
|
||||
const pathname = `/${V2_ENDPOINTS.eventTypes}?username=${username}`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEY, username],
|
||||
queryFn: () => {
|
||||
return http?.get<ApiResponse<EventTypeOutput_2024_06_14[]>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<EventTypeOutput_2024_06_14[]>).data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { EventTypesByViewer } from "@calcom/platform-libraries";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "get-private-events";
|
||||
export const useEventTypesPrivate = () => {
|
||||
const pathname = `/${V2_ENDPOINTS.eventTypes}`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEY],
|
||||
queryFn: () => {
|
||||
return http?.get<ApiResponse<EventTypesByViewer>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<EventTypesByViewer>).data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { EventTypesPublic } from "@calcom/platform-libraries";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "get-public-events";
|
||||
export const useEventTypesPublic = (username: string) => {
|
||||
const pathname = `/${V2_ENDPOINTS.eventTypes}/${username}/public`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEY, username],
|
||||
queryFn: () => {
|
||||
return http?.get<ApiResponse<EventTypesPublic>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<EventTypesPublic>).data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { V2_ENDPOINTS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { EventType as AtomEventType } from "@calcom/platform-libraries";
|
||||
import type { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
|
||||
import http from "../../lib/http";
|
||||
|
||||
export const QUERY_KEY = "get-event-by-id";
|
||||
export const useGetEventTypeById = (id: number | null) => {
|
||||
const pathname = `/${V2_ENDPOINTS.eventTypes}/${id}?for=atom`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QUERY_KEY, id],
|
||||
queryFn: () => {
|
||||
return http?.get<ApiResponse<AtomEventType>>(pathname).then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
return (res.data as ApiSuccessResponse<AtomEventType>).data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import {
|
||||
useTimePreferences,
|
||||
mapBookingToMutationInput,
|
||||
mapRecurringBookingToMutationInput,
|
||||
} from "@calcom/features/bookings/lib";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
|
||||
|
||||
@@ -13,7 +13,12 @@ import type { UseCreateBookingInput } from "./useCreateBooking";
|
||||
|
||||
type UseHandleBookingProps = {
|
||||
bookingForm: UseBookingFormReturnType["bookingForm"];
|
||||
event: useEventReturnType;
|
||||
event?: {
|
||||
data?: Pick<
|
||||
BookerEvent,
|
||||
"id" | "isDynamic" | "metadata" | "recurringEvent" | "length" | "slug" | "schedulingType"
|
||||
> | null;
|
||||
};
|
||||
metadata: Record<string, string>;
|
||||
hashedLink?: string | null;
|
||||
teamMemberEmail?: string;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { shallow } from "zustand/shallow";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||
|
||||
import { useDeleteSelectedSlot } from "./useDeleteSelectedSlot";
|
||||
@@ -12,7 +12,7 @@ import { useReserveSlot } from "./useReserveSlot";
|
||||
|
||||
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
|
||||
|
||||
export const useSlots = (event: useEventReturnType) => {
|
||||
export const useSlots = (event: { data?: Pick<BookerEvent, "id" | "length"> | null }) => {
|
||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
||||
const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
|
||||
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
|
||||
|
||||
@@ -5,9 +5,9 @@ export { BookerPlatformWrapper as Booker } from "./booker";
|
||||
export { useIsPlatform } from "./hooks/useIsPlatform";
|
||||
export { useAtomsContext } from "./hooks/useAtomsContext";
|
||||
export { useConnectedCalendars } from "./hooks/useConnectedCalendars";
|
||||
export { useEventTypesPublic } from "./hooks/event-types/useEventTypesPublic";
|
||||
export { usePublicEvent } from "./hooks/usePublicEvent";
|
||||
export { useGetEventTypeById } from "./hooks/event-types/useGetEventTypeById";
|
||||
export { useEventTypes } from "./hooks/event-types/public/useEventTypes";
|
||||
export { useEventType as useEvent } from "./hooks/event-types/public/useEventType";
|
||||
export { useEventTypeById } from "./hooks/event-types/private/useEventTypeById";
|
||||
export { useCancelBooking } from "./hooks/useCancelBooking";
|
||||
export { useGetBooking } from "./hooks/useGetBooking";
|
||||
export { useGetBookings } from "./hooks/useGetBookings";
|
||||
|
||||
@@ -3,7 +3,7 @@ export { AvailabilitySettingsWebWrapper as AvailabilitySettings } from "./availa
|
||||
export { CalProvider } from "./cal-provider/CalProvider";
|
||||
export { useIsPlatform } from "./hooks/useIsPlatform";
|
||||
export { useAtomsContext } from "./hooks/useAtomsContext";
|
||||
export { useGetEventTypeById } from "./hooks/event-types/useGetEventTypeById";
|
||||
export { useEventTypeById } from "./hooks/event-types/private/useEventTypeById";
|
||||
export { useHandleBookEvent } from "./hooks/useHandleBookEvent";
|
||||
export * as Dialog from "./src/components/ui/dialog";
|
||||
export { Timezone } from "./timezone";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user