From f81074c4f557916f7cd11d47643caaad68dd2957 Mon Sep 17 00:00:00 2001 From: Rajiv Sahal Date: Fri, 16 Aug 2024 03:01:53 +0530 Subject: [PATCH 1/3] feat: destination calendar settings for `CalendarSettings` atom (#16133) Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Morgan Vernay Co-authored-by: Peer Richelsen --- .../outputs/connected-calendars.output.ts | 4 +- ...stination-calendars.controller.e2e-spec.ts | 171 ++++++++++++ .../destination-calendars.controller.ts | 46 ++++ .../destination-calendars.module.ts | 26 ++ .../destination-calendars.repository.ts | 34 +++ .../inputs/destination-calendars.input.ts | 36 +++ .../outputs/destination-calendars.output.ts | 34 +++ .../services/destination-calendars.service.ts | 50 ++++ apps/api/v2/src/modules/endpoints.module.ts | 2 + apps/api/v2/swagger/documentation.json | 100 +++++++ .../components/apps/CalendarListContainer.tsx | 34 +-- .../calendars/DestinationCalendarSelector.tsx | 4 +- .../calendar-settings/CalendarSettings.tsx | 5 - .../CalendarSettingsPlatformWrapper.tsx | 254 ++---------------- .../DestinationCalendar.tsx | 37 +++ .../DestinationCalendarSelector.tsx | 148 ++++++++++ .../atoms/destination-calendar/index.ts | 1 + ...inationCalendarSettingsPlatformWrapper.tsx | 48 ++++ .../DestinationCalendarSettingsWebWrapper.tsx | 31 +++ .../useUpdateDestinationCalendars.ts | 59 ++++ packages/platform/atoms/index.ts | 4 +- .../atoms/lib/getPlaceholderContent.ts | 16 ++ packages/platform/atoms/monorepo.ts | 3 +- .../SelectedCalendarSettings.tsx | 7 + .../platform/atoms/selected-calendar/index.ts | 1 + ...electedCalendarSettingsPlatformWrapper.tsx | 246 +++++++++++++++++ .../SelectedCalendarSettingsWebWrapper.tsx} | 18 +- .../examples/base/src/pages/calendars.tsx | 2 +- 28 files changed, 1138 insertions(+), 283 deletions(-) create mode 100644 apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.e2e-spec.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/destination-calendars.module.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/outputs/destination-calendars.output.ts create mode 100644 apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts delete mode 100644 packages/platform/atoms/calendar-settings/CalendarSettings.tsx create mode 100644 packages/platform/atoms/destination-calendar/DestinationCalendar.tsx create mode 100644 packages/platform/atoms/destination-calendar/DestinationCalendarSelector.tsx create mode 100644 packages/platform/atoms/destination-calendar/index.ts create mode 100644 packages/platform/atoms/destination-calendar/wrappers/DestinationCalendarSettingsPlatformWrapper.tsx create mode 100644 packages/platform/atoms/destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper.tsx create mode 100644 packages/platform/atoms/hooks/calendars/useUpdateDestinationCalendars.ts create mode 100644 packages/platform/atoms/lib/getPlaceholderContent.ts create mode 100644 packages/platform/atoms/selected-calendar/SelectedCalendarSettings.tsx create mode 100644 packages/platform/atoms/selected-calendar/index.ts create mode 100644 packages/platform/atoms/selected-calendar/wrappers/SelectedCalendarSettingsPlatformWrapper.tsx rename packages/platform/atoms/{calendar-settings/wrappers/CalendarSettingsWebWrapper.tsx => selected-calendar/wrappers/SelectedCalendarSettingsWebWrapper.tsx} (91%) diff --git a/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts b/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts index f71a469eeb7cfe..f084caad4a4dce 100644 --- a/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts +++ b/apps/api/v2/src/ee/calendars/outputs/connected-calendars.output.ts @@ -105,7 +105,7 @@ class Primary { credentialId!: number; } -class Calendar { +export class Calendar { @IsEmail() externalId!: string; @@ -135,7 +135,7 @@ class Calendar { credentialId!: number; } -class ConnectedCalendar { +export class ConnectedCalendar { @ValidateNested() @IsObject() integration!: Integration; diff --git a/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.e2e-spec.ts b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.e2e-spec.ts new file mode 100644 index 00000000000000..80e4b7037dc6ef --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.e2e-spec.ts @@ -0,0 +1,171 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { CalendarsService } from "@/ee/calendars/services/calendars.service"; +import { HttpExceptionFilter } from "@/filters/http-exception.filter"; +import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; +import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard"; +import { DestinationCalendarsOutputResponseDto } from "@/modules/destination-calendars/outputs/destination-calendars.output"; +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, Credential } from "@prisma/client"; +import * as request from "supertest"; +import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture"; +import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; +import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; +import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture"; +import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; + +import { APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants"; +import { SUCCESS_STATUS } from "@calcom/platform-constants"; + +const CLIENT_REDIRECT_URI = "http://localhost:5555"; + +describe("Platform Destination Calendar Endpoints", () => { + let app: INestApplication; + + let oAuthClient: PlatformOAuthClient; + let organization: Team; + let userRepositoryFixture: UserRepositoryFixture; + let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let teamRepositoryFixture: TeamRepositoryFixture; + let tokensRepositoryFixture: TokensRepositoryFixture; + let credentialsRepositoryFixture: CredentialsRepositoryFixture; + let appleCalendarCredentials: Credential; + let user: User; + let accessTokenSecret: string; + let refreshTokenSecret: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [PrismaExceptionFilter, HttpExceptionFilter], + imports: [AppModule, UsersModule, TokensModule], + }) + .overrideGuard(PermissionsGuard) + .useValue({ + canActivate: () => true, + }) + + .compile(); + + oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); + tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef); + credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef); + organization = await teamRepositoryFixture.create({ name: "organization" }); + oAuthClient = await createOAuthClient(organization.id); + user = await userRepositoryFixture.createOAuthManagedUser("office365-connect@gmail.com", oAuthClient.id); + const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id); + accessTokenSecret = tokens.accessToken; + refreshTokenSecret = tokens.refreshToken; + appleCalendarCredentials = await credentialsRepositoryFixture.create( + APPLE_CALENDAR_TYPE, + {}, + user.id, + APPLE_CALENDAR_ID + ); + jest.spyOn(CalendarsService.prototype, "getCalendars").mockReturnValue( + Promise.resolve({ + connectedCalendars: [ + { + integration: { + installed: false, + type: "apple_calendar", + title: "", + name: "", + description: "", + variant: "calendar", + slug: "", + locationOption: null, + categories: ["calendar"], + logo: "", + publisher: "", + url: "", + email: "", + }, + calendars: { + externalId: + "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/", + readOnly: false, + integration: "apple_calendar", + credentialId: appleCalendarCredentials.id, + primary: true, + email: user.email, + }, + error: { message: "" }, + }, + ], + }) + ); + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + await app.init(); + }); + + async function createOAuthClient(organizationId: number) { + const data = { + logo: "logo-url", + name: "name", + redirectUris: [CLIENT_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(accessTokenSecret).toBeDefined(); + expect(refreshTokenSecret).toBeDefined(); + expect(user).toBeDefined(); + }); + + it(`POST /v2/destination-calendars: it should respond with a 200 returning back the user updated destination calendar`, async () => { + const body = { + integration: appleCalendarCredentials.type, + externalId: "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/", + }; + + return request(app.getHttpServer()) + .put("/v2/destination-calendars") + .set("Authorization", `Bearer ${accessTokenSecret}`) + .send(body) + .expect(200) + .then(async (response) => { + const responseBody: DestinationCalendarsOutputResponseDto = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.credentialId).toEqual(appleCalendarCredentials.id); + expect(responseBody.data.integration).toEqual(body.integration); + expect(responseBody.data.externalId).toEqual(body.externalId); + expect(responseBody.data.userId).toEqual(user.id); + }); + }); + + it(`POST /v2/destination-calendars: should fail 400 if calendar type is invalid`, async () => { + const body = { + integration: "not-supported-calendar", + externalId: "https://caldav.icloud.com/20961146906/calendars/83C4F9A1-F1D0-41C7-8FC3-0B$9AE22E813/", + }; + + return request(app.getHttpServer()) + .put("/v2/destination-calendars") + .set("Authorization", `Bearer ${accessTokenSecret}`) + .send(body) + .expect(400); + }); + + afterAll(async () => { + await oauthClientRepositoryFixture.delete(oAuthClient.id); + await teamRepositoryFixture.delete(organization.id); + await userRepositoryFixture.deleteByEmail(user.email); + await app.close(); + }); +}); diff --git a/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts new file mode 100644 index 00000000000000..75ada4217b5b07 --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/controllers/destination-calendars.controller.ts @@ -0,0 +1,46 @@ +import { API_VERSIONS_VALUES } from "@/lib/api-versions"; +import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator"; +import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; +import { DestinationCalendarsInputBodyDto } from "@/modules/destination-calendars/inputs/destination-calendars.input"; +import { + DestinationCalendarsOutputDto, + DestinationCalendarsOutputResponseDto, +} from "@/modules/destination-calendars/outputs/destination-calendars.output"; +import { DestinationCalendarsService } from "@/modules/destination-calendars/services/destination-calendars.service"; +import { UserWithProfile } from "@/modules/users/users.repository"; +import { Body, Controller, Put, UseGuards } from "@nestjs/common"; +import { ApiTags as DocsTags } from "@nestjs/swagger"; +import { plainToClass } from "class-transformer"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; + +@Controller({ + path: "/v2/destination-calendars", + version: API_VERSIONS_VALUES, +}) +@DocsTags("Destination-Calendars") +export class DestinationCalendarsController { + constructor(private readonly destinationCalendarsService: DestinationCalendarsService) {} + + @Put("/") + @UseGuards(ApiAuthGuard) + @DocsTags("Select a third party destination calendar where events will be created") + async updateDestinationCalendars( + @Body() input: DestinationCalendarsInputBodyDto, + @GetUser() user: UserWithProfile + ): Promise { + const { integration, externalId } = input; + const updatedDestinationCalendar = await this.destinationCalendarsService.updateDestinationCalendars( + integration, + externalId, + user.id + ); + + return { + status: SUCCESS_STATUS, + data: plainToClass(DestinationCalendarsOutputDto, updatedDestinationCalendar, { + strategy: "excludeAll", + }), + }; + } +} diff --git a/apps/api/v2/src/modules/destination-calendars/destination-calendars.module.ts b/apps/api/v2/src/modules/destination-calendars/destination-calendars.module.ts new file mode 100644 index 00000000000000..0c0c29085a3a60 --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/destination-calendars.module.ts @@ -0,0 +1,26 @@ +import { CalendarsRepository } from "@/ee/calendars/calendars.repository"; +import { CalendarsService } from "@/ee/calendars/services/calendars.service"; +import { AppsRepository } from "@/modules/apps/apps.repository"; +import { CredentialsRepository } from "@/modules/credentials/credentials.repository"; +import { DestinationCalendarsController } from "@/modules/destination-calendars/controllers/destination-calendars.controller"; +import { DestinationCalendarsRepository } from "@/modules/destination-calendars/destination-calendars.repository"; +import { DestinationCalendarsService } from "@/modules/destination-calendars/services/destination-calendars.service"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { UsersRepository } from "@/modules/users/users.repository"; +import { Module } from "@nestjs/common"; + +@Module({ + imports: [PrismaModule], + providers: [ + CalendarsRepository, + CalendarsService, + DestinationCalendarsService, + DestinationCalendarsRepository, + UsersRepository, + CredentialsRepository, + AppsRepository, + ], + controllers: [DestinationCalendarsController], + exports: [DestinationCalendarsRepository], +}) +export class DestinationCalendarsModule {} diff --git a/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts b/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts new file mode 100644 index 00000000000000..412a886bdc0566 --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/destination-calendars.repository.ts @@ -0,0 +1,34 @@ +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class DestinationCalendarsRepository { + constructor(private readonly dbWrite: PrismaWriteService) {} + + async updateCalendar( + integration: string, + externalId: string, + credentialId: number, + userId: number, + primaryEmail: string | null + ) { + return await this.dbWrite.prisma.destinationCalendar.upsert({ + update: { + integration, + externalId, + credentialId, + primaryEmail, + }, + create: { + integration, + externalId, + credentialId, + primaryEmail, + userId, + }, + where: { + userId: userId, + }, + }); + } +} diff --git a/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts b/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts new file mode 100644 index 00000000000000..76cbc16055472b --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/inputs/destination-calendars.input.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Expose } from "class-transformer"; +import { IsString, IsEnum } from "class-validator"; + +import { + APPLE_CALENDAR_TYPE, + GOOGLE_CALENDAR_TYPE, + OFFICE_365_CALENDAR_TYPE, +} from "@calcom/platform-constants"; + +export class DestinationCalendarsInputBodyDto { + @IsString() + @Expose() + @ApiProperty({ + example: APPLE_CALENDAR_TYPE, + description: "The calendar service you want to integrate, as returned by the /calendars endpoint", + enum: [APPLE_CALENDAR_TYPE, GOOGLE_CALENDAR_TYPE, OFFICE_365_CALENDAR_TYPE], + required: true, + }) + @IsEnum([APPLE_CALENDAR_TYPE, GOOGLE_CALENDAR_TYPE, OFFICE_365_CALENDAR_TYPE]) + readonly integration!: + | typeof APPLE_CALENDAR_TYPE + | typeof GOOGLE_CALENDAR_TYPE + | typeof OFFICE_365_CALENDAR_TYPE; + + @IsString() + @Expose() + @ApiProperty({ + example: "https://caldav.icloud.com/26962146906/calendars/1644422A-1945-4438-BBC0-4F0Q23A57R7S/", + description: + "Unique identifier used to represent the specfic calendar, as returned by the /calendars endpoint", + type: "string", + required: true, + }) + readonly externalId!: string; +} diff --git a/apps/api/v2/src/modules/destination-calendars/outputs/destination-calendars.output.ts b/apps/api/v2/src/modules/destination-calendars/outputs/destination-calendars.output.ts new file mode 100644 index 00000000000000..c0ab8e9aba4c56 --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/outputs/destination-calendars.output.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Expose, Type } from "class-transformer"; +import { IsInt, IsString, ValidateNested, IsEnum } from "class-validator"; + +import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants"; + +export class DestinationCalendarsOutputDto { + @IsInt() + @Expose() + readonly userId!: number; + + @IsString() + @Expose() + readonly integration!: string; + + @IsString() + @Expose() + readonly externalId!: string; + + @IsInt() + @Expose() + readonly credentialId!: number | null; +} + +export class DestinationCalendarsOutputResponseDto { + @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) + @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) + status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + @Expose() + @ValidateNested() + @Type(() => DestinationCalendarsOutputDto) + data!: DestinationCalendarsOutputDto; +} diff --git a/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts b/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts new file mode 100644 index 00000000000000..b80fe4beb6d474 --- /dev/null +++ b/apps/api/v2/src/modules/destination-calendars/services/destination-calendars.service.ts @@ -0,0 +1,50 @@ +import { ConnectedCalendar, Calendar } from "@/ee/calendars/outputs/connected-calendars.output"; +import { CalendarsService } from "@/ee/calendars/services/calendars.service"; +import { DestinationCalendarsRepository } from "@/modules/destination-calendars/destination-calendars.repository"; +import { Injectable, NotFoundException } from "@nestjs/common"; + +@Injectable() +export class DestinationCalendarsService { + constructor( + private readonly calendarsService: CalendarsService, + private readonly destinationCalendarsRepository: DestinationCalendarsRepository + ) {} + + async updateDestinationCalendars(integration: string, externalId: string, userId: number) { + const userCalendars = await this.calendarsService.getCalendars(userId); + const allCalendars = userCalendars.connectedCalendars + .map((cal: ConnectedCalendar) => cal.calendars ?? []) + .flat(); + const credentialId = allCalendars.find( + (cal: Calendar) => + cal.externalId === externalId && cal.integration === integration && cal.readOnly === false + )?.credentialId; + + if (!credentialId) { + throw new NotFoundException(`Could not find calendar ${externalId}`); + } + + const primaryEmail = + allCalendars.find((cal: Calendar) => cal.primary && cal.credentialId === credentialId)?.email ?? null; + + const { + integration: updatedCalendarIntegration, + externalId: updatedCalendarExternalId, + credentialId: updatedCalendarCredentialId, + userId: updatedCalendarUserId, + } = await this.destinationCalendarsRepository.updateCalendar( + integration, + externalId, + credentialId, + userId, + primaryEmail + ); + + return { + userId: updatedCalendarUserId, + integration: updatedCalendarIntegration, + externalId: updatedCalendarExternalId, + credentialId: updatedCalendarCredentialId, + }; + } +} diff --git a/apps/api/v2/src/modules/endpoints.module.ts b/apps/api/v2/src/modules/endpoints.module.ts index d4ef222182856a..5fe14276e154fd 100644 --- a/apps/api/v2/src/modules/endpoints.module.ts +++ b/apps/api/v2/src/modules/endpoints.module.ts @@ -1,5 +1,6 @@ import { PlatformEndpointsModule } from "@/ee/platform-endpoints-module"; import { BillingModule } from "@/modules/billing/billing.module"; +import { DestinationCalendarsModule } from "@/modules/destination-calendars/destination-calendars.module"; import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module"; import { TimezoneModule } from "@/modules/timezones/timezones.module"; import type { MiddlewareConsumer, NestModule } from "@nestjs/common"; @@ -16,6 +17,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module"; TimezoneModule, UsersModule, WebhooksModule, + DestinationCalendarsModule, ], }) export class EndpointsModule implements NestModule { diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 3ee2180616d1e5..19507b6b4798fe 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -3794,6 +3794,38 @@ "OAuthClients Webhooks" ] } + }, + "/v2/destination-calendars": { + "put": { + "operationId": "DestinationCalendarsController_updateDestinationCalendars", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DestinationCalendarsInputBodyDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DestinationCalendarsOutputResponseDto" + } + } + } + } + }, + "tags": [ + "Destination-Calendars", + "Select a third party destination calendar where events will be created" + ] + } } }, "info": { @@ -9163,6 +9195,74 @@ "status", "data" ] + }, + "DestinationCalendarsInputBodyDto": { + "type": "object", + "properties": { + "integration": { + "type": "string", + "example": "apple_calendar", + "description": "The calendar service you want to integrate, as returned by the /calendars endpoint", + "enum": [ + "apple_calendar", + "google_calendar", + "office365_calendar" + ] + }, + "externalId": { + "type": "string", + "example": "https://caldav.icloud.com/26962146906/calendars/1644422A-1945-4438-BBC0-4F0Q23A57R7S/", + "description": "Unique identifier used to represent the specfic calendar, as returned by the /calendars endpoint" + } + }, + "required": [ + "integration", + "externalId" + ] + }, + "DestinationCalendarsOutputDto": { + "type": "object", + "properties": { + "userId": { + "type": "number" + }, + "integration": { + "type": "string" + }, + "externalId": { + "type": "string" + }, + "credentialId": { + "type": "number", + "nullable": true + } + }, + "required": [ + "userId", + "integration", + "externalId", + "credentialId" + ] + }, + "DestinationCalendarsOutputResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "success", + "enum": [ + "success", + "error" + ] + }, + "data": { + "$ref": "#/components/schemas/DestinationCalendarsOutputDto" + } + }, + "required": [ + "status", + "data" + ] } } } diff --git a/apps/web/components/apps/CalendarListContainer.tsx b/apps/web/components/apps/CalendarListContainer.tsx index d161cc75c7732b..2c070447b63cb4 100644 --- a/apps/web/components/apps/CalendarListContainer.tsx +++ b/apps/web/components/apps/CalendarListContainer.tsx @@ -1,14 +1,15 @@ import { Fragment, useEffect } from "react"; import { InstallAppButton } from "@calcom/app-store/components"; -import { CalendarSettingsWebWrapper } from "@calcom/atoms/monorepo"; -import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector"; +import { + SelectedCalendarSettingsWebWrapper, + DestinationCalendarSettingsWebWrapper, +} from "@calcom/atoms/monorepo"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Button, EmptyScreen, - Label, List, ShellSubHeading, AppSkeletonLoader as SkeletonLoader, @@ -105,31 +106,8 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding <> {heading && ( <> -
-
-

- {t("add_to_calendar")} -

- -

- {t("add_to_calendar_description")} -

-
-
-
-
- - -
-
-
-
- + ) => { +export const SingleValueComponent = ({ ...props }: SingleValueProps