Compare commits

...
61 Commits
Author SHA1 Message Date
GitHub Actions 695a4d7a59 chore: release v5.1.3
Post release / release (push) Canceled after 0s
2025-03-20 22:05:36 +00:00
Anik Dhabal BabuandTushar Bhatt 9bcea0fa01 replyTo includes the correct email (#20235)
Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
2025-03-20 17:16:12 +00:00
Rajiv Sahal 9a4c8c147b feat: improve trpc exception handling for api v2 (#20158)
* improve trpc exception handling for api v2

* update error messages

* fixup
2025-03-20 16:03:29 +00:00
Gaurav Verma a48ad2219f feat: add aria-labels for accessibility in AddVariablesDropdown and ToolbarPlugin components (#20260) 2025-03-20 15:59:47 +00:00
Udit Takkarandsean-brydon 55a97b71c8 perf: fix circular dependencies (#20121)
* perf: fix circular dependencies

* fix: types

* fix: types

* fix: types

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2025-03-20 15:30:26 +00:00
Udit Takkar b1b8b0135e refactor: event type builder (#19459) 2025-03-20 07:49:14 -07:00
Rajiv SahalandMorgan 08d4c800e9 feat: allow timezones selection in Booker atom via prop (#20044)
* expose props to pass in timezones and booking dry run values from outside

* fix merge conflicts

* update typings

* add props values to test

* fixup: PR feedback

* PR feedback

* fix wrong prop name

* fix merge conflicts

* fixup

* update docs

* update docs examples

* fix props naming

* fix timezone component logic and typo

* extract typings of timeZones

* update typings for Timezone

* update docs

* resolve merge conflicts

* fix typings

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-03-20 14:05:45 +00:00
Vijay efeb0a8f41 fix: repeatedly occurring flaky tests (#20238)
* fix flaky tests

* minor tweak in ooo tests

* chore

* update: fill description at end
2025-03-20 18:46:41 +05:30
Hariom Balhara 207147f5ff fix: Errors seen in some cases when organization is created through webhook (#20249) 2025-03-20 13:02:32 +00:00
Anik Dhabal Babu 6bb8a7c6a9 fix: e2e flake (#20252) 2025-03-20 12:14:51 +00:00
Hariom Balhara aa7f9719c9 Must slugify the slug - in UI and before we use it (#20242) 2025-03-20 11:23:46 +00:00
Carina WollendorferandCarinaWolli e045dcff11 fix: add response id to webhook payload (#20247)
* add responseId to webhook payload

* fix test

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2025-03-20 11:21:30 +00:00
Eunjae Lee 80eb6daf1f fix: case insensitive search on organization member list (#20240) 2025-03-20 10:47:57 +00:00
Benny Joo b0bf274896 perf: Remove packages/ui/index.ts barrel file (#20243)
* remove packages/ui/index.ts

* Update tests
2025-03-20 07:26:12 -03:00
Morgan 4762d475cb chore: get oauth client parans from event-type hosts (#20236) 2025-03-20 08:02:25 +00:00
Peer Richelsen c7d9c63390 added padding bottom so plain chat doenst overlay anything (#20224) 2025-03-20 03:56:16 +00:00
Benny JooandKeith Williams 853f9bc436 perf: do not import from @calcom/ui barrel file (#20184)
* Icon and IconName

* Button and ButtonGroup

* UserAvatar

* AvatarGroup

* Avatar

* WizardLayout

* Dialogs

* EmptyScreen

* showToast and TextField

* Editor

* Skeleton

* Skeleton

* TopBanner and showToast

* Button again

* more

* perf: Remove app-store reference from @calcom/ui

* more

* Fixing types

* Icon

* Fixed casing

* dropdown

* more

* Select

* more

* Badge

* List

* more

* Divider

* more

* fix

* fix type check

* refactor

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix type check

* fix

* fix

* fix

* fix

* more

* more

* more

* more

* add index file to components/command

* fix

* fix

* fix

* fix imports

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix build errors

* fix build errors

* fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-03-19 19:00:55 -03:00
GitHub Actions ca19aa00c1 chore: release v5.1.2
Post release / release (push) Canceled after 0s
2025-03-19 21:59:27 +00:00
Anik Dhabal Babu 467c979aa7 fix: updating the user does not invalidate/refresh the table (#20228) 2025-03-19 20:28:18 +00:00
Hariom Balhara 9f9bf10040 fix: A user exists with that username (#20217) 2025-03-19 14:34:33 +00:00
Lauris Skraucis 8102192987 refactor: v2 booking responses schema (#20030)
* refactor: when rescheduling fallback on attendee if responses invalid

* chore: submit auto generated docs

* fix: slots event type metadata parsing

* fix: booking responses without email with attendee phone

* refactor

* refactor: test

* remove unused import

* refactor

* test: slots variable duration
2025-03-19 16:32:00 +02:00
Pranav PatilandTushar Bhatt 232f9d288c fix: Loading indicator alignment in settings page (#20219)
* Fix: Loading indicator alignment in profile page

* Added animate spin class

---------

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
2025-03-19 14:31:05 +00:00
Anik Dhabal Babu 7dab100382 fix: unskip confirm step if there are multiple location (#20051) 2025-03-19 12:17:49 +00:00
Lauris Skraucis 91d4f1c219 feat: booker atom confirm and back button styling (#20162)
* feat: submitButtonDisabled Booker atom prop

* feat: confirmStep.confirmButton & backButton booker styles

* refactor: rename submitButtonDisabled to confirmButtonDisabled

* feat: implement this feature for skip confirm step

* refactor: naming
2025-03-19 11:30:48 +00:00
MorganandSomay Chauhan a64e7f9c2a fix: set default managed users calendars (#20208)
* fix: set default managed users calendars

* fix: module imports

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
2025-03-19 09:22:28 +00:00
sean-brydonandKeith Williams 3c1297aa72 fix: calcom trpc sessio circle dep (#19893)
* fix trpc session circle dep

* fixes trpc session cirlce dep

* fix relative imports

* fix more imports to use types and not trpc

* fix exports

* Fixed types

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-03-19 05:50:22 -03:00
Vijay 96f7a01c5f fix: few flaky tests in ooo and bookings-list (#20170)
* fix flaky tests

* chore
2025-03-19 08:32:47 +00:00
Udit TakkarandTushar Bhatt a4a05878ef fix: use correct skeleton loader (#20197)
Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
2025-03-19 08:07:22 +00:00
Keith WilliamsandhbjORbj 352d68ae1c perf: Remove app-store reference from @calcom/ui (#20188)
* perf: Remove app-store reference from @calcom/ui

* Fixing types

* Fixed casing

* fix type check

* refactor

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: hbjORbj <sldisek783@gmail.com>
2025-03-19 05:04:02 -03:00
Tushar Bhatt ffd034679c fix: team-update-on-eventTypes-page (#20152) 2025-03-19 07:28:18 +00:00
Morgan d64ae3c901 fix: missing booking reminder reference in cron (#20200) 2025-03-19 07:27:52 +00:00
Hariom Balhara 7250e22257 fix salesforce query (#20196) 2025-03-19 09:23:12 +02:00
Udit Takkar f459e5d9c4 fix: use correct logo (#20194) 2025-03-19 06:29:07 +00:00
Tushar Bhatt 30bb0f1383 fix: logo-sync-issue (#20190) 2025-03-19 04:44:55 +00:00
Abdullah Arif 24c993a3d2 fix(createButton): add scrolling to empty webhook dropdown (#20191)
Added a scroll-bar class and set a max height on DropdownMenuContent to ensure large dropdowns are scrollable, improving the user experience by making all items accessible without expanding beyond the viewport.
2025-03-19 09:54:09 +05:30
GitHub Actions aa4276be5b chore: release v5.1.1
Post release / release (push) Canceled after 0s
2025-03-18 20:45:23 +00:00
Somay ChauhanandKeith Williams 413aba6e52 fix: add documentation to the possible values of :app in conferencing controller (#20164)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-03-18 17:41:35 -03:00
sean-brydon c2547acb3d fix eslint error (#20127) 2025-03-18 20:21:08 +00:00
Benny Joo cc1b6bac4a perf: Remove QueryCell from availability view (#20176) 2025-03-18 17:05:02 -03:00
Udit Takkar d5d90a8b87 fix: sms formatting (#18647) 2025-03-18 16:59:13 -03:00
Joe Au-YeungandKeith Williams 40333c81c5 chore: Add logging to Salesforce (#20167)
* Remove unused param

* Add log when no contacts are created

* Replace console.log with logger

* Add logging around `buildRecordUpdatePayload`

* Add details if field isn't being caught

* Revert change

* Type fixes

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-03-18 19:51:02 +00:00
Tushar Bhatt ff92979311 fix: spacing (#20172) 2025-03-18 17:37:24 +00:00
Anik Dhabal Babu f63edef48d fix: flaky e2e tests (#20168)
* fix: flaky e2e tests

* update

* fix
2025-03-18 22:30:00 +05:30
Rajiv Sahal 37e56ebbf9 dont auto accept user into an org when creating platform org (#20125) 2025-03-18 16:12:26 +00:00
07c38f1f33 test: Add tests for Booker component (#19051)
* feat: Don't allow unavailable slot's booking form to be visible - Send back to slots listing

* Improvements

* feat: Add env variables and strategies to prevent booking failures

- Added new environment variables for configuring slot reservation and availability checks
- Implemented strategies to handle concurrent bookings and slot availability
- Updated Booker component and related files to support new reservation and slot checking mechanisms
- Added README with detailed explanation of booking prevention strategies
- Configured dynamic intervals for slot and reservation queries

* docs: Update Booker README with detailed slot reservation and availability explanation

- Clarified slot reservation behavior when multiple users access the same booking page
- Added details about `getSchedule` refetching strategies and timing
- Explained how slot availability is detected and communicated to users

* Support for skip confirmation form flow

* feat: Enhance slot availability and reservation mechanism

- Refactored slot availability checking to support multiple slots
- Added tentative selected timeslots to improve booking UX
- Updated trpc handler to check availability for multiple slots
- Introduced new store methods for managing tentative slot selections
- Improved slot reservation and availability status tracking

* refactor: Improve slot availability quick check mechanism

- Renamed and restructured slot availability check parameters
- Extracted quick availability checks into a separate hook
- Updated type definitions for slot status and availability checks
- Simplified slot availability logic in Booker and related components
- Maintained cached state for quick availability checks

* Add tests booker

* Support minimu booking check too

* change data-testid

* Rneame

* Fix race condition with uid cookie not set

* fix tests

* Remove unsded variable

* fixup! Merge branch 'main' into dont-allow-booking-form-with-unavaialble-slot

* Fix the unavailability bug when only seconds differ

* Handle column view that doesnt have selected date and also add tests for isTimeSlotAavailable

* Fi ts and unit tests

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmgmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2025-03-18 13:02:20 +01:00
Gene Parmesan Thomas 192238f4bf fix: UID cookie isn't accessible/created when in embed as it is third party context. (#19859)
* [CAL-5240] Embeds: UID cookie isn't accessible/created when in embed as it is third party context.

* comments
2025-03-18 16:22:55 +05:30
Lauris SkraucisandMorgan 94a26031a8 feat: v2 api phone based organization team event types (#20134)
* refactor: input event types service remove unused code

* feat: toggle if email field is hidden

* chore: ensure event type has email or attendee phone field required

* chore: ensure non-org team cant have phone only event type

* chore: ensure users cant have phone only event type

* chore: ensure org team can have phone only event type

* chore: bump libraries

* docs

* refactor: confirm failing test response message

* fix: unit test

* chore: default organizer booking location docs

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2025-03-18 11:34:35 +02:00
Joe Au-Yeung e9f20b1a0b fix: Salesforce - fallback to creating lead (#20137)
* Fallback creating lead

* Add tests

* Type fix

* Type fixes

* Type fix

* Fix tests
2025-03-18 14:29:09 +05:30
Morgan 7cb9e2b99b chore: create org membership should be an upsert apiv2 (#20129) 2025-03-17 21:07:32 +00:00
Morgan dd45d8cdbc fix: don't remind platform users in reminder cron (#20056) 2025-03-17 23:00:25 +02:00
Hariom Balhara 3a181f1dac fix: Salesforce Stricter Website querying to prevent wrong account match during contact owner check (#20133)
* Make website check stricter in contact ownership flow as well

* Add test
2025-03-17 17:34:36 -03:00
Morgan c3abfa51ba chore: stringify body for request interceptor/middleware (#20131)
* chore: stringify body for request interceptor/middleware

* rename jsonBody to jsonBodyString
2025-03-17 19:45:37 +00:00
Alex van Andel 6498af167a refactor: Delete dead code that is not safe to use (#20141) 2025-03-17 17:23:39 +00:00
Keith WilliamsandhbjORbj 30dce7bacd perf: Move apps procedures off of loggedInViewer (#20112)
* perf: Slim down loggedInViewer tRPC router

* Fixed trpc client calls for new routes

* Moved bookingUnconfirmedCount

* Moved getUserTopBanners to me router

* Moved shouldVerifyEmail to me router

* Moved i18n from public to its own router

* fix ssrInit

* fix ssrInit usage

* fix type check

* better naming

* fix

* fix

* Fix types

* Removed used of importHandler

* perf: Move apps procedures off of loggedInViewer

* Moved 2 more apps-related procedures

---------

Co-authored-by: hbjORbj <sldisek783@gmail.com>
2025-03-17 17:18:19 +00:00
Keith Williams 3be9e1bd38 perf: Remove slots router from publicViewer (#20128) 2025-03-17 09:28:40 -07:00
Lauris Skraucis db52f44293 fix: atoms making requests to customers' domains (#20084)
* refactor: centralize useSchedule hook with isInit

* refactor: centralize useSchedules hook with isInit

* refactor: useConnectedCalendars enabled if isInit

* chore: some type fixes

* chore: type fix

* fix: import paths

* driveby: oauth clients docs
2025-03-17 10:43:39 +02:00
Keith WilliamsandhbjORbj 20d6eb5200 perf: Slim down loggedInViewer tRPC router (#20111)
* perf: Slim down loggedInViewer tRPC router

* Fixed trpc client calls for new routes

* Moved bookingUnconfirmedCount

* Moved getUserTopBanners to me router

* Moved shouldVerifyEmail to me router

* Moved i18n from public to its own router

* fix ssrInit

* fix ssrInit usage

* fix type check

* better naming

* fix

* fix

* Fix types

* Removed used of importHandler

---------

Co-authored-by: hbjORbj <sldisek783@gmail.com>
2025-03-16 18:22:48 -07:00
Keith Williams 424d94f16d feat: Use Watchlist on sending email verifications (#20114) 2025-03-16 14:20:20 +00:00
GitHub Actions 73e7d6f986 chore: release v5.1.0
Post release / release (push) Canceled after 0s
2025-03-15 11:11:37 +00:00
Alex van Andel 145001d26e chore: Bump typescript version in @calcom/ui (#19766)
* chore: Bump typescript version in @calcom/ui

* Reduce required types from @calcom/types to just used ones
2025-03-15 06:43:05 -03:00
5d2d7ce979 fix: German translation for seats_availability text (#20086)
* Fix German translation for seats_availability text

* enhancements

---------

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
Co-authored-by: Tushar <tusharbhatt0135@gmail.com>
2025-03-15 08:12:04 +00:00
1012 changed files with 6658 additions and 3005 deletions
+8
View File
@@ -0,0 +1,8 @@
undecided:
- calcom-monorepo
- "@calcom/app-store-cli"
- "@calcom/platform-constants"
- "@calcom/platform-enums"
- "@calcom/platform-types"
- "@calcom/platform-utils"
- "@calcom/prisma"
+6
View File
@@ -0,0 +1,6 @@
undecided:
- "@calcom/app-store-cli"
- "@calcom/platform-constants"
- "@calcom/platform-enums"
- "@calcom/platform-types"
- "@calcom/platform-utils"
View File
+3
View File
@@ -0,0 +1,3 @@
undecided:
- calcom-monorepo
- "@calcom/prisma"
+1 -1
View File
@@ -38,7 +38,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.151",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.152",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -1,15 +1,20 @@
import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller";
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
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 { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { BillingModule } from "@/modules/billing/billing.module";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UsersModule } from "@/modules/users/users.module";
@@ -33,6 +38,11 @@ import { Module } from "@nestjs/common";
ApiKeysRepository,
OAuthClientUsersService,
PlatformBookingsService,
CalendarsService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
SelectedCalendarsRepository,
],
controllers: [BookingsController_2024_04_15],
})
@@ -4,19 +4,24 @@ import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bo
import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service";
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
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 { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { BillingModule } from "@/modules/billing/billing.module";
import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module";
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UsersModule } from "@/modules/users/users.module";
@@ -47,6 +52,11 @@ import { Module } from "@nestjs/common";
BookingSeatRepository,
ApiKeysRepository,
PlatformBookingsService,
CalendarsService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
SelectedCalendarsRepository,
],
controllers: [BookingsController_2024_08_13],
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
@@ -434,7 +434,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
}
describe("get individual booking", () => {
it("should should get a seated booking with split name responses", async () => {
it("should get a seated booking with split name responses", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${bookingWithSplitName.uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -457,7 +457,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get a seated booking with split name responses", async () => {
it("should get a seated booking with split name responses", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${seatedBookingWithSplitName.uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1,6 +1,7 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
@@ -31,6 +32,7 @@ import {
RecurringBookingOutput_2024_08_13,
GetBookingsOutput_2024_08_13,
GetSeatedBookingOutput_2024_08_13,
RescheduleBookingInput_2024_08_13,
} from "@calcom/platform-types";
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
@@ -66,6 +68,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
const team2EventTypeSlug = `team-bookings-event-type-${randomString()}`;
const phoneOnlyEventTypeSlug = `team-bookings-event-type-${randomString()}`;
let phoneBasedBooking: BookingOutput_2024_08_13;
beforeAll(async () => {
const moduleRef = await withApiAuth(
teamUserEmail,
@@ -408,12 +412,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
it("should create a phone based booking", async () => {
const phoneNumber = "+919876543210";
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(),
eventTypeId: phoneOnlyEventTypeId,
attendee: {
name: "alice",
phoneNumber: "+919876543210",
phoneNumber,
timeZone: "Europe/Madrid",
language: "es",
},
@@ -453,6 +458,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
expect(data.meetingUrl).toEqual(body.meetingUrl);
expect(data.absentHost).toEqual(false);
expect(data.bookingFieldsResponses.attendeePhoneNumber).toEqual(phoneNumber);
phoneBasedBooking = data;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibly recurring bookings"
@@ -523,7 +530,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
describe("get team bookings", () => {
it("should should get bookings by teamId", async () => {
it("should get bookings by teamId", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?teamId=${team1.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -542,7 +549,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by teamId", async () => {
it("should get bookings by teamId", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?teamId=${team2.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -598,7 +605,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by teamIds", async () => {
it("should get bookings by teamIds", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?teamIds=${team1.id},${team2.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -619,6 +626,59 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
describe("reschedule", () => {
it("should reschedule phone based booking", async () => {
const body: RescheduleBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2035, 0, 8, 14, 0, 0)).toISOString(),
reschedulingReason: "Flying to mars that day",
};
return request(app.getHttpServer())
.post(`/v2/bookings/${phoneBasedBooking.uid}/reschedule`)
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsBooking(responseBody.data)).toBe(true);
if (responseDataIsBooking(responseBody.data)) {
const data: BookingOutput_2024_08_13 = responseBody.data;
expect(data.id).toBeDefined();
expect(data.uid).toBeDefined();
expect(data.hosts.length).toEqual(1);
expect(data.hosts[0].id).toEqual(teamUser.id);
expect(data.status).toEqual("accepted");
expect(data.start).toEqual(body.start);
expect(data.end).toEqual(new Date(Date.UTC(2035, 0, 8, 14, 15, 0)).toISOString());
expect(data.duration).toEqual(15);
expect(data.eventTypeId).toEqual(phoneOnlyEventTypeId);
expect(data.attendees.length).toEqual(1);
expect(data.attendees[0]).toEqual({
name: phoneBasedBooking.attendees[0].name,
email: phoneBasedBooking.attendees[0].email,
phoneNumber: phoneBasedBooking.attendees[0].phoneNumber,
timeZone: phoneBasedBooking.attendees[0].timeZone,
language: phoneBasedBooking.attendees[0].language,
absent: false,
});
expect(data.meetingUrl).toEqual(phoneBasedBooking.meetingUrl);
expect(data.absentHost).toEqual(false);
expect(data.bookingFieldsResponses.attendeePhoneNumber).toEqual(
phoneBasedBooking.bookingFieldsResponses.attendeePhoneNumber
);
phoneBasedBooking = data;
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibly recurring bookings"
);
}
});
});
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
@@ -527,7 +527,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
describe("get individual booking", () => {
it("should should get a booking", async () => {
it("should get a booking", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${createdBooking.uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -562,7 +562,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get a booking with rating", async () => {
it("should get a booking with rating", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${bookingInThePast.uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -586,7 +586,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get 1 recurrence of a recurring booking", async () => {
it("should get 1 recurrence of a recurring booking", async () => {
const recurrenceUid = createdRecurringBooking[0].uid;
return request(app.getHttpServer())
.get(`/v2/bookings/${recurrenceUid}`)
@@ -622,7 +622,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get all recurrences of the recurring bookings", async () => {
it("should get all recurrences of the recurring bookings", async () => {
const recurringBookingUid = createdRecurringBooking[0].recurringBookingUid;
return request(app.getHttpServer())
.get(`/v2/bookings/${recurringBookingUid}`)
@@ -691,7 +691,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
describe("get bookings", () => {
it("should should get all bookings", async () => {
it("should get all bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -709,7 +709,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should take bookings", async () => {
it("should take bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?take=3`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -727,7 +727,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should skip bookings", async () => {
it("should skip bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?skip=2`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -745,7 +745,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get upcoming bookings", async () => {
it("should get upcoming bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?status=upcoming`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -763,7 +763,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get past bookings", async () => {
it("should get past bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?status=past`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -781,7 +781,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get upcoming and past bookings", async () => {
it("should get upcoming and past bookings", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?status=upcoming,past`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -799,7 +799,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get recurring booking recurrences", async () => {
it("should get recurring booking recurrences", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?status=recurring`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -817,7 +817,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by attendee email", async () => {
it("should get bookings by attendee email", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?attendeeEmail=mr_proper@gmail.com`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -835,7 +835,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by attendee name", async () => {
it("should get bookings by attendee name", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?attendeeName=Mr Proper Recurring`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -853,7 +853,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by eventTypeId", async () => {
it("should get bookings by eventTypeId", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -871,7 +871,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by eventTypeIds", async () => {
it("should get bookings by eventTypeIds", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeIds=${eventTypeId},${recurringEventTypeId}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -889,7 +889,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by after specified start time", async () => {
it("should get bookings by after specified start time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?afterStart=${createdRecurringBooking[1].start}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -907,7 +907,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings by before specified end time", async () => {
it("should get bookings by before specified end time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?beforeEnd=${createdRecurringBooking[0].end}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -925,7 +925,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings after specified update time", async () => {
it("should get bookings after specified update time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[1].updatedAt}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -943,7 +943,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings before specified update time", async () => {
it("should get bookings before specified update time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?beforeUpdatedAt=${createdRecurringBooking[0].updatedAt}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -961,7 +961,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings in update range", async () => {
it("should get bookings in update range", async () => {
return request(app.getHttpServer())
.get(
`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[0].updatedAt}&beforeUpdatedAt=${createdRecurringBooking[2].updatedAt}`
@@ -982,7 +982,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
describe("createdAt filter", () => {
it("should should get bookings after specified createdAt time", async () => {
it("should get bookings after specified createdAt time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?afterCreatedAt=${createdRecurringBooking[1].createdAt}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1000,7 +1000,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings before specified createdAt time", async () => {
it("should get bookings before specified createdAt time", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?beforeCreatedAt=${createdRecurringBooking[0].createdAt}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1018,7 +1018,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should get bookings in createdAt range", async () => {
it("should get bookings in createdAt range", async () => {
return request(app.getHttpServer())
.get(
`/v2/bookings?afterCreatedAt=${createdRecurringBooking[0].createdAt}&beforeCreatedAt=${createdRecurringBooking[1].createdAt}`
@@ -1039,7 +1039,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by start in descending order", async () => {
it("should sort bookings by start in descending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortStart=desc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1059,7 +1059,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by start in ascending order", async () => {
it("should sort bookings by start in ascending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortStart=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1079,7 +1079,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by end in descending order", async () => {
it("should sort bookings by end in descending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortEnd=desc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1099,7 +1099,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by end in ascending order", async () => {
it("should sort bookings by end in ascending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortEnd=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1119,7 +1119,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by created in descending order", async () => {
it("should sort bookings by created in descending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortCreated=desc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1139,7 +1139,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by created in ascending order", async () => {
it("should sort bookings by created in ascending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortCreated=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1159,7 +1159,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by updated in descending order", async () => {
it("should sort bookings by updated in descending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=desc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1179,7 +1179,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should should sort bookings by updated in ascending order", async () => {
it("should sort bookings by updated in ascending order", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=asc`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -1201,7 +1201,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
describe("reschedule bookings", () => {
it("should should reschedule normal booking", async () => {
it("should reschedule normal booking", async () => {
const body: RescheduleBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2035, 0, 8, 14, 0, 0)).toISOString(),
reschedulingReason: "Flying to mars that day",
@@ -1949,6 +1949,95 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
describe("rescheduling booking with invalid booking responses", () => {
it("should reschedule", async () => {
const email = `user-bookings-attendee-${randomString(10)}@gmail.com`;
const username = `user-bookings-attendee-${randomString(10)}`;
const locale = "it";
const timeZone = "Europe/Rome";
const attendee = await userRepositoryFixture.create({
email,
username,
locale,
timeZone,
});
const booking = await bookingsRepositoryFixture.create({
uid: `booking-uid-${randomString(10)}`,
title: "booking title",
startTime: "2060-09-05T11:00:00.000Z",
endTime: "2060-09-05T12:00:00.000Z",
eventType: {
connect: {
id: eventTypeId,
},
},
status: "ACCEPTED",
metadata: {},
responses: "null",
user: {
connect: {
id: user.id,
},
},
attendees: {
create: {
email: email,
name: username,
timeZone,
locale,
},
},
});
const body: RescheduleBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2060, 0, 9, 14, 0, 0)).toISOString(),
reschedulingReason: "Flying to mars that day",
};
return request(app.getHttpServer())
.post(`/v2/bookings/${booking.uid}/reschedule`)
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const data: BookingOutput_2024_08_13 = responseBody.data;
expect(data.reschedulingReason).toEqual(body.reschedulingReason);
expect(data.start).toEqual(body.start);
expect(data.end).toEqual(new Date(Date.UTC(2060, 0, 9, 15, 0, 0)).toISOString());
expect(data.rescheduledFromUid).toEqual(booking.uid);
expect(data.id).toBeDefined();
expect(data.uid).toBeDefined();
expect(data.hosts[0].id).toEqual(user.id);
expect(data.hosts[0].username).toEqual(user.username);
expect(data.hosts[0].email).toEqual(user.email);
expect(data.status).toEqual("accepted");
expect(data.eventTypeId).toEqual(booking.eventTypeId);
expect(data.attendees.length).toEqual(1);
expect(data.attendees[0]).toEqual({
name: attendee.username,
email: attendee.email,
timeZone: attendee.timeZone,
language: attendee.locale,
absent: false,
});
expect(data.bookingFieldsResponses).toEqual({
name: attendee.username,
email: attendee.email,
rescheduledReason: body.reschedulingReason,
guests: [],
});
await bookingsRepositoryFixture.deleteById(data.id);
await userRepositoryFixture.delete(attendee.id);
});
});
});
function responseDataIsRecurranceBooking(data: any): data is RecurringBookingOutput_2024_08_13 {
return (
!Array.isArray(data) &&
@@ -7,6 +7,8 @@ import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
import { defaultBookingResponses } from "@/lib/safe-parse/default-responses-booking";
import { safeParse } from "@/lib/safe-parse/safe-parse";
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
@@ -41,7 +43,7 @@ import {
RescheduleSeatedBookingInput_2024_08_13,
} from "@calcom/platform-types";
import { BookingInputLocation_2024_08_13 } from "@calcom/platform-types/bookings/2024-08-13/inputs/location.input";
import { EventType, PlatformOAuthClient } from "@calcom/prisma/client";
import { EventType } from "@calcom/prisma/client";
type BookingRequest = NextApiRequest & { userId: number | undefined } & OAuthRequestParams;
@@ -429,6 +431,7 @@ export class InputBookingsService_2024_08_13 {
...(inputBooking.bookingFieldsResponses || {}),
name: inputBooking.attendee.name,
email: attendeeEmail,
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
guests,
location,
},
@@ -567,8 +570,19 @@ export class InputBookingsService_2024_08_13 {
throw new NotFoundException(`Event type with id=${booking.eventTypeId} not found`);
}
const bookingResponses = bookingResponsesSchema.parse(booking.responses);
const attendee = booking.attendees.find((attendee) => attendee.email === bookingResponses.email);
const bookingResponses = safeParse(
bookingResponsesSchema,
booking.responses,
defaultBookingResponses,
false
);
const bookingResponsesMissing =
bookingResponses.name === defaultBookingResponses.name &&
bookingResponses.email === defaultBookingResponses.email;
const attendee = bookingResponsesMissing
? booking.attendees[0]
: booking.attendees.find((attendee) => attendee.email === bookingResponses.email);
if (!attendee) {
throw new NotFoundException(
@@ -576,6 +590,12 @@ export class InputBookingsService_2024_08_13 {
);
}
if (bookingResponsesMissing) {
bookingResponses.name = attendee.name;
bookingResponses.email = attendee.email;
bookingResponses.attendeePhoneNumber = attendee.phoneNumber || undefined;
}
const startTime = DateTime.fromISO(inputBooking.start, { zone: "utc" }).setZone(attendee.timeZone);
const endTime = startTime.plus({ minutes: eventType.length });
@@ -27,6 +27,7 @@ import { Booking, BookingSeat } from "@calcom/prisma/client";
export const bookingResponsesSchema = z
.object({
email: z.string(),
attendeePhoneNumber: z.string().optional(),
name: z.union([
z.string(),
z.object({
@@ -45,6 +46,7 @@ export const seatedBookingDataSchema = z
responses: z
.object({
email: z.string(),
attendeePhoneNumber: z.string().optional(),
name: z.union([
z.string(),
z.object({
@@ -42,6 +42,10 @@ export class PlatformBookingsService {
} else if (eventType?.teamId) {
oAuthClient = await this.oAuthClientRepository.getByTeamId(eventType.teamId);
}
// Last resort check the hosts of the event-type
if (!oAuthClient && eventType?.teamId) {
oAuthClient = await this.oAuthClientRepository.getByEventTypeHosts(eventTypeId);
}
if (oAuthClient) {
return {
@@ -116,6 +116,7 @@ describe("Event types Endpoints", () => {
slug: "email",
required: true,
disableOnPrefill: false,
hidden: false,
};
const defaultResponseBookingFieldLocation = {
@@ -336,6 +337,46 @@ describe("Event types Endpoints", () => {
.expect(404);
});
it("should not be able to create phone-only event type", async () => {
const body: CreateEventTypeInput_2024_06_14 = {
title: "Phone coding consultation",
slug: "phone-coding-consultation",
description: "Our team will review your codebase.",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
bookingFields: [
{
type: "email",
required: false,
label: "Email",
hidden: true,
},
{
type: "phone",
slug: "attendeePhoneNumber",
required: true,
label: "Phone number",
hidden: false,
},
],
};
const response = await request(app.getHttpServer())
.post("/api/v2/event-types")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(400);
expect(response.body.error.message).toBe(
"checkIsEmailUserAccessible - Email booking field must be required and visible"
);
});
it("should create an event type", async () => {
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
type: "name",
@@ -1191,7 +1232,14 @@ describe("Event types Endpoints", () => {
const expectedReturnSystemFields = [
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
{
isDefault: true,
required: true,
slug: "email",
type: "email",
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "radioInput",
@@ -1644,6 +1692,7 @@ describe("Event types Endpoints", () => {
slug: "email",
required: true,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
@@ -14,6 +14,8 @@ import {
updateEventType,
getEventTypesPublic,
EventTypesPublic,
SystemField,
CustomField,
} from "@calcom/platform-libraries/event-types";
import { GetEventTypesQuery_2024_06_14, InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
import { EventType } from "@calcom/prisma/client";
@@ -31,6 +33,9 @@ export class EventTypesService_2024_06_14 {
) {}
async createUserEventType(user: UserWithProfile, body: InputEventTransformed_2024_06_14) {
if (body.bookingFields) {
this.checkHasUserAccessibleEmailBookingField(body.bookingFields);
}
await this.checkCanCreateEventType(user.id, body);
const eventTypeUser = await this.getUserToCreateEvent(user);
const { destinationCalendar: _destinationCalendar, ...rest } = body;
@@ -78,6 +83,16 @@ export class EventTypesService_2024_06_14 {
await this.checkUserOwnsSchedule(userId, body.scheduleId);
}
checkHasUserAccessibleEmailBookingField(bookingFields: (SystemField | CustomField)[]) {
const emailField = bookingFields.find((field) => field.type === "email" && field.name === "email");
const isEmailFieldRequiredAndVisible = emailField?.required && !emailField?.hidden;
if (!isEmailFieldRequiredAndVisible) {
throw new BadRequestException(
"checkIsEmailUserAccessible - Email booking field must be required and visible"
);
}
}
async getEventTypeByUsernameAndSlug(
username: string,
eventTypeSlug: string,
@@ -219,6 +234,9 @@ export class EventTypesService_2024_06_14 {
body: Partial<InputEventTransformed_2024_06_14>,
user: UserWithProfile
) {
if (body.bookingFields) {
this.checkHasUserAccessibleEmailBookingField(body.bookingFields);
}
await this.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
const eventTypeUser = await this.getUserToUpdateEvent(user);
@@ -37,8 +37,6 @@ import {
} from "@calcom/platform-types";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import { OutputEventTypesService_2024_06_14 } from "./output-event-types.service";
interface ValidationContext {
eventTypeId?: number;
seatsPerTimeSlot?: number | null;
@@ -51,7 +49,6 @@ interface ValidationContext {
export class InputEventTypesService_2024_06_14 {
constructor(
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
private readonly outputEventTypesService: OutputEventTypesService_2024_06_14,
private readonly calendarsService: CalendarsService
) {}
@@ -128,12 +125,11 @@ export class InputEventTypesService_2024_06_14 {
} = inputEventType;
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
const hasMultipleLocations = (locations || defaultLocations).length > 1;
const eventType = {
...rest,
length: lengthInMinutes,
locations: this.transformInputLocations(locations || defaultLocations),
bookingFields: this.transformInputBookingFields(bookingFields, hasMultipleLocations),
bookingFields: this.transformInputBookingFields(bookingFields),
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? this.transformInputIntervalLimits(bookingLimitsDuration)
@@ -182,15 +178,12 @@ export class InputEventTypesService_2024_06_14 {
: {};
const confirmationPolicyTransformed = this.transformInputConfirmationPolicy(confirmationPolicy);
const hasMultipleLocations = !!(locations && locations?.length > 1);
const eventType = {
...rest,
length: lengthInMinutes,
locations: locations ? this.transformInputLocations(locations) : undefined,
bookingFields: bookingFields
? this.transformInputBookingFields(bookingFields, hasMultipleLocations)
: undefined,
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
durationLimits: bookingLimitsDuration
? this.transformInputIntervalLimits(bookingLimitsDuration)
@@ -220,10 +213,7 @@ export class InputEventTypesService_2024_06_14 {
return transformLocationsApiToInternal(inputLocations);
}
transformInputBookingFields(
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
hasMultipleLocations: boolean
) {
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
const internalFields: (SystemField | CustomField)[] = inputBookingFields
? transformBookingFieldsApiToInternal(inputBookingFields)
: [];
@@ -252,7 +242,28 @@ export class InputEventTypesService_2024_06_14 {
systemCustomRescheduleReasonField || systemAfterFieldRescheduleReason,
];
return [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
const bookingFields = [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
if (!this.hasEmailOrPhoneOnlySetup(bookingFields)) {
throw new BadRequestException(
"Booking fields validation failed: visible and required email or visible and required attendee phone field is needed."
);
}
return bookingFields;
}
hasEmailOrPhoneOnlySetup(bookingFields: (SystemField | CustomField)[]) {
const emailField = bookingFields.find((field) => field.type === "email" && field.name === "email");
const attendeePhoneNumberField = bookingFields.find(
(field) => field.type === "phone" && field.name === "attendeePhoneNumber"
);
const isEmailFieldRequiredAndVisible = emailField?.required && !emailField?.hidden;
const isAttendeePhoneNumberFieldRequiredAndVisible =
attendeePhoneNumberField?.required && !attendeePhoneNumberField?.hidden;
return isEmailFieldRequiredAndVisible || isAttendeePhoneNumberFieldRequiredAndVisible;
}
isUserCustomField(field: SystemField | CustomField): field is CustomField {
@@ -16,29 +16,74 @@ export class TRPCExceptionFilter implements ExceptionFilter {
const request = ctx.getRequest<Request>();
let statusCode = 500;
let errorMessage = exception.message;
switch (exception.code) {
case "UNAUTHORIZED":
statusCode = 401;
errorMessage = "You are not authorized to access this resource";
break;
case "FORBIDDEN":
statusCode = 403;
errorMessage = "You don't have necessary permissions to access this resource";
break;
case "NOT_FOUND":
statusCode = 404;
errorMessage = "The requested resource was not found";
break;
case "INTERNAL_SERVER_ERROR":
statusCode = 500;
errorMessage = "An unexpected error occurred on the server. Please try again later";
break;
case "BAD_REQUEST":
statusCode = 400;
errorMessage = "Bad request: Please check your request data and try again";
break;
case "CONFLICT":
statusCode = 409;
errorMessage = "Could not process the request due to a resource conflict with the current state";
break;
case "TOO_MANY_REQUESTS":
statusCode = 429;
errorMessage = "You have exceeded the allowed number of requests";
break;
case "PAYLOAD_TOO_LARGE":
statusCode = 413;
errorMessage = "The request payload is too large";
break;
case "CLIENT_CLOSED_REQUEST":
statusCode = 499;
errorMessage =
"The client closed the connection before the server could finish processing the request";
break;
case "METHOD_NOT_SUPPORTED":
statusCode = 405;
errorMessage = "The requested method is not supported for this resource";
case "NOT_IMPLEMENTED":
statusCode = 501;
errorMessage = "The requested method is not implemented on the server";
break;
case "PRECONDITION_FAILED":
statusCode = 412;
errorMessage =
"The server does not meet one of the preconditions that the requester put on the request";
break;
case "TIMEOUT":
statusCode = 408;
errorMessage = "The request took too long to complete. Please try again later";
break;
case "UNPROCESSABLE_CONTENT":
statusCode = 422;
errorMessage =
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect";
break;
case "PARSE_ERROR":
statusCode = 400;
errorMessage = "The request could not be parsed due to invalid syntax.";
break;
default:
statusCode = 500;
errorMessage = exception.message;
break;
}
@@ -57,7 +102,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
status: ERROR_STATUS,
timestamp: new Date().toISOString(),
path: request.url,
error: { code: exception.name, message: exception.message },
error: { code: exception.name, message: errorMessage },
});
}
}
@@ -2,7 +2,7 @@ export const defaultBookingResponses = {
name: "unknown",
email: "unknown",
guests: [],
rescheduleReason: "unknown",
rescheduledReason: "unknown",
};
export const defaultBookingMetadata = { videoCallUrl: "unknown" };
@@ -21,13 +21,23 @@ export class ResponseInterceptor implements NestInterceptor {
const { statusCode } = response;
const responseTime = Date.now() - startTime;
let jsonBodyString = "{}";
try {
if (data && typeof data === "object") {
jsonBodyString = JSON.stringify(data);
}
} catch (err) {
this.logger.error("Could not parse request body");
}
this.logger.log("Outgoing Response", {
requestId,
method,
url,
statusCode,
responseTime,
responseBody: data,
responseBody: jsonBodyString,
timestamp: new Date().toISOString(),
});
})
@@ -11,13 +11,22 @@ export class RequestIdMiddleware implements NestMiddleware {
const requestId = uuid();
req.headers["X-Request-Id"] = requestId;
const { method, headers, body: requestBody, baseUrl } = req;
let jsonBodyString = "{}";
try {
if (requestBody && typeof requestBody === "object") {
jsonBodyString = JSON.stringify(requestBody);
}
} catch (err) {
this.logger.error("Could not parse request body");
}
this.logger.log("Incoming Request", {
requestId,
method,
url: baseUrl,
headers: filterReqHeaders(headers),
requestBody,
requestBody: jsonBodyString,
timestamp: new Date().toISOString(),
});
@@ -37,11 +37,11 @@ import {
Req,
HttpException,
} from "@nestjs/common";
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { ApiOperation, ApiParam, ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToInstance } from "class-transformer";
import { Request } from "express";
import { GOOGLE_MEET, ZOOM, SUCCESS_STATUS, OFFICE_365_VIDEO } from "@calcom/platform-constants";
import { GOOGLE_MEET, ZOOM, SUCCESS_STATUS, OFFICE_365_VIDEO, CAL_VIDEO } from "@calcom/platform-constants";
export type OAuthCallbackState = {
accessToken: string;
@@ -71,6 +71,12 @@ export class ConferencingController {
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Connect your conferencing application" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [GOOGLE_MEET],
required: true,
})
async connect(
@GetUser("id") userId: number,
@Param("app") app: string
@@ -93,6 +99,12 @@ export class ConferencingController {
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Get OAuth conferencing app auth url" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [ZOOM, OFFICE_365_VIDEO],
required: true,
})
async redirect(
@Req() req: Request,
@Headers("Authorization") authorization: string,
@@ -138,6 +150,12 @@ export class ConferencingController {
@UseGuards()
@Redirect(undefined, 301)
@ApiOperation({ summary: "conferencing apps oauths callback" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [ZOOM, OFFICE_365_VIDEO],
required: true,
})
async save(
@Query("state") state: string,
@Param("app") app: string,
@@ -203,6 +221,12 @@ export class ConferencingController {
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Set your default conferencing application" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [GOOGLE_MEET, ZOOM, OFFICE_365_VIDEO, CAL_VIDEO],
required: true,
})
async default(
@GetUser() user: UserWithProfile,
@Param("app") app: string
@@ -224,6 +248,12 @@ export class ConferencingController {
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard)
@ApiOperation({ summary: "Disconnect your conferencing application" })
@ApiParam({
name: "app",
description: "Conferencing application type",
enum: [GOOGLE_MEET, ZOOM, OFFICE_365_VIDEO],
required: true,
})
async disconnect(
@GetUser() user: UserWithProfile,
@Param("app") app: string
@@ -29,19 +29,12 @@ import {
NotFoundException,
BadRequestException,
} from "@nestjs/common";
import {
ApiOperation as DocsOperation,
ApiCreatedResponse as DocsCreatedResponse,
ApiTags,
} from "@nestjs/swagger";
import { ApiCreatedResponse as DocsCreatedResponse, ApiTags } from "@nestjs/swagger";
import { User, MembershipRole } from "@prisma/client";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { CreateOAuthClientInput, UpdateOAuthClientInput, Pagination } from "@calcom/platform-types";
const AUTH_DOCUMENTATION = `⚠️ First, this endpoint requires \`Cookie: next-auth.session-token=eyJhbGciOiJ\` header. Log into Cal web app using owner of organization that was created after visiting \`/settings/organizations/new\`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.
Second, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.`;
@Controller({
path: "/v2/oauth-clients",
version: API_VERSIONS_VALUES,
@@ -61,7 +54,6 @@ export class OAuthClientsController {
@Post("/")
@HttpCode(HttpStatus.CREATED)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
@DocsCreatedResponse({
description: "Create an OAuth client",
type: CreateOAuthClientResponseDto,
@@ -90,7 +82,6 @@ export class OAuthClientsController {
@Get("/")
@HttpCode(HttpStatus.OK)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
async getOAuthClients(@GetOrgId() organizationId: number): Promise<GetOAuthClientsResponseDto> {
const clients = await this.oAuthClientsService.getOAuthClients(organizationId);
return { status: SUCCESS_STATUS, data: clients };
@@ -99,7 +90,6 @@ export class OAuthClientsController {
@Get("/:clientId")
@HttpCode(HttpStatus.OK)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
@UseGuards(OAuthClientGuard)
async getOAuthClientById(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
const client = await this.oAuthClientsService.getOAuthClientById(clientId);
@@ -109,7 +99,6 @@ export class OAuthClientsController {
@Get("/:clientId/managed-users")
@HttpCode(HttpStatus.OK)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
@UseGuards(OAuthClientGuard)
async getOAuthClientManagedUsersById(
@Param("clientId") clientId: string,
@@ -128,7 +117,6 @@ export class OAuthClientsController {
@Patch("/:clientId")
@HttpCode(HttpStatus.OK)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
@UseGuards(OAuthClientGuard)
async updateOAuthClient(
@Param("clientId") clientId: string,
@@ -142,7 +130,6 @@ export class OAuthClientsController {
@Delete("/:clientId")
@HttpCode(HttpStatus.OK)
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
@UseGuards(OAuthClientGuard)
async deleteOAuthClient(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
this.logger.log(`Deleting OAuth Client with ID: ${clientId}`);
@@ -1,7 +1,11 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
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 { AppsRepository } from "@/modules/apps/apps.repository";
import { AuthModule } from "@/modules/auth/auth.module";
import { BillingModule } from "@/modules/billing/billing.module";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { OAuthClientUsersController } from "@/modules/oauth-clients/controllers/oauth-client-users/oauth-client-users.controller";
import { OAuthClientsController } from "@/modules/oauth-clients/controllers/oauth-clients/oauth-clients.controller";
@@ -16,6 +20,7 @@ import { OrganizationsModule } from "@/modules/organizations/organizations.modul
import { OrganizationsTeamsService } from "@/modules/organizations/teams/index/services/organizations-teams.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
@@ -42,6 +47,11 @@ import { JwtService } from "@nestjs/jwt";
OAuthClientRepository,
TokensRepository,
OAuthFlowService,
CalendarsService,
CredentialsRepository,
AppsRepository,
CalendarsRepository,
SelectedCalendarsRepository,
OAuthClientUsersService,
OrganizationsTeamsService,
OAuthClientsService,
@@ -118,4 +118,19 @@ export class OAuthClientRepository {
},
});
}
async getByEventTypeHosts(eventTypeId: number) {
const hostWithUserPlatformClient = await this.dbRead.prisma.host.findFirst({
select: {
user: { select: { platformOAuthClients: true } },
},
where: {
eventTypeId: eventTypeId,
user: {
isPlatformManaged: true,
},
},
});
return hostWithUserPlatformClient?.user?.platformOAuthClients?.[0] ?? null;
}
}
@@ -1,3 +1,4 @@
import { CalendarsService } from "@/ee/calendars/services/calendars.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 { GetManagedUsersInput } from "@/modules/oauth-clients/controllers/oauth-client-users/inputs/get-managed-users.input";
@@ -5,18 +6,21 @@ import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
import { UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
import { BadRequestException, ConflictException, Injectable, Logger } from "@nestjs/common";
import { User, CreationSource, PlatformOAuthClient } from "@prisma/client";
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
@Injectable()
export class OAuthClientUsersService {
private readonly logger = new Logger("OAuthClientUsersService");
constructor(
private readonly userRepository: UsersRepository,
private readonly tokensRepository: TokensRepository,
private readonly eventTypesService: EventTypesService_2024_04_15,
private readonly schedulesService: SchedulesService_2024_04_15
private readonly schedulesService: SchedulesService_2024_04_15,
private readonly calendarsService: CalendarsService
) {}
async createOAuthClientUser(oAuthClient: PlatformOAuthClient, body: CreateManagedUserInput) {
@@ -87,6 +91,13 @@ export class OAuthClientUsersService {
user.defaultScheduleId = defaultSchedule.id;
}
try {
this.logger.log(`Setting default calendars in db for user with id ${user.id}`);
await this.calendarsService.getCalendars(user.id);
} catch (err) {
this.logger.error(`Could not get calendars of new managed user with id ${user.id}`);
}
return {
user,
tokens: {
@@ -81,7 +81,7 @@ export class OrganizationsEventTypesController {
bodyEventType
);
const eventType = await this.organizationsEventTypesService.createTeamEventType(
const eventType = await this.organizationsEventTypesService.createOrganizationTeamEventType(
user,
teamId,
orgId,
@@ -212,7 +212,7 @@ export class OrganizationsEventTypesController {
bodyEventType
);
const eventType = await this.organizationsEventTypesService.updateTeamEventType(
const eventType = await this.organizationsEventTypesService.updateOrganizationTeamEventType(
eventTypeId,
teamId,
transformedBody,
@@ -581,6 +581,123 @@ describe("Organizations Event Types Endpoints", () => {
});
});
it("should be able to create phone-only event type", async () => {
const body: CreateTeamEventTypeInput_2024_06_14 = {
title: "Phone coding consultation",
slug: "phone-coding-consultation",
description: "Our team will review your codebase.",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
{
type: "organizersDefaultApp",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teammate1.id,
mandatory: true,
priority: "high",
},
],
bookingFields: [
{
type: "email",
required: false,
label: "Email",
hidden: true,
},
{
type: "phone",
slug: "attendeePhoneNumber",
required: true,
label: "Phone number",
hidden: false,
},
],
};
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data = responseBody.data;
expect(data.bookingFields).toEqual([
{
isDefault: true,
type: "name",
slug: "name",
required: true,
disableOnPrefill: false,
},
{
isDefault: true,
type: "email",
slug: "email",
required: false,
label: "Email",
disableOnPrefill: false,
hidden: true,
},
{
isDefault: true,
type: "radioInput",
slug: "location",
required: false,
hidden: false,
},
{
isDefault: true,
type: "phone",
slug: "attendeePhoneNumber",
required: true,
hidden: false,
label: "Phone number",
disableOnPrefill: false,
},
{
isDefault: true,
type: "text",
slug: "title",
required: true,
disableOnPrefill: false,
hidden: true,
},
{
isDefault: true,
type: "textarea",
slug: "notes",
required: false,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "multiemail",
slug: "guests",
required: false,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "textarea",
slug: "rescheduleReason",
required: false,
disableOnPrefill: false,
hidden: false,
},
]);
});
});
it("should be able to configure phone-only event type", async () => {
const body: UpdateTeamEventTypeInput_2024_06_14 = {
bookingFields: [
@@ -588,13 +705,14 @@ describe("Organizations Event Types Endpoints", () => {
type: "email",
required: false,
label: "Email",
hidden: true,
},
{
type: "phone",
slug: "attendeePhoneNumber",
required: true,
label: "Phone number",
hidden: true,
hidden: false,
},
],
};
@@ -622,6 +740,7 @@ describe("Organizations Event Types Endpoints", () => {
required: false,
label: "Email",
disableOnPrefill: false,
hidden: true,
},
{
isDefault: true,
@@ -635,7 +754,7 @@ describe("Organizations Event Types Endpoints", () => {
type: "phone",
slug: "attendeePhoneNumber",
required: true,
hidden: true,
hidden: false,
label: "Phone number",
disableOnPrefill: false,
},
@@ -776,7 +895,14 @@ describe("Organizations Event Types Endpoints", () => {
expect(fetchedEventType.bookingFields).toEqual([
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
{
isDefault: true,
required: true,
slug: "email",
type: "email",
disableOnPrefill: false,
hidden: false,
},
{
disableOnPrefill: false,
isDefault: true,
@@ -26,7 +26,7 @@ export class OrganizationsEventTypesService {
private readonly usersService: UsersService
) {}
async createTeamEventType(
async createOrganizationTeamEventType(
user: UserWithProfile,
teamId: number,
orgId: number,
@@ -48,7 +48,7 @@ export class OrganizationsEventTypesService {
},
});
return this.teamsEventTypesService.updateTeamEventType(eventTypeCreated.id, teamId, body, user);
return this.teamsEventTypesService.updateTeamEventType(eventTypeCreated.id, teamId, body, user, true);
}
async getUserToCreateTeamEvent(user: UserWithProfile, organizationId: number) {
@@ -91,13 +91,13 @@ export class OrganizationsEventTypesService {
return await this.organizationEventTypesRepository.getOrganizationTeamsEventTypes(orgId, skip, take);
}
async updateTeamEventType(
async updateOrganizationTeamEventType(
eventTypeId: number,
teamId: number,
body: TransformedUpdateTeamEventTypeInput,
user: UserWithProfile
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
return this.teamsEventTypesService.updateTeamEventType(eventTypeId, teamId, body, user);
return this.teamsEventTypesService.updateTeamEventType(eventTypeId, teamId, body, user, true);
}
async deleteTeamEventType(teamId: number, eventTypeId: number) {
@@ -37,11 +37,12 @@ export class OrganizationsMembershipRepository {
}
async createOrgMembership(organizationId: number, data: CreateOrgMembershipDto) {
return this.dbWrite.prisma.membership.create({
data: { ...data, teamId: organizationId },
return this.dbWrite.prisma.membership.upsert({
create: { ...data, teamId: organizationId },
update: { role: data.role, accepted: data.accepted, disableImpersonation: data.disableImpersonation },
where: { userId_teamId: { userId: data.userId, teamId: organizationId } },
});
}
async updateOrgMembership(organizationId: number, membershipId: number, data: UpdateOrgMembershipDto) {
return this.dbWrite.prisma.membership.update({
data: { ...data },
@@ -265,7 +265,7 @@ describe("Organizations TeamsBookings Endpoints 2024-08-13", () => {
});
describe("get team bookings", () => {
it("should should get bookings by teamId", async () => {
it("should get bookings by teamId", async () => {
return request(app.getHttpServer())
.get(`/v2/organizations/${organization.id}/teams/${team1.id}/bookings`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
@@ -75,6 +75,9 @@ describe("Slots 2024-09-04 Endpoints", () => {
const seatedEventTypeSlug = `slots-2024-09-04-seated-event-type-${randomString()}`;
let seatedEventType: EventType;
const variableLengthEventTypeSlug = `slots-2024-09-04-variable-length-event-type-${randomString()}`;
let variableLengthEventType: EventType;
let reservedSlot: ReserveSlotOutputData_2024_09_04;
beforeAll(async () => {
@@ -168,6 +171,17 @@ describe("Slots 2024-09-04 Endpoints", () => {
);
seatedEventType = seatedEvent;
const variableLengthEvent = await eventTypesRepositoryFixture.create(
{
title: "frisbee match",
slug: `slots-2024-09-04-variable-length-event-type-${randomString()}`,
length: 15,
metadata: { multipleDuration: [15, 30, 45, 60] },
},
user.id
);
variableLengthEventType = variableLengthEvent;
team = await teamRepositoryFixture.create({
name: `slots-2024-09-04-team-${randomString()}`,
isOrganization: false,
@@ -1246,6 +1260,90 @@ describe("Slots 2024-09-04 Endpoints", () => {
await bookingsRepositoryFixture.deleteById(booking.id);
});
describe("variable length", () => {
it("should not be able to reserve a slot for variable length event type with invalid duration", async () => {
const slotStartTime = "2050-09-05T10:00:00.000Z";
const reserveResponse = await request(app.getHttpServer())
.post(`/v2/slots/reservations`)
.send({
eventTypeId: variableLengthEventType.id,
slotStart: slotStartTime,
slotDuration: 1000,
})
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
.expect(400);
expect(reserveResponse.body.error.message).toEqual(
"Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: 15, 30, 45, 60"
);
});
it("should reserve a slot with slot duration for variable event type length", async () => {
// note(Lauris): mock current date to test slots release time
const now = "2049-09-05T12:00:00.000Z";
const newDate = DateTime.fromISO(now, { zone: "UTC" }).toJSDate();
advanceTo(newDate);
const slotDuration = 60;
const slotStartTime = "2050-09-05T10:00:00.000Z";
const reserveResponse = await request(app.getHttpServer())
.post(`/v2/slots/reservations`)
.send({
eventTypeId: variableLengthEventType.id,
slotStart: slotStartTime,
slotDuration,
})
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
.expect(201);
const reserveResponseBody: ReserveSlotOutputResponse_2024_09_04 = reserveResponse.body;
expect(reserveResponseBody.status).toEqual(SUCCESS_STATUS);
const responseReservedSlot: ReserveSlotOutputData_2024_09_04 = reserveResponseBody.data;
expect(responseReservedSlot.reservationUid).toBeDefined();
expect(responseReservedSlot.eventTypeId).toEqual(variableLengthEventType.id);
expect(responseReservedSlot.slotStart).toEqual(slotStartTime);
expect(responseReservedSlot.slotDuration).toEqual(slotDuration);
expect(responseReservedSlot.slotEnd).toEqual(
DateTime.fromISO(slotStartTime, { zone: "UTC" }).plus({ minutes: slotDuration }).toISO()
);
expect(responseReservedSlot.reservationDuration).toEqual(5);
if (!responseReservedSlot.reservationUid) {
throw new Error("Reserved slot uid is undefined");
}
const response = await request(app.getHttpServer())
.get(
`/v2/slots?eventTypeId=${variableLengthEventType.id}&start=2050-09-05&end=2050-09-09&duration=60`
)
.set(CAL_API_VERSION_HEADER, VERSION_2024_09_04)
.expect(200);
const responseBody: GetSlotsOutput_2024_09_04 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const slots = responseBody.data;
expect(slots).toBeDefined();
const days = Object.keys(slots);
expect(days.length).toEqual(5);
const expectedSlotsUTC2050_09_05 = expectedSlotsUTC["2050-09-05"].filter(
(slot) => slot.start !== slotStartTime
);
expect(slots).toEqual({ ...expectedSlotsUTC, "2050-09-05": expectedSlotsUTC2050_09_05 });
const dbSlot = await selectedSlotsRepositoryFixture.getByUid(reservedSlot.reservationUid);
expect(dbSlot).toBeDefined();
if (dbSlot) {
const dbReleaseAt = DateTime.fromJSDate(dbSlot.releaseAt, { zone: "UTC" }).toISO();
const expectedReleaseAt = DateTime.fromISO(now, { zone: "UTC" }).plus({ minutes: 5 }).toISO();
expect(dbReleaseAt).toEqual(expectedReleaseAt);
expect(responseReservedSlot.reservationUntil).toEqual(expectedReleaseAt);
}
clear();
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(user.email);
await userRepositoryFixture.deleteByEmail(unrelatedUser.email);
@@ -21,9 +21,11 @@ import { getAvailableSlots } from "@calcom/platform-libraries/slots";
import { GetSlotsInput_2024_09_04, ReserveSlotInput_2024_09_04 } from "@calcom/platform-types";
import { EventType } from "@calcom/prisma/client";
const eventTypeMetadataSchema = z.object({
multipleDuration: z.number().array().optional(),
});
const eventTypeMetadataSchema = z
.object({
multipleDuration: z.number().array().optional(),
})
.nullable();
const DEFAULT_RESERVATION_DURATION = 5;
@@ -89,17 +91,8 @@ export class SlotsService_2024_09_04 {
throw new BadRequestException("Invalid start date");
}
const metadata = eventTypeMetadataSchema.parse(eventType);
if (
input.slotDuration &&
metadata.multipleDuration &&
!metadata.multipleDuration.includes(input.slotDuration)
) {
throw new BadRequestException(
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${metadata.multipleDuration.join(
", "
)}`
);
if (input.slotDuration) {
this.validateSlotDuration(eventType, input.slotDuration);
}
const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });
@@ -160,6 +153,23 @@ export class SlotsService_2024_09_04 {
return this.slotsOutputService.getReservationSlotCreated(slot, reservationDuration);
}
validateSlotDuration(eventType: EventType, inputSlotDuration: number) {
const eventTypeMetadata = eventTypeMetadataSchema.parse(eventType.metadata);
if (!eventTypeMetadata?.multipleDuration) {
throw new BadRequestException(
"You passed 'slotDuration' but this event type is not a variable length event type."
);
}
if (!eventTypeMetadata.multipleDuration.includes(inputSlotDuration)) {
throw new BadRequestException(
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${eventTypeMetadata.multipleDuration.join(
", "
)}`
);
}
}
async canSpecifyCustomReservationDuration(authUserId: number, eventType: EventType) {
if (eventType.userId) {
return await this.canSpecifyCustomReservationDurationIndividualEvent(authUserId, eventType.userId);
@@ -214,17 +224,8 @@ export class SlotsService_2024_09_04 {
throw new BadRequestException("Invalid start date");
}
const metadata = eventTypeMetadataSchema.parse(eventType);
if (
input.slotDuration &&
metadata.multipleDuration &&
!metadata.multipleDuration.includes(input.slotDuration)
) {
throw new BadRequestException(
`Provided 'slotDuration' is not one of the possible lengths for the event type. The possible lengths for this variable length event type are: ${metadata.multipleDuration.join(
", "
)}`
);
if (input.slotDuration) {
this.validateSlotDuration(eventType, input.slotDuration);
}
const endDate = startDate.plus({ minutes: input.slotDuration ?? eventType.length });
@@ -167,6 +167,56 @@ describe("Organizations Event Types Endpoints", () => {
return request(app.getHttpServer()).post(`/v2/teams/${team.id}/event-types`).send(body).expect(404);
});
it("should not be able to create phone-only event type", async () => {
const body: CreateTeamEventTypeInput_2024_06_14 = {
title: "Phone coding consultation",
slug: "phone-coding-consultation",
description: "Our team will review your codebase.",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
{
type: "organizersDefaultApp",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
{
userId: teamMember2.id,
},
],
bookingFields: [
{
type: "email",
required: false,
label: "Email",
hidden: true,
},
{
type: "phone",
slug: "attendeePhoneNumber",
required: true,
label: "Phone number",
hidden: false,
},
],
};
const response = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(body)
.expect(400);
expect(response.body.error.message).toBe(
"checkIsEmailUserAccessible - Email booking field must be required and visible"
);
});
it("should create a collective team event-type", async () => {
const body: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-collective-${randomString()}`,
@@ -181,7 +181,8 @@ export class TeamsEventTypesController {
eventTypeId,
teamId,
transformedBody,
user
user,
false
);
return {
@@ -30,6 +30,10 @@ export class TeamsEventTypesService {
teamId: number,
body: TransformedCreateTeamEventTypeInput
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
// note(Lauris): once phone only event types / bookings are enabled for simple users remove checkHasUserAccessibleEmailBookingField check
if (body.bookingFields) {
this.eventTypesService.checkHasUserAccessibleEmailBookingField(body.bookingFields);
}
const eventTypeUser = await this.getUserToCreateTeamEvent(user);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { hosts, children, destinationCalendar, ...rest } = body;
@@ -44,7 +48,7 @@ export class TeamsEventTypesService {
},
});
return this.updateTeamEventType(eventTypeCreated.id, teamId, body, user);
return this.updateTeamEventType(eventTypeCreated.id, teamId, body, user, false);
}
async validateEventTypeExists(teamId: number, eventTypeId: number) {
@@ -105,8 +109,14 @@ export class TeamsEventTypesService {
eventTypeId: number,
teamId: number,
body: TransformedUpdateTeamEventTypeInput,
user: UserWithProfile
user: UserWithProfile,
// note(Lauris): once phone only event types / bookings are enabled for simple users remove isOrg parameter (right now only organization team event types support hidden / non-required email field)
isOrg: boolean
): Promise<DatabaseTeamEventType | DatabaseTeamEventType[]> {
if (!isOrg && body.bookingFields) {
// note(Lauris): once phone only event types / bookings are enabled for simple users remove checkHasUserAccessibleEmailBookingField check
this.eventTypesService.checkHasUserAccessibleEmailBookingField(body.bookingFields);
}
await this.validateEventTypeExists(teamId, eventTypeId);
const eventTypeUser = await this.eventTypesService.getUserToUpdateEvent(user);
await updateEventType({
+83 -18
View File
@@ -3756,6 +3756,26 @@
"type": "string"
}
},
{
"name": "afterCreatedAt",
"required": false,
"in": "query",
"description": "Filter bookings that have been created after this date string.",
"example": "?afterCreatedAt=2025-03-07T10:00:00.000Z",
"schema": {
"type": "string"
}
},
{
"name": "beforeCreatedAt",
"required": false,
"in": "query",
"description": "Filter bookings that have been created before this date string.",
"example": "?beforeCreatedAt=2025-03-14T11:00:00.000Z",
"schema": {
"type": "string"
}
},
{
"name": "afterUpdatedAt",
"required": false,
@@ -4722,7 +4742,11 @@
"name": "app",
"required": true,
"in": "path",
"description": "Conferencing application type",
"schema": {
"enum": [
"google-meet"
],
"type": "string"
}
}
@@ -4761,7 +4785,12 @@
"name": "app",
"required": true,
"in": "path",
"description": "Conferencing application type",
"schema": {
"enum": [
"zoom",
"msteams"
],
"type": "string"
}
},
@@ -4816,7 +4845,12 @@
"name": "app",
"required": true,
"in": "path",
"description": "Conferencing application type",
"schema": {
"enum": [
"zoom",
"msteams"
],
"type": "string"
}
},
@@ -4870,7 +4904,14 @@
"name": "app",
"required": true,
"in": "path",
"description": "Conferencing application type",
"schema": {
"enum": [
"google-meet",
"zoom",
"msteams",
"daily-video"
],
"type": "string"
}
}
@@ -4923,7 +4964,13 @@
"name": "app",
"required": true,
"in": "path",
"description": "Conferencing application type",
"schema": {
"enum": [
"google-meet",
"zoom",
"msteams"
],
"type": "string"
}
}
@@ -5516,8 +5563,6 @@
"/v2/oauth-clients": {
"post": {
"operationId": "OAuthClientsController_createOAuthClient",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [],
"requestBody": {
"required": true,
@@ -5547,8 +5592,6 @@
},
"get": {
"operationId": "OAuthClientsController_getOAuthClients",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [],
"responses": {
"200": {
@@ -5570,8 +5613,6 @@
"/v2/oauth-clients/{clientId}": {
"get": {
"operationId": "OAuthClientsController_getOAuthClientById",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [
{
"name": "clientId",
@@ -5600,8 +5641,6 @@
},
"patch": {
"operationId": "OAuthClientsController_updateOAuthClient",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [
{
"name": "clientId",
@@ -5640,8 +5679,6 @@
},
"delete": {
"operationId": "OAuthClientsController_deleteOAuthClient",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [
{
"name": "clientId",
@@ -5672,8 +5709,6 @@
"/v2/oauth-clients/{clientId}/managed-users": {
"get": {
"operationId": "OAuthClientsController_getOAuthClientManagedUsersById",
"summary": "",
"description": "⚠️ First, this endpoint requires `Cookie: next-auth.session-token=eyJhbGciOiJ` header. Log into Cal web app using owner of organization that was created after visiting `/settings/organizations/new`, refresh swagger docs, and the cookie will be added to requests automatically to pass the NextAuthGuard.\nSecond, make sure that the logged in user has organizationId set to pass the OrganizationRolesGuard guard.",
"parameters": [
{
"name": "clientId",
@@ -8263,7 +8298,7 @@
},
"slug": {
"type": "string",
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking",
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking. Special slug is `attendeePhoneNumber` - if you create\n a phone input field with this slug for organization team event type you can create an organization team event type that can be booked using phone without requiring an email by setting {\"type\": \"email\", \"required\": false, \"hidden\": true} to the email booking field input in the request body.",
"example": "some-slug"
},
"label": {
@@ -9109,7 +9144,12 @@
"type": "string"
},
"required": {
"type": "object"
"type": "object",
"description": "Can be set to false only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
},
"hidden": {
"type": "boolean",
"description": "Can be set to true only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
},
"placeholder": {
"type": "string"
@@ -9122,7 +9162,6 @@
"required": [
"type",
"label",
"required",
"placeholder"
]
},
@@ -9845,7 +9884,12 @@
},
"required": {
"type": "object",
"default": true
"default": true,
"description": "Can be set to false only for organization team event types and if you also pass booking field {type: \"phone\", slug: \"attendeePhoneNumber\", required: true, hidden: false, label: \"whatever label\"} of booking field type PhoneFieldInput_2024_06_14 - this is done\n to enable phone only bookings where during the booking attendee can provide only their phone number and not provide email, so you must pass to the email booking field {hidden: true, required: false}.\n If true show under event type settings but don't show this booking field in the Booker. If false show in both."
},
"hidden": {
"type": "boolean",
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both. Can only be hidden\n for organization team event types when also providing attendee phone number booking field."
},
"placeholder": {
"type": "string"
@@ -9867,7 +9911,6 @@
},
"required": [
"type",
"required",
"isDefault",
"slug"
]
@@ -10645,7 +10688,7 @@
},
"slug": {
"type": "string",
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking",
"description": "Unique identifier for the field in format `some-slug`. It is used to access response to this booking field during the booking. Special slug is `attendeePhoneNumber` - if you create\n a phone input field with this slug for organization team event type you can create an organization team event type that can be booked using phone without requiring an email by setting {\"type\": \"email\", \"required\": false, \"hidden\": true} to the email booking field input in the request body.",
"example": "some-slug"
},
"label": {
@@ -16480,6 +16523,19 @@
"type"
]
},
"BookingInputOrganizersDefaultAppLocation_2024_08_13": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "organizersDefaultApp",
"description": "only available for team event types and the only allowed value for type is `organizersDefaultApp` - it refers to the default app defined by the organizer."
}
},
"required": [
"type"
]
},
"ValidateBookingLocation_2024_08_13": {
"type": "object",
"properties": {}
@@ -16629,6 +16685,9 @@
},
{
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
},
{
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
}
]
},
@@ -16719,6 +16778,9 @@
},
{
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
},
{
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
}
]
},
@@ -16815,6 +16877,9 @@
},
{
"$ref": "#/components/schemas/BookingInputPhoneLocation_2024_08_13"
},
{
"$ref": "#/components/schemas/BookingInputOrganizersDefaultAppLocation_2024_08_13"
}
]
},
@@ -3,7 +3,7 @@
import { useRouter } from "next/navigation";
import type { LayoutProps } from "@calcom/features/shell/Shell";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
export const ShellMainAppDirBackButton = ({ backPath }: { backPath: LayoutProps["backPath"] }) => {
const router = useRouter();
+1 -1
View File
@@ -1,7 +1,7 @@
import { _generateMetadata, getTranslate } from "app/_utils";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
export const generateMetadata = () =>
_generateMetadata(
@@ -1,7 +1,8 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, showToast } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { showToast } from "@calcom/ui/components/toast";
export default function CopyButton({ error }: { error: string }) {
const { t } = useLocale();
+1 -1
View File
@@ -1,7 +1,7 @@
import { _generateMetadata, getTranslate } from "app/_utils";
import { APP_NAME } from "@calcom/lib/constants";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import CopyButton from "./copy-button";
@@ -3,7 +3,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import Link from "next/link";
import { z } from "zod";
import { Button, Icon } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Icon } from "@calcom/ui/components/icon";
import AuthContainer from "@components/ui/AuthContainer";
@@ -2,7 +2,8 @@
import React, { Suspense } from "react";
import { ErrorBoundary, Icon } from "@calcom/ui";
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
import { Icon } from "@calcom/ui/components/icon";
export default function TroubleshooterLayout({ children }: { children: React.ReactNode }) {
return (
@@ -5,7 +5,9 @@ import { useSession } from "next-auth/react";
import { UpgradeTip } from "@calcom/features/tips";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Button, ButtonGroup } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
import { Icon } from "@calcom/ui/components/icon";
export default function UpgradeTipWrapper({ children }: { children: React.ReactNode }) {
const { t } = useLocale();
@@ -6,7 +6,7 @@ import React, { useEffect } from "react";
import type Shell from "@calcom/features/shell/Shell";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { ErrorBoundary } from "@calcom/ui";
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
export type AdminLayoutProps = {
children: React.ReactNode;
@@ -2,7 +2,7 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import UsersListingView from "@calcom/features/ee/users/pages/users-listing-view";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -18,9 +18,14 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { OrganizationRepository } from "@calcom/lib/server/repository/organization";
import { IdentityProvider, MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { VerticalTabItemProps } from "@calcom/ui";
import { Badge, Button, ErrorBoundary, Icon, Skeleton, VerticalTabItem } from "@calcom/ui";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
import { Icon } from "@calcom/ui/components/icon";
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
import { VerticalTabItem } from "@calcom/ui/components/navigation";
import { Skeleton } from "@calcom/ui/components/skeleton";
const getTabs = (orgBranding: OrganizationBranding | null) => {
const tabs: VerticalTabItemProps[] = [
@@ -174,7 +179,7 @@ const organizationAdminKeys = [
const useTabs = ({ isDelegationCredentialEnabled }: { isDelegationCredentialEnabled: boolean }) => {
const session = useSession();
const { data: user } = trpc.viewer.me.useQuery({ includePasswordAdded: true });
const { data: user } = trpc.viewer.me.get.useQuery({ includePasswordAdded: true });
const orgBranding = useOrgBranding();
const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN;
const isOrgAdminOrOwner =
@@ -4,7 +4,7 @@ import { Suspense } from "react";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { NewWebhookView } from "@calcom/features/webhooks/pages/webhook-new-view";
import { APP_NAME } from "@calcom/lib/constants";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import { SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -2,7 +2,7 @@ import { _generateMetadata } from "app/_utils";
import { getTranslate } from "app/_utils";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
+11 -2
View File
@@ -55,6 +55,8 @@ async function postHandler(request: NextRequest) {
locale: true,
timeZone: true,
destinationCalendar: true,
isPlatformManaged: true,
platformOAuthClients: { select: { id: true, areEmailsEnabled: true } },
},
},
eventType: {
@@ -70,11 +72,18 @@ async function postHandler(request: NextRequest) {
},
});
const bookingsToRemind = bookings.filter(
(booking) =>
!booking.user ||
!booking.user.isPlatformManaged ||
(booking.user.isPlatformManaged && Boolean(booking.user.platformOAuthClients?.[0]?.areEmailsEnabled))
);
const reminders = await prisma.reminderMail.findMany({
where: {
reminderType: ReminderType.PENDING_BOOKING_CONFIRMATION,
referenceId: {
in: bookings.map((b) => b.id),
in: bookingsToRemind.map((b) => b.id),
},
elapsedMinutes: {
gte: interval,
@@ -82,7 +91,7 @@ async function postHandler(request: NextRequest) {
},
});
for (const booking of bookings.filter((b) => !reminders.some((r) => r.referenceId == b.id))) {
for (const booking of bookingsToRemind.filter((b) => !reminders.some((r) => r.referenceId == b.id))) {
const { user } = booking;
const name = user?.name || user?.username;
if (!user || !name || !user.timeZone) {
+2 -2
View File
@@ -1,7 +1,7 @@
"use client";
import { Icon } from "@calcom/ui";
import type { IconName } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import type { IconName } from "@calcom/ui/components/icon";
export const IconGrid = (props: {
title: string;
+2 -1
View File
@@ -2,7 +2,8 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import { type IconName, IconSprites } from "@calcom/ui";
import { IconSprites } from "@calcom/ui/components/icon";
import type { IconName } from "@calcom/ui/components/icon";
import { lucideIconList } from "../../../../packages/ui/components/icon/icon-list.mjs";
import { IconGrid } from "./IconGrid";
+1 -1
View File
@@ -5,7 +5,7 @@ import { headers, cookies } from "next/headers";
import React from "react";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { IconSprites } from "@calcom/ui";
import { IconSprites } from "@calcom/ui/components/icon";
import { NotificationSoundHandler } from "@calcom/web/components/notification-sound-handler";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
+1 -1
View File
@@ -8,7 +8,7 @@ import {
subdomainSuffix,
} from "@calcom/features/ee/organizations/lib/orgDomains";
import { DOCS_URL, IS_CALCOM, WEBSITE_URL } from "@calcom/lib/constants";
import { Icon } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapperAppDir";
+1 -1
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
export default function AddToHomescreen() {
const { t } = useLocale();
+3 -1
View File
@@ -3,7 +3,9 @@
import Shell from "@calcom/features/shell/Shell";
import { UpgradeTip } from "@calcom/features/tips";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, ButtonGroup, Icon } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
import { Icon } from "@calcom/ui/components/icon";
export default function EnterprisePage() {
const { t } = useLocale();
+1 -1
View File
@@ -2,7 +2,7 @@ import { CALCOM_VERSION } from "@calcom/lib/constants";
import { trpc } from "@calcom/trpc/react";
export function useViewerI18n(locale: string) {
return trpc.viewer.public.i18n.useQuery(
return trpc.viewer.i18n.get.useQuery(
{ locale, CalComVersion: CALCOM_VERSION },
{
/**
+1 -1
View File
@@ -1,4 +1,4 @@
/**
* @deprecated Use custom Skeletons instead
**/
export { Loader as default } from "@calcom/ui";
export { Loader as default } from "@calcom/ui/components/skeleton";
+1 -1
View File
@@ -10,7 +10,7 @@ import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { IS_CALCOM, WEBAPP_URL } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import { IconSprites } from "@calcom/ui";
import { IconSprites } from "@calcom/ui/components/icon";
import type { AppProps } from "@lib/app-providers";
import AppProviders from "@lib/app-providers";
@@ -4,13 +4,13 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@calcom/ui";
} from "@calcom/ui/components/dropdown";
import { Button } from "@calcom/ui/components/button";
interface AdditionalCalendarSelectorProps {
isPending?: boolean;
@@ -18,7 +18,7 @@ interface AdditionalCalendarSelectorProps {
const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorProps): JSX.Element | null => {
const { t } = useLocale();
const [data] = trpc.viewer.integrations.useSuspenseQuery({ variant: "calendar", onlyInstalled: true });
const [data] = trpc.viewer.apps.integrations.useSuspenseQuery({ variant: "calendar", onlyInstalled: true });
const options = data.items.map((item) => ({
label: item.name,
+8 -4
View File
@@ -14,8 +14,12 @@ import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App as AppType } from "@calcom/types/App";
import { Badge, Button, Icon, SkeletonButton, SkeletonText, showToast } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import classNames from "@calcom/ui/classNames";
import { SkeletonButton, SkeletonText } from "@calcom/ui/components/skeleton";
import { InstallAppButtonChild } from "./InstallAppButtonChild";
import { MultiDisconnectIntegration } from "./MultiDisconnectIntegration";
@@ -87,7 +91,7 @@ export const AppPage = ({
if (data?.setupPending) return;
setIsLoading(false);
showToast(data?.message || t("app_successfully_installed"), "success");
await utils.viewer.appCredentialsByType.invalidate({ appType: type });
await utils.viewer.apps.appCredentialsByType.invalidate({ appType: type });
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error");
@@ -143,7 +147,7 @@ export const AppPage = ({
*/
const [appInstalledForAllTargets, setAppInstalledForAllTargets] = useState(false);
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery({ appType: type });
const appDbQuery = trpc.viewer.apps.appCredentialsByType.useQuery({ appType: type });
useEffect(
function refactorMeWithoutEffect() {
@@ -161,7 +165,7 @@ export const AppPage = ({
[appDbQuery.data, availableForTeams]
);
const dependencyData = trpc.viewer.appsRouter.queryForDependencies.useQuery(dependencies, {
const dependencyData = trpc.viewer.apps.queryForDependencies.useQuery(dependencies, {
enabled: !!dependencies,
});
@@ -6,16 +6,14 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { DestinationCalendarSettingsWebWrapper } from "@calcom/atoms/destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper";
import { SelectedCalendarsSettingsWebWrapper } from "@calcom/atoms/selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper";
import AppListCard from "@calcom/features/apps/components/AppListCard";
import { SkeletonLoader } from "@calcom/features/apps/components/SkeletonLoader";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
EmptyScreen,
List,
ShellSubHeading,
AppSkeletonLoader as SkeletonLoader,
showToast,
} from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
import { ShellSubHeading } from "@calcom/ui/components/layout";
import { List } from "@calcom/ui/components/list";
import { showToast } from "@calcom/ui/components/toast";
import { QueryCell } from "@lib/QueryCell";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -31,7 +29,7 @@ type Props = {
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
const query = trpc.viewer.apps.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
return (
<QueryCell
@@ -80,7 +78,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
const utils = trpc.useUtils();
const onChanged = () =>
Promise.allSettled([
utils.viewer.integrations.invalidate(
utils.viewer.apps.integrations.invalidate(
{ variant: "calendar", onlyInstalled: true },
{
exact: true,
@@ -89,7 +87,10 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
utils.viewer.connectedCalendars.invalidate(),
]);
const query = trpc.viewer.connectedCalendars.useQuery();
const installedCalendars = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
const installedCalendars = trpc.viewer.apps.integrations.useQuery({
variant: "calendar",
onlyInstalled: true,
});
const mutation = trpc.viewer.setDestinationCalendar.useMutation({
onSuccess: () => {
utils.viewer.connectedCalendars.invalidate();
@@ -1,8 +1,8 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { AppFrontendPayload } from "@calcom/types/App";
import type { ButtonProps } from "@calcom/ui";
import { Button } from "@calcom/ui";
import type { ButtonProps } from "@calcom/ui/components/button";
import { Button } from "@calcom/ui/components/button";
export const InstallAppButtonChild = ({
multiInstall,
@@ -11,7 +11,7 @@ export const InstallAppButtonChild = ({
...props
}: {
multiInstall?: boolean;
credentials?: RouterOutputs["viewer"]["appCredentialsByType"]["credentials"];
credentials?: RouterOutputs["viewer"]["apps"]["appCredentialsByType"]["credentials"];
paid?: AppFrontendPayload["paid"];
} & ButtonProps) => {
const { t } = useLocale();
@@ -3,23 +3,23 @@ import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/types/server/routers/_app";
import { Button } from "@calcom/ui/components/button";
import { ConfirmationDialogContent } from "@calcom/ui/components/dialog";
import { Dialog } from "@calcom/ui/components/dialog";
import {
Button,
Dropdown,
DropdownMenuTrigger,
DropdownItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuItem,
DropdownItem,
Dialog,
ConfirmationDialogContent,
showToast,
} from "@calcom/ui";
DropdownMenuTrigger,
} from "@calcom/ui/components/dropdown";
import { showToast } from "@calcom/ui/components/toast";
import type { inferRouterOutputs } from "@trpc/server";
type RouterOutput = inferRouterOutputs<AppRouter>;
type Credentials = RouterOutput["viewer"]["appCredentialsByType"]["credentials"];
type Credentials = RouterOutput["viewer"]["apps"]["appCredentialsByType"]["credentials"];
interface Props {
credentials: Credentials;
@@ -48,7 +48,7 @@ export function MultiDisconnectIntegration({ credentials, onSuccess }: Props) {
},
async onSettled() {
await utils.viewer.connectedCalendars.invalidate();
await utils.viewer.integrations.invalidate();
await utils.viewer.apps.integrations.invalidate();
},
});
@@ -4,9 +4,9 @@ import React, { useState } from "react";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { User } from "@calcom/prisma/client";
import { StepCard } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import classNames from "@calcom/ui/classNames";
import { Avatar } from "@calcom/ui/components/avatar";
import { StepCard } from "@calcom/ui/components/card";
import type { TTeams } from "~/apps/installation/[[...step]]/step-view";
@@ -12,7 +12,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { LocationObject } from "@calcom/lib/location";
import type { AppCategories } from "@calcom/prisma/enums";
import type { EventTypeMetaDataSchema, eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import { Button, Form, Icon, Avatar } from "@calcom/ui";
import { Avatar } from "@calcom/ui/components/avatar";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import EventTypeAppSettingsWrapper from "@components/apps/installation/EventTypeAppSettingsWrapper";
import EventTypeConferencingAppSettings from "@components/apps/installation/EventTypeConferencingAppSettings";
@@ -10,8 +10,8 @@ import type { SingleValueLocationOption } from "@calcom/features/form/components
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import { Skeleton, Label } from "@calcom/ui";
import { Label } from "@calcom/ui/components/form";
import { Skeleton, SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
import { QueryCell } from "@lib/QueryCell";
@@ -6,7 +6,10 @@ import { useFieldArray, useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { ScrollableArea, Badge, Button, Avatar } from "@calcom/ui";
import { Avatar } from "@calcom/ui/components/avatar";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { ScrollableArea } from "@calcom/ui/components/scrollable";
import type { TEventType, TEventTypesForm, TEventTypeGroup } from "~/apps/installation/[[...step]]/step-view";
@@ -5,7 +5,7 @@ import React from "react";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen } from "@calcom/ui";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
type AppsLayoutProps = {
children: React.ReactNode;
+1 -1
View File
@@ -2,7 +2,7 @@ import React from "react";
import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label, TextField } from "@calcom/ui";
import { Label, TextField } from "@calcom/ui/components/form";
export default function TwoFactor({ center = true }) {
const { t } = useLocale();
+1 -1
View File
@@ -3,7 +3,7 @@ import useDigitInput from "react-digit-input";
import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label, Input } from "@calcom/ui";
import { Input, Label } from "@calcom/ui/components/form";
export default function TwoFactor({ center = true, autoFocus = true }) {
const [value, onChange] = useState("");
+12 -15
View File
@@ -21,14 +21,11 @@ import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { Ensure } from "@calcom/types/utils";
import type { ActionType } from "@calcom/ui";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import {
Badge,
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
Dropdown,
DropdownItem,
DropdownMenuCheckboxItem,
@@ -38,14 +35,14 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuPortal,
Icon,
MeetingTimeInTimezones,
showToast,
TableActions,
TextAreaField,
Tooltip,
} from "@calcom/ui";
import classNames from "@calcom/ui/classNames";
} from "@calcom/ui/components/dropdown";
import { TextAreaField } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { MeetingTimeInTimezones } from "@calcom/ui/components/popover";
import type { ActionType } from "@calcom/ui/components/table";
import { TableActions } from "@calcom/ui/components/table";
import { showToast } from "@calcom/ui/components/toast";
import { Tooltip } from "@calcom/ui/components/tooltip";
import assignmentReasonBadgeTitleMap from "@lib/booking/assignmentReasonBadgeTitleMap";
@@ -5,7 +5,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRefreshData } from "@calcom/lib/hooks/useRefreshData";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button, Icon, Label, TextArea, Select } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Label, Select, TextArea } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
interface InternalNotePresetsSelectProps {
internalNotePresets: { id: number; name: string }[];
@@ -1,6 +1,6 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui/components/skeleton";
function SkeletonLoader() {
return (
+5 -10
View File
@@ -4,16 +4,11 @@ import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
MultiEmail,
Icon,
showToast,
} from "@calcom/ui";
import { MultiEmail } from "@calcom/ui/components/address";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/components/dialog";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
interface IAddGuestsDialog {
isOpenDialog: boolean;
@@ -3,16 +3,10 @@ import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
} from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogClose } from "@calcom/ui/components/dialog";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -19,7 +19,10 @@ import type { LocationOption } from "@calcom/features/form/components/LocationSe
import LocationSelect from "@calcom/features/form/components/LocationSelect";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Icon, Input, Dialog, DialogContent, DialogFooter, Form } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
import { Form, Input } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { QueryCell } from "../../lib/QueryCell";
+8 -11
View File
@@ -10,22 +10,19 @@ import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import {
Button,
Form,
Label,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogClose,
ConfirmationDialogContent,
showToast,
Input,
Icon,
RadioGroup as RadioArea,
TextAreaField,
} from "@calcom/ui";
import classNames from "@calcom/ui/classNames";
} from "@calcom/ui/components/dialog";
import { TextAreaField, Form, Label, Input } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { RadioAreaGroup as RadioArea } from "@calcom/ui/components/radio";
import { showToast } from "@calcom/ui/components/toast";
enum ReassignType {
ROUND_ROBIN = "round_robin",
+4 -3
View File
@@ -24,9 +24,10 @@ import type { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { Ensure } from "@calcom/types/utils";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui";
import { Button, Tooltip } from "@calcom/ui";
import { showToast } from "@calcom/ui";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { showToast } from "@calcom/ui/components/toast";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/components/dialog";
const enum ReroutingStatusEnum {
REROUTING_NOT_INITIATED = "not_initiated",
@@ -3,16 +3,11 @@ import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
TextArea,
} from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import { TextArea } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/components/dialog";
import { showToast } from "@calcom/ui/components/toast";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -1,3 +1,4 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { act, fireEvent, render, screen } from "@testing-library/react";
import { vi } from "vitest";
@@ -81,15 +82,9 @@ vi.mock("@calcom/web/lib/hooks/useRouterQuery", () => ({
}),
}));
vi.mock("@calcom/ui", async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const actual = await importOriginal<any>("@calcom/ui");
return {
...actual,
Tooltip: vi.fn(({ children }) => <div data-testid="mock-tooltip">{children}</div>),
};
});
vi.mock("@calcom/ui/components/tooltip", () => ({
Tooltip: vi.fn(({ children }) => <div data-testid="mock-tooltip">{children}</div>),
}));
vi.mock("@calcom/trpc/react", () => ({
trpc: {
@@ -321,7 +316,11 @@ describe("RerouteDialog", () => {
});
test("renders the dialog when open", () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.getByText("reroute_booking")).toBeInTheDocument();
expect(screen.getByText("reroute_booking_description")).toBeInTheDocument();
@@ -329,14 +328,20 @@ describe("RerouteDialog", () => {
test("doesn't render the dialog when closed", () => {
render(
<RerouteDialog isOpenDialog={false} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
<TooltipProvider>
<RerouteDialog isOpenDialog={false} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.queryByText("reroute_booking")).not.toBeInTheDocument();
});
test("displays current routing status", async () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.getByText("current_routing_status")).toBeInTheDocument();
expectEventTypeInfoInCurrentRouting({
@@ -353,17 +358,29 @@ describe("RerouteDialog", () => {
});
test("verify_new_route button is enabled even when form fields are not filled", async () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
await expect(screen.getByText("verify_new_route")).toBeEnabled();
});
test("disabledFields are passed to FormInputFields with value ['email'] - email field is disabled", async () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.getByTestId("mock-form-field-disabled-fields-identifiers")).toHaveTextContent(/^email$/);
});
test("Expect form fields and name to be rendered", async () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.getByText("Test Form")).toBeInTheDocument();
expect(screen.getByTestId("mock-form-field-company-size-label")).toHaveTextContent("Company Size");
expect(screen.getByTestId("mock-form-field-company-size-value")).toHaveTextContent("small");
@@ -372,7 +389,11 @@ describe("RerouteDialog", () => {
});
test("cancel button closes the dialog", async () => {
render(<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />);
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
expect(screen.getByText("cancel")).toBeInTheDocument();
@@ -383,7 +404,9 @@ describe("RerouteDialog", () => {
describe("New Routing tests", () => {
test("when verify_new_route is clicked, the form is submitted", async () => {
render(
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
fireEvent.click(screen.getByText("verify_new_route"));
@@ -402,7 +425,9 @@ describe("RerouteDialog", () => {
describe("New tab rescheduling", () => {
test("new tab is closed when new booking is rerouted", async () => {
render(
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
clickVerifyNewRouteButton();
clickRescheduleToTheNewEventWithDifferentTimeslotButton();
@@ -441,7 +466,9 @@ describe("RerouteDialog", () => {
test("Rescheduling with same timeslot works", async () => {
render(
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
);
clickVerifyNewRouteButton();
clickRescheduleWithSameTimeslotOfChosenEventButton();
+1 -1
View File
@@ -3,7 +3,7 @@
import React from "react";
import { HttpError } from "@calcom/lib/http-error";
import { Button } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
type Props = {
statusCode?: number | null;
@@ -7,7 +7,10 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Badge, Button, Icon, showToast } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
interface IAppConnectionItem {
title: string;
@@ -25,7 +28,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
const { title, logo, type, installed, isDefault, defaultInstall, slug } = props;
const { t } = useLocale();
const utils = trpc.useUtils();
const setDefaultConferencingApp = trpc.viewer.appsRouter.setDefaultConferencingApp.useMutation({
const setDefaultConferencingApp = trpc.viewer.apps.setDefaultConferencingApp.useMutation({
onSuccess: async () => {
await utils.viewer.me.invalidate();
},
@@ -54,7 +57,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
setDefaultConferencingApp.mutate({ slug });
}
setInstalling(false);
utils.viewer.integrations.invalidate();
utils.viewer.apps.integrations.invalidate();
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -1,4 +1,4 @@
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui";
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui/components/skeleton";
export function StepConnectionLoader() {
return (
@@ -1,6 +1,7 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon, List } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import { List } from "@calcom/ui/components/list";
import classNames from "@calcom/ui/classNames";
import { AppConnectionItem } from "../components/AppConnectionItem";
@@ -19,7 +20,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
eventTypeId: null,
});
const { t } = useLocale();
const queryIntegrations = trpc.viewer.integrations.useQuery({
const queryIntegrations = trpc.viewer.apps.integrations.useQuery({
variant: "calendar",
onlyInstalled: false,
sortByMostPopular: true,
@@ -2,7 +2,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Icon, List } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
import { List } from "@calcom/ui/components/list";
import classNames from "@calcom/ui/classNames";
import { AppConnectionItem } from "../components/AppConnectionItem";
@@ -14,7 +15,7 @@ interface ConnectedAppStepProps {
const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
const { nextStep } = props;
const { data: queryConnectedVideoApps, isPending } = trpc.viewer.integrations.useQuery({
const { data: queryConnectedVideoApps, isPending } = trpc.viewer.apps.integrations.useQuery({
variant: "conferencing",
onlyInstalled: false,
@@ -5,7 +5,8 @@ import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/types/server/routers/_app";
import { Button, Form } from "@calcom/ui";
import { Form } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import type { TRPCClientErrorLike } from "@trpc/client";
@@ -7,15 +7,19 @@ import { md } from "@calcom/lib/markdownIt";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { UserAvatar } from "@calcom/ui";
import { UserAvatar } from "@calcom/ui/components/avatar";
import { Button } from "@calcom/ui/components/button";
import { Editor } from "@calcom/ui/components/editor";
import { Label } from "@calcom/ui/components/form";
import { ImageUploader } from "@calcom/ui/components/image-uploader";
import { showToast } from "@calcom/ui/components/toast";
type FormData = {
bio: string;
};
const UserProfile = () => {
const [user] = trpc.viewer.me.useSuspenseQuery();
const [user] = trpc.viewer.me.get.useSuspenseQuery();
const { t } = useLocale();
const avatarRef = useRef<HTMLInputElement>(null);
const { setValue, handleSubmit, getValues } = useForm<FormData>({
@@ -56,7 +60,7 @@ const UserProfile = () => {
console.error(error);
}
await utils.viewer.me.refetch();
await utils.viewer.me.get.refetch();
const redirectUrl = localStorage.getItem("onBoardingRedirect");
localStorage.removeItem("onBoardingRedirect");
redirectUrl ? router.push(redirectUrl) : router.push("/");
@@ -10,7 +10,8 @@ import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { Button, Input } from "@calcom/ui";
import { Input } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@@ -21,7 +22,7 @@ interface IUserSettingsProps {
const UserSettings = (props: IUserSettingsProps) => {
const { nextStep } = props;
const [user] = trpc.viewer.me.useSuspenseQuery();
const [user] = trpc.viewer.me.get.useSuspenseQuery();
const { t } = useLocale();
const { setTimezone: setSelectedTimeZone, timezone: selectedTimeZone } = useTimePreferences();
const telemetry = useTelemetry();
@@ -1,6 +1,6 @@
import type { ReactNode } from "react";
import { Badge } from "@calcom/ui";
import { Badge } from "@calcom/ui/components/badge";
function pluralize(opts: { num: number; plural: string; singular: string }) {
if (opts.num === 0) {
@@ -3,7 +3,9 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent } from "@calcom/ui/components/dialog";
import { Form, PasswordField } from "@calcom/ui/components/form";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,6 +1,8 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, Button, showToast } from "@calcom/ui";
import { showToast } from "@calcom/ui/components/toast";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
const utils = trpc.useUtils();
@@ -5,7 +5,9 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useCallbackRef } from "@calcom/lib/hooks/useCallbackRef";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form } from "@calcom/ui";
import { Form } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent } from "@calcom/ui/components/dialog";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,7 +1,8 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge, Button } from "@calcom/ui";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import DisableTwoFactorModal from "./DisableTwoFactorModal";
import EnableTwoFactorModal from "./EnableTwoFactorModal";
@@ -1,4 +1,4 @@
import { Icon } from "@calcom/ui";
import { Icon } from "@calcom/ui/components/icon";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return (
@@ -2,16 +2,16 @@ import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import {
Badge,
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Button,
InputError,
} from "@calcom/ui";
} from "@calcom/ui/components/dropdown";
import { InputError } from "@calcom/ui/components/form";
import type { FormValues } from "~/settings/my-account/profile-view";
@@ -3,7 +3,9 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, DialogFooter, Form, PasswordField } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
import { Form, PasswordField } from "@calcom/ui/components/form";
import BackupCode from "@components/auth/BackupCode";
import TwoFactor from "@components/auth/TwoFactor";
@@ -5,7 +5,10 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useCallbackRef } from "@calcom/lib/hooks/useCallbackRef";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, DialogFooter, Form, PasswordField, showToast } from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
import { Form, PasswordField } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/components/toast";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,7 +1,7 @@
import { Trans } from "react-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
interface SecondaryEmailConfirmModalProps {
email: string;
@@ -5,16 +5,9 @@ import { z } from "zod";
import { emailSchema } from "@calcom/lib/emailSchema";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Dialog,
DialogContent,
DialogFooter,
DialogClose,
Button,
TextField,
Form,
InputError,
} from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import { Form, TextField, InputError } from "@calcom/ui/components/form";
interface SecondaryEmailModalProps {
isLoading: boolean;
@@ -5,17 +5,9 @@ import dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences";
import { TimezoneSelect } from "@calcom/features/components/timezone-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Dialog,
DialogContent,
DialogFooter,
DialogClose,
Button,
Label,
DateRangePicker,
SettingsToggle,
DatePicker,
} from "@calcom/ui";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import { Label, SettingsToggle, DateRangePicker, DatePicker } from "@calcom/ui/components/form";
import type { FormValues } from "~/settings/my-account/general-view";

Some files were not shown because too many files have changed in this diff Show More