Compare commits

..
478 Commits
Author SHA1 Message Date
Omar López 2e50625165 v3.5.4
release.yml / release (push) Canceled after 0s
2023-11-30 15:47:15 -07:00
sean-brydonandMorgan c78eb752a7 feat: rate limit removeMember (#12570)
* feat: rate limit removeMember

* Remove optional type as its always there

* fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-11-30 22:16:40 +00:00
Somay Chauhan 1c20bdcecf fix: rescheduling recurring events (#12567) 2023-11-30 13:27:37 -05:00
Amit Sharma 338756439f fix: meeting ended incorrect content type (#12484) 2023-11-30 17:52:55 +00:00
56050b994d fix 'attempt booking in the past' error (#12597)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-30 17:31:59 +00:00
sean-brydon c11ecbb323 Fix:use typed query initially fill default paramaters (#12568) 2023-11-29 17:51:47 -05:00
Peer RichelsenandOmar López 7f23ae156b fix: improved team upgrade screen to also show unpublished teams (#12492)
* improved team upgrade screen to also show unpublished teams

* Update TeamsListing.tsx

* bunch of stuff

---------

Co-authored-by: Omar López <zomars@me.com>
2023-11-29 14:48:26 -07:00
zomars 33250652b3 Double e2e shards 2023-11-29 14:40:21 -07:00
Morgan a65e18d92d chore: improve invitation form validation (#12594)
* chore: improve invitation form validation

* fixup! chore: improve invitation form validation

* fixup! fixup! chore: improve invitation form validation
2023-11-29 12:44:48 -07:00
Carina WollendorferandCarinaWolli d0f7085cb8 fix: updating workflow with new step and new active event type (#12592)
* correctly filter event types to avoid null values

* clean up all filters

* re-add reverted fix

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-29 23:51:12 +05:30
Morgan ea8437b4f7 fix: invite member by username (#12591)
* fix: invite member by username

* fixup! fix: invite member by username
2023-11-29 19:57:51 +02:00
Joe Au-YeungandOmar López 877cd4cdff refactor: Team Creation Flow [CAL-2751] (#12501)
* Create new endpoint for creating a team

* Generate a team checkout session

* Create team navigate to checkout

* Clean up

* UI changes

* Add comments

* Fix

* Type fix

* Type fix

* Type fix

* Type fixes

* Set telemetry

* Import fix

* Type fix

* Update tests

* Type fix

* fix: e2e

* fix: e2e

* fix: e2e

* fix: e2e

* Update teams.e2e.ts

* fix: e2e

---------

Co-authored-by: Omar López <zomars@me.com>
2023-11-29 09:39:21 -07:00
Igor Perzic bae3bd76e5 docs: update byte size for openssl command (#12587) 2023-11-29 15:15:04 +00:00
Joe Au-Yeung 8b24995d52 refactor: Abstract createBooking in handleNewBooking [CAL-2619] (#11959)
## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

In anticipation of refactoring `handleSeats` we need to abstract `createBooking` in order to get the return type. This PR is purposely aiming to do one thing so nothing is missed while refactoring `handleNewBooking`

Fixes # (issue)

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Requirement/Documentation

<!-- Please provide all documents that are important to understand the reason of that PR. -->

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

<!-- Please delete bullets that are not relevant. -->

- [x] Chore (refactoring code, technical debt, workflow improvements)


## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR

## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

## Checklist

<!-- Please remove all the irrelevant bullets to your PR -->

- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my PR needs changes to the documentation
- I haven't checked if my changes generate no new warnings
- I haven't added tests that prove my fix is effective or that my feature works
- I haven't checked if new and existing unit tests pass locally with my changes
2023-11-29 08:11:09 -07:00
Udit Takkarandsean-brydon 7dc6df2ad6 chore: improve logs (#12467)
* chore: improve logs

* fix: import

* chore: use safe stringify

* chore: add more logs to google calendar

* chore: use this.log

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-29 19:39:25 +05:30
Peer RichelsenandUdit Takkar 6c4b1154b8 chore: removed last web3 references (#12540)
* removed last web3 references

* updated yarn

* Update schema.prisma

* readded icon

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-29 11:57:50 +00:00
Pratik KumarandUdit Takkar 1099bad930 fix:selecting behaviour fixed (#12578)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-29 11:53:31 +00:00
Crowdin Bot dbd5d27d9d New Crowdin translations by Github Action 2023-11-29 09:57:23 +00:00
Udit TakkarandMorgan 45c4f4789b chore: add check for recording (#12584)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-11-29 09:54:14 +00:00
Morgan 07f42057fc fix: increase maxWait/Timeout invite members transaction (#12583) 2023-11-29 09:07:03 +00:00
Crowdin Bot 79ee7c41b2 New Crowdin translations by Github Action 2023-11-29 08:03:18 +00:00
Joe Au-Yeung 57e6971942 chore: Refactor <CalendarListContainer /> (#12388)
* Import calendarlistcontainer in settings

* Change CalendarListComponent to Figma design

* Add border subtle

* Address feedback
2023-11-29 13:29:51 +05:30
Alex van Andel bd6ca21e02 fix: Toggling really fast throws internal errors (#12580) 2023-11-28 23:04:54 +00:00
Carina Wollendorfer f19a1926b4 chore: refactor reschedule EventManager with changed organizer (#12574)
## What does this PR do?

Follow up for https://github.com/calcom/cal.com/pull/12469

Event creation is now already handled in the reschedule function of EventManager. 

## Type of change

- [x] Chore (refactoring code, technical debt, workflow improvements)

## How should this be tested?

- Same as in https://github.com/calcom/cal.com/pull/12469
2023-11-28 11:37:31 -07:00
Erik 477ee1c526 v3.5.3
release.yml / release (push) Canceled after 0s
2023-11-28 15:32:02 -03:00
Udit TakkarandJoe Au-Yeung 6704e18e32 fix: cal video issues (#12546)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-11-28 18:28:35 +00:00
Crowdin Bot 20f17903b9 New Crowdin translations by Github Action 2023-11-28 14:56:47 +00:00
8dabbbd09f feat: signup refactor (#11421)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-28 14:52:53 +00:00
sean-brydon 32933ed6ef fix: premium username to premium username (#12569) 2023-11-28 12:55:35 +00:00
Omar López c765ea9b31 fix: team and org invite e2e tests (#12566) 2023-11-28 10:49:06 +00:00
Matt NicollsandOmar López d3ddbf418f fix: fetch event type with a team permissions (#12448)
Co-authored-by: Omar López <zomars@me.com>
2023-11-27 23:14:59 -07:00
Joe Au-Yeung 79d0513428 Change method to getOrgMembership (#12563) 2023-11-27 14:46:55 -05:00
Carina WollendorferandCarinaWolli 230b82d3bf fix: rescheduling round robin events (emails and calendar events) (#12469)
* send the correct booking email for round robin rescheduling

* fixing originalBookingMemberEmails

* fix calendar event (needs some code refactoring)

* refactor rescheduling code

* code clean up

* add comment

* fix event name if host changes

* add tests for rr rescheduling emails

* fix videoCallUrl of google meet

* code clean up

* fix destinationCalendar for new booking

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-27 13:09:33 -05:00
Hariom Balhara c9b50ffb78 fix: Provide calOrigin for organization embeds (#12380) 2023-11-27 10:38:27 -07:00
Somay Chauhan ada0ef242b fix: crash on other team members and profile page (#12539) 2023-11-27 16:59:03 +00:00
Shashank KaulandPeer Richelsen b26e688950 fix(api): Added correct include for GET /teams/{teamId}/event-types (#12459)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-27 16:44:13 +00:00
Crowdin Bot d9041d03b7 New Crowdin translations by Github Action 2023-11-27 16:10:27 +00:00
Peer RichelsenandSean Brydon f60b3c14e1 fix: small UI improvements for troubleshooter (#12535)
Co-authored-by: Sean Brydon <sean@brydon.io>
2023-11-27 16:06:54 +00:00
Crowdin Bot fbcc43931e New Crowdin translations by Github Action 2023-11-27 13:57:45 +00:00
Udit Takkar a48e7cb39a fix: remove duplicate key in common.json (#12543)
* fix: remove duplicate key in common.json

* chore: add  both keys in same log
2023-11-27 19:23:35 +05:30
sean-brydon db3f718ba6 fix: removing next steps (#12549)
* Removing next steps

* Removing next steps
2023-11-27 16:49:40 +05:30
Morgan ca5dd4626d fix: event-types post api endpoint not showing hosts in response data (#12550) 2023-11-27 12:35:39 +02:00
Amit Sharma 4524d722f6 fix: phone-number-input (#12266) 2023-11-27 10:27:01 +00:00
Morgan 2094d59856 refactor: invite members handler (#12442)
* refactor: invite members handler

* fixup! refactor: invite members handler

* fixup! fixup! refactor: invite members handler

* refactor: promise all settled send emails

* fixup! refactor: promise all settled send emails

* fixup! fixup! refactor: promise all settled send emails

* fixup! fixup! fixup! refactor: promise all settled send emails

* fix: opening team invite link in email throws error on signup page

* fixup! Merge branch 'main' into cal-2698-refactor-invitemember-handler

* fix: centralize validation if invitee can be invited

* fix: improve select query and fix tests

* fixup! Merge branch 'main' into cal-2698-refactor-invitemember-handler

* rename functions and add some tests
2023-11-27 09:27:27 +00:00
Lauris Skraucis 7b350a5b8f revert: sync platform branch with main (#12548) 2023-11-27 08:35:58 +00:00
Brijendra Singh 5a869228d3 typo (#12541) 2023-11-26 14:34:40 +05:30
Peer Richelsen dabd5eae73 chore: fixed order of tips (#12529) 2023-11-25 13:55:40 -03:00
Somay ChauhanandUdit Takkar cb78de231a feat: if profile only has one public event-type, redirect to it (#12158)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-24 21:54:14 +00:00
DmytroHryshyn b0a1ef8a49 chore: remove dynamic loading of EventTypeDescription on event-types page (#12524) 2023-11-24 19:12:43 +00:00
Joe Au-Yeung 0a43aa3351 OAuth URL account for dev environment (#12530) 2023-11-24 17:38:21 +00:00
Lauris Skraucis ed4cce1c7e fix: platform branch sync with main (#12528) 2023-11-24 17:46:33 +01:00
Lauris Skraucis d595aa23c0 fix: platform branch sync with main (#12526) 2023-11-24 20:30:01 +05:30
Lauris Skraucis a6df49235f chore: sync platform branch with main (#12525) 2023-11-24 15:18:08 +01:00
Erik 5df41e37a0 fix: Stripe webhook event mismatch (#12522) 2023-11-24 13:18:06 +00:00
Peer Richelsen fca5778b6d chore: avatar padding (#12515) 2023-11-23 23:43:14 +00:00
Alex van Andel 0ddefaa141 Remove cache-control and disable cache (#12517) 2023-11-23 23:42:18 +00:00
c11f7aeffc chore: insights UI refresher (#12498)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-11-23 21:25:27 +00:00
Erik 9903fcaa05 chore: Remove free use of Cal.AI (#12489) 2023-11-23 21:07:08 +00:00
Alex van Andel 0910f65b87 v3.5.2
release.yml / release (push) Canceled after 0s
2023-11-23 16:56:52 +00:00
Alex van Andelandsean-brydon 343f8ee303 Avatar write and unset, ensure no bad behaviour (#12504)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-23 15:39:50 +00:00
Hariom Balhara de479bb2da fix: getting-started crash and build failure (#12506) 2023-11-22 22:41:20 -07:00
Omar López 5dc3065a47 v3.5.1
release.yml / release (push) Canceled after 0s
2023-11-22 15:42:24 -07:00
Manpreet Singh 36d315343c fix: adds teamId to team events payload (#12417) 2023-11-22 14:54:18 -05:00
zomars 2171a320f5 fix: Locks Stripe version 2023-11-22 12:19:30 -07:00
JA 7ee035c3a3 feat(webhooks): pass webhook secret to testTrigger (#12187) 2023-11-22 14:17:50 -05:00
Greg PabianandOmar López 1de05ebf3d chore: migrate the event-types page to the app directory (#12390)
Co-authored-by: Omar López <zomars@me.com>
2023-11-22 19:17:33 +00:00
zomars 53d7e2ec3f syncpack fix-mismatches 2023-11-22 12:08:30 -07:00
Udit Takkar f65c7e413f fix: default organizer bug in managed event type (#11921) 2023-11-22 12:43:25 -05:00
Joe Au-YeungandAlex van Andel b762f60214 test: Integration Test GCal Primary Calendar (#12011)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-11-22 11:15:47 -03:00
Joe Au-Yeung 2498785c49 chore: Clean Up Delete Credential Selected Calendar Error Message (#12353) 2023-11-22 10:22:03 -03:00
Matt Nicolls 9a6683e01d fix: include eventTypeId in BOOKING_CANCELLED event (#12445) 2023-11-22 12:04:51 +00:00
sebzzandUdit Takkar 2853288f49 docs: add google credentials in example env (#11695)
* docs:add google credentials in example env

* docs: add a space after #

* chore: update .env.example

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 11:49:27 +00:00
Morgan d04226ab9a fix: alby payment isPaid always false on create (#12463) 2023-11-22 11:39:00 +00:00
cb7ddc455a chore: Add team invite tests (#12425)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-22 11:26:43 +00:00
Adugna TadesseandJoe Au-Yeung 73aa1e8a22 outlook second account fix (#12013)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-11-22 11:01:29 +00:00
Udit Takkar a3b5263b76 chore: reset form on submission (#12465) 2023-11-22 10:42:19 +00:00
Peer Richelsen af2c6c0844 chore: ignore "platform" in pr-assign-team workflow (#12487) 2023-11-22 10:25:56 +00:00
Peer Richelsen 113195224a chore: fixed cal.ai thumbnail (#12486) 2023-11-22 10:23:54 +00:00
Peer Richelsen 828092c1d0 chore: fix cal.ai price (#12485) 2023-11-22 10:18:09 +00:00
Somay ChauhanandKeith Williams 4b060fc2cd fix: opening team invite link in email throws error on signup page (#12397)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-11-22 06:49:37 -03:00
Syed Ali ShahbazandUdit Takkar 16c5b070b6 fix: Admin Logic for event-type API endpoint (#12482)
* Fix Admin logic

* chore: fix prettier

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-22 06:46:03 -03:00
Hariom Balhara 48dde246e9 test: Add more orgs tests (#12241) 2023-11-21 14:03:01 -03:00
Morgan 556b382f75 fix: better errors for googlecalendar integration (#12403) 2023-11-21 15:14:25 +00:00
85237c4985 fix: Date overrides UI bug depending on screen size (#12423)
* Update DateOverrideInputDialog.tsx

fix: Date overrides UI bug depending on screen size (calcom#12406)

* chore: remove comment

---------

Co-authored-by: madhurgoyal19 <35370133+madhurgoyal19@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-21 14:56:59 +00:00
Crowdin Bot 404bc0e4d6 New Crowdin translations by Github Action 2023-11-21 12:13:50 +00:00
Morgan 00553e897b fix: alby payment could not be created (#12460)
* fix: alby payment could not be created

* fixup! fix: alby payment could not be created

* fixup! fixup! fix: alby payment could not be created
2023-11-21 12:09:50 +00:00
Crowdin Bot ed2ce005c9 New Crowdin translations by Github Action 2023-11-20 16:42:20 +00:00
Peer Richelsen 058844b6b8 chore: paid support wip (#12419)
* paid support wip

* nit

* added upgrade box to intercom

* nit
2023-11-20 22:08:39 +05:30
77fa3702db fix: slug value in webhook payload (#12290)
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-20 15:39:21 +00:00
9eb2ae470c fix: [embed-react] types fix for compatible with TypeScript's "moduleResolution": "bundler" (#12327)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-20 13:33:27 +00:00
Somay Chauhan 3de7b73f5f fix: don't send notes to booker if "Hide notes in calendar" is on (#12333) 2023-11-20 13:28:40 +00:00
Alex van Andel 8e7785ca64 fix: Better error reporting & some fixes to deleteUser API (#12439) 2023-11-20 13:28:25 +00:00
sean-brydonandUdit Takkar 608b891536 fix: identifer (#12426)
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-20 13:28:10 +00:00
Alex van AndelandPeer Richelsen 4f26ca1a7b feat: Base implementation of v2 of avatars (#12369)
* feat: Base implementation of v2 of avatars

* Make avatarUrl and logoUrl entirely optional

* Made necessary backwards compat changes

* fix: type errors

* Fix: OG image

* fix types

* Consistency with other behaviour, ux tweak

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-20 12:49:38 +00:00
Hariom Balhara c55b36f235 fix: Members count when team slug is same as org slug (#12124) 2023-11-20 18:01:50 +05:30
sean-brydon bdd3b132d4 feat: troubleshooter with weekly view (V2) (#12280)
* Inital UI + layout setup

* use booker approach of grid

* event-select - sidebar + store work

* adds get schedule by event-type-slug

* Calendar toggle

* Load schedule from event slug

* Add busy events to calendar

* useschedule

* Store more event info than just slug

* Add date override to calendar

* Changes sizes on smaller screens

* add event title as a tooltip

* Ensure header navigation works

* Stop navigator throwing errors on inital render

* Correct br

* Event duration fixes

* Add getMoreInfo if user is authed with current request.username

* Add calendar color map wip

* Add WIP comments for coloured outlines

* Revert more info changes

* Calculate date override correctly

* Add description option

* Fix inital schedule data not being populated

* Nudge overlap over to make it clearer

* Fix disabled state

* WIP on math logic

* Event list overlapping events logic

* NIT about width

* i18n + manage calendars link

* Delete old troubleshooter

* Update packages/features/calendars/weeklyview/components/event/EventList.tsx

* Remove t-slots

* Fix i18n & install calendar action

* sm:imrovments

* NITS

* Fix types

* fix: back button

* Month prop null as we control from query param

* Add head SEO

* Fix headseo import

* Fix date override tests
2023-11-20 17:49:33 +05:30
Crowdin Bot 9a4c20cca4 New Crowdin translations by Github Action 2023-11-20 11:08:01 +00:00
Hariom Balhara 2144fcb23e fix: add node-mocks-http to web (#12435) 2023-11-20 16:34:29 +05:30
Crowdin Bot f2c39fc786 New Crowdin translations by Github Action 2023-11-17 16:12:37 +00:00
Crowdin Bot e83e0a770c New Crowdin translations by Github Action 2023-11-17 16:09:31 +00:00
Omar Lópezandsean-brydon c818ef3188 feat: implements basic user locking for admins (#12393)
* feat: implements basic user locking for admins

* Update sendPasswordReset.handler.ts

* check fixes

* Update packages/features/ee/users/components/UsersTable.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-17 16:06:29 +00:00
MehulandPeer Richelsen 176564f4ed fix: logo lack of contrast (#12401)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-17 14:10:58 +00:00
Morgan 27d59b6413 fix: add idx scheduleId on eventType (#12400) 2023-11-17 11:01:13 +00:00
Somay ChauhanandPeer Richelsen 57a65401e7 fix: weird date override behaviour (#12292)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-16 21:09:48 +00:00
DmytroHryshynandzomars 28acbe549a chore: [app dir bootstrapping 9]: replace useSearchParams with useCompatSearchParams hook (#12056)
Co-authored-by: zomars <zomars@me.com>
2023-11-16 13:38:27 -07:00
Keith Williams 3534e3c224 Revert "perf: Increased memory/vCPU for slots perf (#12387)" (#12389)
This reverts commit 0b96ef5476.
2023-11-16 20:40:42 +02:00
Erik e48a6c3dcf chore: Disable i18n cache in development (#12374) 2023-11-16 16:56:24 +00:00
Carina WollendorferandCarinaWolli 7d09ccb0d7 fix: add sendgrid-specific header only for SendGrid smtp server (#11932)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-16 13:51:30 -03:00
Morgan 9029c5b4c4 fix: change api root statusCode to 200 (#12362) 2023-11-16 16:50:43 +00:00
a4c1df3658 refactor: team settings redesign (#12230)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-16 16:18:24 +00:00
Keith Williams 0b96ef5476 perf: Increased memory/vCPU for slots perf (#12387) 2023-11-16 11:57:37 -03:00
shaharyarshamshiandPeer Richelsen ca9e0f5b72 refactor: Removed the dirName config for app store cli (#12321)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-16 13:59:01 +00:00
a9418b06d6 fix: Wrong spelling of the word "pode" (fix-wrongStringPT-BR) (#12344)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-16 13:15:38 +00:00
Crowdin Bot 833a326c5b New Crowdin translations by Github Action 2023-11-16 13:09:03 +00:00
sean-brydon b7e2a62e26 chore: Use apiLogin instead of user.login in e2e tests (#12382) 2023-11-16 13:05:39 +00:00
Crowdin Bot f687056d60 New Crowdin translations by Github Action 2023-11-16 08:51:33 +00:00
0a39f53a4b fix(stripePaymentCallback): better errors (#12223)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-11-16 08:48:46 +00:00
Joe Au-Yeung ea0a64624c fix: saving credential id for payment apps (#12251)
## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

This PR adds the `credentialId` to payment app data. This fixes a bug where team installed payment apps were not working with team events.

Fixes # (issue)

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Requirement/Documentation

<!-- Please provide all documents that are important to understand the reason of that PR. -->

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

<!-- Please delete bullets that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Install Stripe for the individual user
- Enable it for the individual's event type
- The `credentialId` should be saved to the metadata
- Install Stripe to the user's team
- Enable it in the team's event type
- The `credentialId` should be saved to the metadata

## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

## Checklist

<!-- Remove bullet points below that don't apply to you -->

- I haven't checked if new and existing unit tests pass locally with my changes
2023-11-15 19:59:43 -07:00
Erik 6a8726f5f8 chore: Insights readonly DB client (#12373) 2023-11-15 14:50:20 -07:00
Omar López eb97e1660b v3.5.0
release.yml / release (push) Canceled after 0s
2023-11-15 14:48:10 -07:00
Udit Takkar 371a0f7245 feat: booking errors logging (#12325)
Fixes: https://github.com/calcom/cal.com/issues/12297
Fixes https://github.com/calcom/cal.com/issues/11234

- Displaying error message and X-Vercel-Id( Unique Request Id ) to user on book event form
- Improve error logging 
- Add Error codes

Few things to discuss

1) How to handle calendar integration failures ?
   Currently if for example google integration is broken and someone is trying to book that person then we log the error but don't inform the user that the google calendar is broken and the meeting goes through.
   
 Should I throw error when integration is broken ?
   
<img width="758" alt="Screenshot 2023-11-12 at 12 52 36 AM" src="https://github.com/calcom/cal.com/assets/53316345/c4d921c4-9c8a-4b9b-82a2-bbe0fdbcb3d4">

   
2)  How to handle conferencing app failures? 
 We just default to Cal Video  as location if we are unable to generated conferencing url and log the error and not inform the user(organizer).
2023-11-15 12:52:19 -07:00
Carina Wollendorfer 270d4f6e82 fix: round-robin booked out of availability (#12376)
## What does this PR do?

Fixes that it can happen that Round Robin host is booked outside of availability. 

I found and fixed the following two scenarios where this can happen: 
- when host has a date override
- when host is available for only a part the event time (for example, booking time 9:00-11:00 and user is only available between 10:00-11:00)

Fixes #10315
Fixes #11690

It also fixes that it can happen that round robin doesn't correctly pick the luck user (least recently booked). This happened when a user was an attendee of a booking before, then we always compared this booking and never the actual last booking of this user. 

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How should this be tested?

#### Booked outside of availability: 
1. 
- Create Round Robin event and assign user1 and user2 as round robin hosts
   - event duration: 120 minutes 
- user 1 availability: 
  - Monday to Friday 9:00-17:00
- user2 availability: 
  - Monday to Friday 10:00-17:00
- Book event at a 9:00 slot -> check if i user1 is booked 
- Book event again at a 9:00 slot -> check if user1 is booked again (user2 is not available at that time)

2.
- Change availability of user2
   - Mark Monday as unavailable 
   -   Add date override on any day this month 
- Book any Monday this month -> see that user 1 is booked 
- Again Book any Monday this month -> see that user 1 is booked again 

#### Wrong lucky user 
- Book event and add user1's email as the attendee email address
- Book several slots where both users should be available, and see that it alternates between user1 and user2 (before it ended up always booking user1)

## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-11-15 12:49:03 -07:00
DmytroHryshyn 48e7b616b8 chore: [app dir bootstrapping 10] adds ab tests middleware (#12045)
## What does this PR do?
 - Adds AB tests middleware that redirects users to the app-dir pages with probability that is defined by env var
 
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

Fixes # (issue)

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Requirement/Documentation

<!-- Please provide all documents that are important to understand the reason of that PR. -->

This PR requires new env variables:
`APP_ROUTER_EVENT_TYPES_ENABLED`  - boolean that defines if app dir event-types page redirect is enabled
`AB_TEST_BUCKET_PROBABILITY` - number [0, 100] that defines the percentage of users getting redirected to the app dir pages

## Type of change

<!-- Please delete bullets that are not relevant. -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] Chore (refactoring code, technical debt, workflow improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->
Does not requires testing, implements middleware as a dead code

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-11-15 11:06:18 -07:00
Crowdin Bot 8b80ea99c2 New Crowdin translations by Github Action 2023-11-15 16:18:21 +00:00
Rajiv Sahal 150c6e0f50 refactor: rename AtomsProvider to CalProvider (#12375)
* rename context provider

* rename atoms provider export
2023-11-15 16:14:37 +00:00
Crowdin Bot 4037e87fbd New Crowdin translations by Github Action 2023-11-15 12:33:14 +00:00
a804a29516 feat: Stripe paid apps flow (#12103)
* chore: Stripe paid apps flow

* chore: Subscription

* chore: Webhooks

* chore: Abstract functions

* chore: Lockfile

* chore: Webhook handler

* chore: Use catch-all

* chore: Webhook changes, etc

* chore: Cleanup

* chore: Use actual price id

* chore: Updates

* chore: Install normally until expiry date

* Disable team install for paid apps and cal.ai\

* Fix the same at another place

* Fix Typescript error

* redactedCause doesnt have message has enumerable prop

* Fix reinstallation of an already installed app

* chore: Remove unused deps

* chore: Ensure index

* chore: Price in usd

* chore: PR suggestion

* Fix missing packages in yarn.lock

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-15 09:29:41 -03:00
Crowdin Bot 87b514b91b New Crowdin translations by Github Action 2023-11-15 10:14:51 +00:00
Crowdin Bot 693ca04626 New Crowdin translations by Github Action 2023-11-15 10:11:45 +00:00
Crowdin Bot 67f83c7e7d New Crowdin translations by Github Action 2023-11-15 10:08:40 +00:00
Crowdin Bot ecfecd832d New Crowdin translations by Github Action 2023-11-15 10:05:17 +00:00
Crowdin Bot e55302cbc5 New Crowdin translations by Github Action 2023-11-15 10:02:20 +00:00
Crowdin Bot 9514a68039 New Crowdin translations by Github Action 2023-11-15 09:59:22 +00:00
Crowdin Bot 4e3d159401 New Crowdin translations by Github Action 2023-11-15 09:56:40 +00:00
Mehul a519941b81 fix avatar remove button (#12249) 2023-11-15 15:23:33 +05:30
Crowdin Bot f62c58532d New Crowdin translations by Github Action 2023-11-15 00:52:06 +00:00
Ash Davis 0981c4cb23 feat: POST/PATCH availabilities API to accept date field (#12238) 2023-11-14 21:49:27 -03:00
9d06f6dd0e fix: Fix 'Book a new time' link in request-reschedule for Team Event (#12261)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-11-14 13:23:44 -07:00
Morgan 00df493623 revert: "fix: create prisma client singleton with immediately invoked function" (#12366) 2023-11-14 20:22:35 +00:00
Crowdin Bot 2cf2b11a7b New Crowdin translations by Github Action 2023-11-14 19:32:20 +00:00
Joe Au-Yeung 5c3fd0993e fix: getting shimmer app keys (#12363)
* Fix getting shimmer app keys

* Read from config
2023-11-15 00:59:32 +05:30
Crowdin Bot 811e655720 New Crowdin translations by Github Action 2023-11-14 15:30:43 +00:00
Crowdin Bot defaafc9be New Crowdin translations by Github Action 2023-11-14 15:27:31 +00:00
Crowdin Bot e0c6feff1f New Crowdin translations by Github Action 2023-11-14 15:23:26 +00:00
Morgan d17dcbd319 v3.4.10
release.yml / release (push) Canceled after 0s
2023-11-14 17:19:22 +02:00
Rajiv Sahal 90d52663ac feat: AtomsProvider component (#12354)
* init: atoms provider component

* chore: add atoms provider to index of atoms package

* cleanup comments
2023-11-14 20:47:48 +05:30
sean-brydon ada74467bc fix: Callback redirect (#12361) 2023-11-14 12:15:50 -03:00
Crowdin Bot 7db758c2d5 New Crowdin translations by Github Action 2023-11-14 14:04:22 +00:00
Amit Sharma 2867d53442 fix resend team member invite (#12360) 2023-11-14 13:59:49 +00:00
Crowdin Bot 31067fcd45 New Crowdin translations by Github Action 2023-11-14 12:59:06 +00:00
Hariom Balhara 8c2ce972cc fix: team booking page having same slug as the org (#12213) 2023-11-14 18:25:46 +05:30
Crowdin Bot d3ab11e38e New Crowdin translations by Github Action 2023-11-14 11:54:31 +00:00
Crowdin Bot 3d596fcb1a New Crowdin translations by Github Action 2023-11-14 11:51:13 +00:00
Crowdin Bot 820b45a912 New Crowdin translations by Github Action 2023-11-14 11:48:30 +00:00
Udit Takkar b945849736 chore: variables names (#12352) 2023-11-14 11:45:09 +00:00
Crowdin Bot 6b2c59b733 New Crowdin translations by Github Action 2023-11-14 10:27:14 +00:00
Crowdin Bot b7f85d785a New Crowdin translations by Github Action 2023-11-14 10:23:40 +00:00
Crowdin Bot 08e4e816c6 New Crowdin translations by Github Action 2023-11-14 10:20:41 +00:00
Crowdin Bot 20430022e1 New Crowdin translations by Github Action 2023-11-14 10:17:32 +00:00
Crowdin Bot e4c71e5d2c New Crowdin translations by Github Action 2023-11-14 10:14:23 +00:00
Crowdin Bot 28eda407be New Crowdin translations by Github Action 2023-11-14 10:11:07 +00:00
Crowdin Bot 9dab171520 New Crowdin translations by Github Action 2023-11-14 10:07:51 +00:00
Crowdin Bot d5f359291d New Crowdin translations by Github Action 2023-11-14 10:03:34 +00:00
Morgan 3c1d0a9bf1 fix: add translations to api dockerfile (#12349) 2023-11-14 09:59:32 +00:00
Crowdin Bot 3057edbd87 New Crowdin translations by Github Action 2023-11-14 04:59:52 +00:00
Carina WollendorferandCarinaWolli a2f859b55a fix: unhandled promise rejection in scheduleWorkflowReminder (#12301)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-14 10:27:16 +05:30
Crowdin Bot b2f4eaee63 New Crowdin translations by Github Action 2023-11-13 21:53:32 +00:00
Hariom Balhara b66c3f3452 fix: Org: Any team URL with slug starting with d is 404 (#12302) 2023-11-13 14:50:54 -07:00
Keith Williams 5ab0b85e52 chore: Reduce slots API serverless memory configuration (#12270)
* chore: Reduce API serverless memory configuration

* Only limit to slots for now

* Moved the vercel.json file to root

* More fun with vercel.json files

* Trying new pattern

* Using a pattern that matches the Vercel deployment summary function name

* Trying to set the memory directly in the handler file

* Trying it on all functions

* Changed glob

* Moved vercel.json file to root

* Trying a new glob

* Give me all functions

* Trying just * for all functions

* Trying again...

* Putting the config on the defaultResponder

* Seeing if web app takes setings

* Changed .js to .ts

* Changed root path

* Added back vercel.json now that the path is changed

* Reduced API slots memory to 512

* Removed unneeded code changes'

* Update defaultResponder.ts
2023-11-13 15:37:26 +00:00
Alex van Andel 4135c1038e v3.4.9
release.yml / release (push) Canceled after 0s
2023-11-13 15:13:25 +00:00
Crowdin Bot fc5190a33b New Crowdin translations by Github Action 2023-11-13 14:31:15 +00:00
Carina WollendorferandCarinaWolli f29328e6a2 fix: meeting url variable in workflow notifitcations (#12308)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-13 09:28:15 -05:00
Udit TakkarandHariom Balhara 621f7639ff fix: display organizer location after booking (#12088)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-11-13 19:29:50 +05:30
63f2f6edba fix: getTeamOrThrow ran on every request (#12337)
* fix: getTeamOrThrow ran on every request

This creates unnecessary strain on the DB when someone is hitting the endpoint a lot.

* Add rate limit

* Update packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts

---------

Co-authored-by: Sean Brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-13 13:45:46 +00:00
Morgan 199f41fb1a fix: add teamId index on VerificationToken (#12339)
* fix: add teamId index on InvitationToken

* fix: add migration
2023-11-13 13:42:27 +00:00
Crowdin Bot 3ea9faa414 New Crowdin translations by Github Action 2023-11-13 11:58:42 +00:00
Peer Richelsen 518ec4f605 feat: added /enterprise upgrade tip (#12207) 2023-11-13 13:55:28 +02:00
Hichem FantarandPeer Richelsen 4db0da5da4 feat: smooth transitions to interactive elements (#11983)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-12 17:45:44 +00:00
Crowdin Bot 115ded8429 New Crowdin translations by Github Action 2023-11-12 17:16:59 +00:00
DmytroHryshyn 71485d1b57 chore: [app dir bootstrapping 7] update middleware (#12044) 2023-11-12 17:14:38 +00:00
Crowdin Bot 87071c4f19 New Crowdin translations by Github Action 2023-11-11 11:19:34 +00:00
Hariom 412f15814e Add removeItem to webstorage 2023-11-11 11:16:35 +00:00
Hariom a57085cf64 fix: Broken embed in incognito of chrome 2023-11-11 11:16:35 +00:00
Crowdin Bot 65d1a34e39 New Crowdin translations by Github Action 2023-11-10 20:09:57 +00:00
Hariom BalharaandKeith Williams e4abc85382 fix: Fix recurring booking failure due to rate limiting (#12322)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-11-10 20:06:52 +00:00
Crowdin Bot eb1eb722f2 New Crowdin translations by Github Action 2023-11-10 18:41:47 +00:00
Amit Sharma 033bc45dd1 appstore-install-loader-fix (#12269) 2023-11-10 18:38:45 +00:00
affe85d02c fix: link attendee to meeting in ZohoCRM app (#12200)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-10 13:50:30 +00:00
Morgan d6f9a833bd fix: add missing config files to api dockerfile (#12318) 2023-11-10 12:50:49 +00:00
Keith Williams e4281a852a feat: Add Sentry to edge (#12304) 2023-11-10 08:47:55 -03:00
e4b2464548 fix: Fix "could not book the meeting" issue (fix-confirmBookingIssue) (#12305)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-11-09 18:43:48 -03:00
8c99a2a7af fix: fix bookings today (#12060)
* fix : fixed today's bookings in bookings upcoming section

* chore: improvements

* chore: fix type error

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-10 02:20:51 +05:30
Keith Williams 2236dfe411 v3.4.8
release.yml / release (push) Canceled after 0s
2023-11-09 14:43:25 -03:00
Udit Takkar a67369460a fix: ui fixes 2023-11-09 16:55:37 +00:00
Siddharth Movaliya b2526300a7 fix duplicate labels 2023-11-09 16:55:37 +00:00
Siddharth Movaliya d69395434f fix: Duplicate labels for Destination Calendar 2023-11-09 16:55:37 +00:00
Peer Richelsen 2720c9cc67 added tracking link 2023-11-09 16:36:35 +00:00
Peer Richelsen f1465a15f6 remove dependency 2023-11-09 16:36:35 +00:00
Peer Richelsen 1d24f2654d added deel app 2023-11-09 16:36:35 +00:00
Keith Williams f7eb86577c fix: Sentry warnings for startTransaction (#12300) 2023-11-09 12:55:53 -03:00
Peer Richelsen cc515f00b7 chore: fix avatar margin (#12274) 2023-11-09 15:34:28 +00:00
Morgan e695f75113 fix: create prisma client singleton with immediately invoked function (#12267) 2023-11-09 12:00:19 -03:00
Ash Davis 0d1b6ea5b2 feat: configure availability time picker interval by env variable (#12174) 2023-11-09 09:50:42 -05:00
Joe Au-Yeung 26637cda08 fix: When Installing Calendar App, Set Primary Calendar As Selected Calendar (#12291)
* GCal set primary calendar on install

* Outlook set default calendar as selected cal

* Zoho create selected calendar on install

* Lark create selected cal on install
2023-11-09 14:47:53 +00:00
Morgan 529054aa76 fix: add missing config files to api dockerfile (#12295) 2023-11-09 12:53:37 +00:00
Hariom Balhara 8deee738c2 fix: Recurring Booking - Check for conflicts on first 2 slots only (#11774)
* Add recurring booking tests and fix the bug

* Fix recurring booking tests supporting the new link verification assertions

* Convert tab to spaces
2023-11-09 17:00:51 +05:30
6848362683 feat: 11642 app shimmer video (#12159)
* added initial app

* created basic functionality for Shimmer Video app with tracking of Daily rooms

* changed the type config value in the shimmer video config.json

* re-fixed update to shimmer-video config type

* updated static images for shimmer video app

* fixed tracking Shimmer video event parameter

* Add zod files

* Allow query for "conferencing" apps

* Move to shimmer video

* Redirect to shimmer app

* Remove console.logs

* Remove legacy use of seed-app-store.

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Vik <vsreed@stanford.edu>
Co-authored-by: pathaksarvesh <sarvesh@incrediblevisibility.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2023-11-09 14:03:50 +05:30
Keith WilliamsandCarina Wollendorfer 09fc7e1a4c fix: Remove count from query to update schedule (#12287)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-11-08 14:47:33 -03:00
Carina WollendorferandCarinaWolli 2f73be0ee6 fix: tests for booking limits (#12284)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-08 17:13:56 +00:00
Ryukemeister 3cdf59f2cc fix capitalization typo for show event type message 2023-11-08 14:27:21 +00:00
Morgan 1f9880204e fix: payment cancel button redirects properly (#12254) 2023-11-08 12:34:50 +02:00
Peer Richelsen d0be48ebde chore: delete welcome workflow (#12279) 2023-11-08 09:39:56 +00:00
Keith Williams 5c8abbb3e9 v3.4.7
release.yml / release (push) Canceled after 0s
2023-11-07 15:07:42 -03:00
Carina WollendorferandCarinaWolli 7bb42ba577 fix: booking limits (#12172)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-07 14:17:05 -03:00
Keith WilliamsandPeer Richelsen 2e3b3257db feat: Add Sentry (#12155)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-07 13:18:25 -03:00
gitstart-app[bot]andgitstart-calcom d28e20e92e fix: Fix "Could not book the meeting" issue (fix-bookingIssue) (#12252)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
2023-11-07 12:33:49 -03:00
Carina WollendorferandCarinaWolli 8b89d6635a fix: recurring events with calendar connections (#12250)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-07 12:09:56 -03:00
Benny Joo 13ec810cb9 fix: [app dir bootstrapping 8] useParamsWithFallback hook and add tests (#12041)
* fix: first solution using RouterContext

* fix: second solution by importing router from next/compat/router

* fix return type
2023-11-07 13:48:02 +00:00
Morgan 367c8a96d8 feat: Add user's avatar to POST and PATCH requests in users endpoints (#11187) (#12256)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

Fix wrong import introduced in this pr: #11187

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Requirement/Documentation

<!-- Please provide all documents that are important to understand the reason of that PR. -->

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

<!-- Please delete bullets that are not relevant. -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR

## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

## Checklist

<!-- Remove bullet points below that don't apply to you -->

- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my PR needs changes to the documentation
- I haven't checked if my changes generate no new warnings
- I haven't added tests that prove my fix is effective or that my feature works
- I haven't checked if new and existing unit tests pass locally with my changes
2023-11-07 10:21:20 +00:00
e94c030c45 feat: Add user's avatar to POST and PATCH requests in users endpoints (#11187)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-07 08:06:52 +00:00
MorganandKeith Williams 6d0c7e4214 chore: init infra folder with dockerized api (#12243)
* chore(infra): scripts and dockerfile for api

* chore(infra): basic readme file

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-11-07 09:47:06 +02:00
Carina WollendorferandCarinaWolli e11cb9e3a5 fix: unhandled promise error in scheduleEmailReminders endpoint (#12183)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-06 09:14:35 -05:00
Manpreet Singh 809abd2f4f fix: Removes send an invite email option & always send email (#12229) 2023-11-06 13:30:37 +05:30
d1d50b0d91 test: Create E2E tests for bookings with custom/required Multiple Emails + other questions (teste2e-multipleEmailQuestion) (#11565)
* Remove unnecessary changes

* add changes

* Requested changes

* Requested changes

* fix: rename file to have .e2e

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2023-11-03 15:29:47 +00:00
Lauris Skraucis fc716f5921 fix: booking timeslots (#12195) 2023-11-03 10:58:58 -04:00
5348464972 test: Create E2E tests for bookings for all questions together as custom/required (teste2e-allQuestions) (#11592)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-11-03 11:12:17 -03:00
3fac6e698e test: Create E2E tests for bookings with custom/required Radio Group + other questions (teste2e-radioGroupQuestion) (#11567)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-11-03 11:11:18 -03:00
dcafbf672a test: Create E2E tests for bookings with custom/required Checkbox Group + other questions (teste2e-checkboxGroupQuestion) (#11568)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-11-03 11:10:17 -03:00
Rahul RoyandRahul Roy dff89a4df8 feat: #11922 Removed error for cancelling past time slots (#12126)
Co-authored-by: Rahul Roy <rahulro@maqsoftware.com>
2023-11-03 11:29:41 +00:00
Syed Ali Shahbaz ed04062540 fix: API to return hosts on event types response (#12204) 2023-11-03 11:28:16 +00:00
Siddharth MovaliyaandPeer Richelsen 23fda4a9e6 feat: The 'confirm' button triggers after entering the code (#12128)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-03 11:27:52 +00:00
Udit Takkar 14c3028c2a fix: use 18n 2023-11-03 11:27:09 +00:00
Udit Takkar dfe49e1732 fix: link input 2023-11-03 11:27:09 +00:00
Udit Takkar 6148c6baea chore: add placeholder 2023-11-03 11:27:09 +00:00
Udit Takkar 5366d891bb fix: improve error message 2023-11-03 11:27:09 +00:00
Sean Brydon 2ee021a87f fix:installed-calendar-info 2023-11-03 10:23:50 +00:00
Alex van Andel dd0c2dbff2 fix: Set the dateFrom to startOf day otherwise bad fromOffset is used (#12210) 2023-11-03 03:13:42 +00:00
Hariom Balhara f044c2d7c7 fix: Embed: data-cal-origin not used by modal(which is used by both element click popup and floating button popup) (#12075) 2023-11-02 19:39:58 +00:00
Somay Chauhanandsean-brydon 703bcd861e fix: should allow org owners and admins to delete members from subteams (#11710)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-02 14:37:19 -04:00
Syed Ali Shahbaz d4d96eb5cd fix admin check (#12175) 2023-11-02 12:29:15 +00:00
Hariom Balhara d8356e9a5b fix: Make sure if header is set already, it isn't set again (#12194) 2023-11-02 09:22:16 -03:00
Alexander Zeitler 9348279818 docs(README): remove unpaired symbol (#12133) 2023-11-02 09:36:47 +00:00
Peer Richelsen 5d77e4ce1d chore: changed readme (#12171) 2023-11-02 09:36:00 +00:00
Syed Ali Shahbaz 968e782dbe Update README.md (#12176) 2023-11-02 09:34:59 +00:00
Udit Takkar 01fb3dd03b fix: hide Check for recording (#12184) 2023-11-02 09:34:25 +00:00
Lauris Skraucis c3537f3290 docs: needs approval label (#12177)
Our CI workflow doesn't allow merging for only .md changes because the required check doesn't clear based on file filters.
2023-11-01 11:52:11 -03:00
3bb42f276e fix: Attach credentialId to location option (#12086)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-11-01 09:34:05 -04:00
Keith Williams 51fd4102ae v3.4.6
release.yml / release (push) Canceled after 0s
2023-10-31 17:25:46 -03:00
Ritesh KumarandPeer Richelsen 9d1ef0a649 chore: [CAL-2654] Broken Icons in org invitation email (#12119)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-31 20:00:07 +00:00
f80dc0738a test: Create E2E tests for bookings with custom/required Select + other questions (teste2e-selectQuestion) (#11564)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-10-31 19:44:18 +00:00
Peer Richelsen 678ab3f453 chore: remove merge conflicts workflow (#12173) 2023-10-31 19:43:38 +00:00
Syed Ali ShahbazandMorgan 199d3e4c3f fix: Auto-link credentials internally on destination calendar (#12055)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-10-31 13:54:09 -03:00
Siddharth Movaliya 79a6aef0e7 fix: text colour for no-show acknowledgment (#12132) 2023-10-31 16:41:25 +00:00
Udit TakkarandPeer Richelsen 4d49fb0636 fix: cal video recording email (#12079)
* fix: cal video recording email

* fix: add check for recording

* chore: remove logs

* chore: change message

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-31 13:35:23 -03:00
Udit Takkar 0be1387d0f fix: org settings for member improvements (#12161) 2023-10-31 16:24:51 +00:00
Morgan 58ab278813 fix(e2e): failsafe if no availabilities (#12168) 2023-10-31 16:10:08 +00:00
Alex van Andel 4de142cb7c fix: Check for same day, not with <= (#12167)
* fix: Check for same day, not with <=

* Cover this situation with a test

* Make the test timezone-independent
2023-10-31 14:48:47 +00:00
Hariom Balhara b4d27a9326 Fix Google spam policy alert layout in embed (#12153) 2023-10-31 10:46:35 +00:00
sean-brydon 31f3d9778e Use correct typing for totalTeamMembers (#12152) 2023-10-30 17:29:09 +02:00
sean-brydon 0a59c95b93 fix: impersonation for orgs (#12113) 2023-10-30 12:20:48 -03:00
Hariom BalharaandUdit Takkar 9e3465eeb6 fix: Support embedding org profile page (#12116)
* support embedding org profile page

* Add checkly tests

* Fix test titles

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-10-30 17:49:13 +05:30
Hariom Balhara 31fc4724e0 fix: request reschedule link in email for an Org event (#12125) 2023-10-30 16:25:12 +05:30
Hariom Balhara f81f0a26ec fix: Prevent possible reason behind avatar infinite redirect (#12143) 2023-10-30 09:19:06 +00:00
Hariom Balhara 9a80bb6194 fix: Skip failing tests (#12144) 2023-10-30 09:05:05 +00:00
Udit Takkar 901fc36c97 fix: padding in footer in profile (#12101) 2023-10-28 18:17:41 +01:00
Joe Au-Yeung 2831fb2b57 refactor: Falling Back to FirstCalendarCredential (#11986) 2023-10-27 11:52:56 -04:00
Hariom Balhara 426d31712e test: E2E for Orgs - The beginning (#12095) 2023-10-27 18:14:16 +05:30
Carina WollendorferandCarinaWolli 09ecd445bb fix adding managed event type to workflow (#12111)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-27 17:30:34 +05:30
Carina WollendorferandCarinaWolli 08d65c85de fix: cron scheduleEmailRemider time out (#12108)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-27 08:53:53 -03:00
Peer Richelsen b9cef10ef2 chore: new cal.ai tip (#12096) 2023-10-27 11:25:28 +00:00
Manish Singh BishtandPeer Richelsen 0dc41592f2 fix: use app.slug not hard coded zoom (#11963)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-27 12:05:30 +01:00
Leo Giovanetti aabf3c54ea Update Avatar.tsx (#12110) 2023-10-26 21:28:30 +00:00
Carina WollendorferandCarinaWolli c2a57fd72b split date ranges for calling /freebusy endpoint (#11962)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-26 10:29:08 -04:00
Carina WollendorferandCarinaWolli 52386e08f2 fix: pull managed event type bookings in zapier (#12106)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-26 10:18:37 -04:00
Keith Williams b724d367fc v3.4.5
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-26 10:38:57 -03:00
07924751ad test: Create E2E tests for bookings with custom/required Address + other questions (teste2e-addresQuestion) (#11563)
* Add E2E tests for address action in a regular booking

* Remove unnecessary changes

* change all tests

* Fix type check failing

* Update addressQuestion.e2e.ts

* Update addressQuestion.e2e.ts

* Requested changes

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-10-26 15:22:09 +03:00
Siddharth Movaliyaandsean-brydon defa8df7ca fix:fixes the padding and position of tabs on the Team Availability page (#12078)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-10-26 08:29:57 +00:00
Hariom Balhara bf8580fa88 fix: Embed Plus Org - Adds missing embed route, also fixes infinite redirection for migrated user in embed (#12071) 2023-10-26 13:42:05 +05:30
DmytroHryshynandzomars c7b1e4dfa1 chore: [app dir bootstrapping 6] server-side translations (#11995)
Co-authored-by: zomars <zomars@me.com>
2023-10-25 14:43:48 -07:00
139a7c8249 chore: [app dir bootstrapping 5] add RootLayout (#11982)
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Greg Pabian <35925521+grzpab@users.noreply.github.com>
2023-10-25 13:58:08 -07:00
Omar López 158da51a5d fix: embed rewrites post dotted usernames (#12087) 2023-10-25 19:33:22 +00:00
Siddharth MovaliyaandCarinaWolli f9ad99e572 feat: Lock timezone on booking page (#11891)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-25 14:16:01 -04:00
Alex van Andel 1c65f5c150 v3.4.4
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-25 19:00:14 +01:00
Aldrin 0fb75b715d fix: event type invalidation (#12077) 2023-10-25 18:29:41 +01:00
Syed Ali Shahbaz 9364055283 fix: typefix for webhook and rename oldBookingid to rescheduleId (#12084)
* add rescheduleId to type

* update oldBookingId to rescheduleId

* Remove old remnant
2023-10-25 18:26:22 +01:00
Syed Ali Shahbaz 1929b23ea8 Update handleNewBooking.ts (#12081) 2023-10-25 14:21:14 +01:00
Alex van Andel efc7be0b6b fix: Infinite loop in timezones on the negative side of UTC (#12063)
* fix: Infinite loop in timezones on the negative side of UTC

* Update packages/features/calendars/lib/getAvailableDatesInMonth.test.ts

* Revert back to real system time after test

* Handle all dates as local time, given this all happens in the browser
2023-10-25 17:18:25 +04:00
sean-brydonandUdit Takkar 327159c2ae fix/profile-dont-wait-for-avatar (#12080)
* fix/profile-dont-wait-for-avatar

* Update apps/web/pages/settings/my-account/profile.tsx

* Update apps/web/pages/settings/my-account/profile.tsx

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-10-25 10:41:25 +00:00
Hariom Balhara af801df421 Fixes in teams and avatar across org (#12070) 2023-10-25 15:57:29 +05:30
Morgan 79c1aa60a2 perf: database index on booking_status_starttime_endtime (#12066) 2023-10-24 14:34:27 -07:00
Greg PabianandOmar López a9535d3fd4 chore: [app dir bootstrapping 4.1] check nullability of navigation hook return values part 2 (#12065)
Co-authored-by: Omar López <zomars@me.com>
2023-10-24 20:52:59 +00:00
Omar López 0ae6506bc1 fix: prevents prisma idle connections (#12068) 2023-10-24 16:59:15 -03:00
sean-brydon a8c03262c2 fix: re-render on booker (#12058) 2023-10-24 12:15:17 -07:00
Crowdin Bot 687669ce17 New Crowdin translations by Github Action 2023-10-24 15:31:04 +00:00
Carina WollendorferandCarinaWolli bf6dd665f0 fix: response size scheduleEmailReminder (#12057)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-24 12:27:30 -03:00
Udit TakkarandPeer Richelsen 9250b91bb0 feat: remove location modal in event setup (#11796)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-24 17:59:54 +05:30
Hariom Balhara b934c74c30 fix: Avatar slug and cal links for cross org users (#12031) 2023-10-24 16:12:36 +05:30
96810b5ba1 test: Create E2E tests for bookings with custom/required Long Text + other questions (teste2e-longTextQuestion) (#11559)
* Add E2E tests for long test question in a regular booking

* Remove unnecessary changes

* change all tests

* Update longTextQuestion.e2e.ts

* refactor

* Update longTextQuestion.e2e.ts

* refactor: split cancelAndRescheduleBooking

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2023-10-24 07:22:52 -03:00
Peer Richelsen ee08118ed3 chore: removed vital.json (#12038) 2023-10-24 11:16:11 +01:00
Carina WollendorferandCarinaWolli 051353e7f1 fix: booking day wrong in booking list (#12007)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-24 11:05:53 +01:00
Matti Nannt 19f52429b0 fix: env.example requesting 24 bytes instead of 32 bytes encryption key (#12043) 2023-10-24 11:03:33 +01:00
4ed15d2755 fix: Event Type header layout issues (fix-headerLayout) (#12047)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-10-24 09:49:41 +01:00
zomars 154af1367a hotfix: unreachable rate limits 2023-10-23 15:14:01 -07:00
Crowdin Bot 1de60bcfeb New Crowdin translations by Github Action 2023-10-23 18:57:22 +00:00
Greg Pabian 3679854c43 chore: [app dir bootstrapping 3] check nullability in AppListCard (#11980) 2023-10-23 11:54:33 -07:00
Syed Ali Shahbaz ce64c494f4 fix: Allow passing secret for the webhooks via API (#12039) 2023-10-23 10:18:18 -03:00
Peer Richelsen df4aa24913 chore: improve cal.ai not-installed message (#12022) 2023-10-23 15:45:26 +03:00
Udit TakkarandPeer Richelsen aa54c013f8 fix: allow dots in username (#11706)
* fix: allow dots in username

* test: added unit tests for slugify

* test: add test for username change

* tests: add test  for username and dynamic booking

* fix: type error

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-23 13:37:30 +01:00
Crowdin Bot 0014ca6865 New Crowdin translations by Github Action 2023-10-23 12:06:24 +00:00
6c00c9b2b8 feat: km-localization-cambodia 🇰🇭 (#12027)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-10-23 12:02:45 +00:00
Alex van Andel 46fc67f70d fix: Date add 1 day adds 24 hours, not 1 day (#12019)
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
* Date add 1 day adds 24 hours, not 1 day, causing the last date to be lost on dst change

* Alternate fix with tests

* Extract logic so test file doesnt register tsx
2023-10-23 01:21:06 +01:00
Greg Pabian e91fe12219 chore: [app dir bootstrapping 2] ensure tests do not have explicit timeouts (#11970) 2023-10-20 16:57:13 -07:00
Crowdin Bot 2a8f7412dd New Crowdin translations by Github Action 2023-10-20 23:51:24 +00:00
Greg Pabian 64d634e406 chore: [app dir bootstrapping 1] generate nonce with native crypto API (#11969) 2023-10-20 16:48:20 -07:00
Greg Pabian 39cfe18ffe chore: [app dir bootstrapping 4] check nullability of navigation hook return values (#12005) 2023-10-20 16:47:05 -07:00
Crowdin Bot eac45c5e23 New Crowdin translations by Github Action 2023-10-20 17:17:26 +00:00
Crowdin Bot 446c2b0f0e New Crowdin translations by Github Action 2023-10-20 17:14:23 +00:00
Crowdin Bot 92e5aae901 New Crowdin translations by Github Action 2023-10-20 17:11:15 +00:00
Crowdin Bot bf3db721e2 New Crowdin translations by Github Action 2023-10-20 17:08:38 +00:00
Crowdin Bot 8d4561c866 New Crowdin translations by Github Action 2023-10-20 17:05:38 +00:00
Crowdin Bot 6627a211d7 New Crowdin translations by Github Action 2023-10-20 17:02:38 +00:00
Crowdin Bot 0763a64b30 New Crowdin translations by Github Action 2023-10-20 16:59:15 +00:00
Crowdin Bot d333a31221 New Crowdin translations by Github Action 2023-10-20 16:56:42 +00:00
Keith Williams 55a8a0d2d3 v3.4.3
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-20 13:53:48 -03:00
Crowdin Bot 3093216534 New Crowdin translations by Github Action 2023-10-20 16:52:30 +00:00
Crowdin Bot 99a1c36ffc New Crowdin translations by Github Action 2023-10-20 16:49:37 +00:00
Crowdin Bot f4e48f5fc1 New Crowdin translations by Github Action 2023-10-20 16:47:04 +00:00
c352dc647e fix: broken layout email embed generator (CALCOM-11779) (#11951)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-20 16:44:26 +00:00
Udit Takkar 34bb069b4a revert: feat: Shows link location (#12024) 2023-10-20 12:43:45 -03:00
Peer Richelsen 39ea9c112d chore: delete "unapproved issue" workflow (#12008) 2023-10-20 14:39:28 +00:00
e32d4648af fix: webhook overflow (#11968)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-20 14:38:52 +00:00
Crowdin Bot 20b7633ab5 New Crowdin translations by Github Action 2023-10-20 14:35:26 +00:00
6f017a7972 fix: fixed caldav app icon and text (#12016)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-20 14:32:07 +00:00
Peer Richelsen cbd0e2d287 chore: removed "set up your public profile" banner (#12001) 2023-10-20 15:31:59 +01:00
Axl ff739bf9be fix: pwa nav improvements (#11972) 2023-10-20 15:31:03 +01:00
Siddharth Movaliya ff3541910b fix: team availability slider overflow (#12020) 2023-10-20 15:27:03 +01:00
sean-brydon d043de7724 fix: overlay calendar modal (#12021) 2023-10-20 15:02:08 +01:00
sean-brydon be1517facd fix: get correct count for team members in slider (#12017) 2023-10-20 09:42:49 -03:00
kremedev 19eced00f5 fix: Unable to modify the location of a booking when rescheduling (#11651) 2023-10-20 09:00:00 +02:00
Hariom Balhara e2414b174a Handle non-org team with same slug as the organizations requestedSlug (#11996) 2023-10-20 00:05:34 +05:30
Keith Williams 5e3c0cdea1 v3.4.2
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-19 12:31:00 -03:00
sean-brydon 6b6d3d90e4 feat/use-primary-by-default-overlay (#11935) 2023-10-19 19:39:06 +05:30
9b348adb6a fix: added loading state to button (#11624)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2023-10-19 13:52:10 +00:00
Nafees NazikandPeer Richelsen feda420f0c fix: org team page not found with uppercase letters (#11737)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-19 14:44:43 +01:00
Syed Ali Shahbaz 4b818de0c8 feat: Allow hideBranding via public API (#11978) 2023-10-19 10:31:13 -03:00
614741d207 test: Create E2E tests for bookings with custom/required Phone + other questions (#11502)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2023-10-19 09:27:32 -03:00
2550485c49 feat: Shows link location and respective icon in /bookings (#11866)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-19 11:06:48 +02:00
Hariom BalharaandPeer Richelsen efc3e864bb fix: Missing avatar for non-migrated users on team booking page (#11977)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-19 08:32:56 +05:30
Joe Au-Yeung 1bf56fbe93 fix: When GCal OAuth Canceled, Do Not Create A Credential (#11987) 2023-10-18 18:35:24 -07:00
Hariom BalharaandSyed Ali Shahbaz 629629cb9e Add /embed route for booking/[uid] (#11976)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2023-10-18 18:43:12 +00:00
Peer Richelsen 8c0751b186 fix: location dropdown overflow (#11967) 2023-10-18 11:36:34 +00:00
sean-brydon 0b46f61a23 feat: (Overlay) Persist toggle option (#11961) 2023-10-18 11:16:02 +01:00
Carina WollendorferandCarinaWolli 0c92fbe11d fix: failing scheduleEmailReminder cron job (#11960)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-18 15:17:39 +05:30
Alex van Andel 59fa713549 v3.4.1
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-17 20:56:46 +01:00
Benny Jooandzomars 63416d4f33 chore: update tslog from 3.2.1 to 4.9.2 (#11717)
Co-authored-by: zomars <zomars@me.com>
2023-10-17 19:00:48 +00:00
DexterStoreyandtedspare 9e927af813 feat: add rate limiting and more error handling to Cal.ai (#11898)
Co-authored-by: tedspare <ted.spare@gmail.com>
2023-10-17 18:26:49 +00:00
Carina WollendorferandCarinaWolli 4b8bdeba74 fix: scheduleEmailReminders cron job (#11929)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-17 11:19:39 -07:00
Peer Richelsen 4e4d67c8c0 chore: updated AI readme (#11926) 2023-10-17 16:08:20 +01:00
Udit Takkar ffda234b3c chore: updat ai port number in readme (#11946) 2023-10-17 16:07:58 +01:00
Peer Richelsen 0bc44c36db feat: added example messages to cal ai post-install (#11944)
* added example messages to cal ai post-install

* Revert yarn.lock changes

* added search plceholder to app store
2023-10-17 16:03:12 +01:00
Crowdin Bot f18bee5c3d New Crowdin translations by Github Action 2023-10-17 14:40:42 +00:00
Alex van Andel 73e16215bd chore: Decouple API from tRPC handlers (#11947) 2023-10-17 15:37:04 +01:00
Peer Richelsen 75d7d2f172 chore: workflow for unapproved issues (#11937)
* chore: workflow for unapproved issues

* Update .github/workflows/comment-unapproved-issues
2023-10-17 12:34:40 +01:00
Crowdin Bot 269dca5b6d New Crowdin translations by Github Action 2023-10-17 11:32:49 +00:00
Alex van Andel 0fd6bed813 chore: Remove users from getSchedule return (#11940) 2023-10-17 13:29:21 +02:00
Joe Au-Yeung 33bef6acd0 Add conditional (#11862) 2023-10-17 11:27:10 +00:00
Crowdin Bot a6c4b31845 New Crowdin translations by Github Action 2023-10-17 11:25:34 +00:00
Crowdin Bot ea8ba8defc New Crowdin translations by Github Action 2023-10-17 11:22:37 +00:00
Crowdin Bot 94cb78491a New Crowdin translations by Github Action 2023-10-17 11:19:55 +00:00
Hariom Balhara d12a5c5883 fix: videoCallUrl not updating when rescheduling with a broken Calendar integration (#11923) 2023-10-17 16:46:24 +05:30
7a014761dc unlock all fields in limits tab (#11936)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-17 09:37:37 +00:00
Manpreet SinghandCarina Wollendorfer 91ac952a72 feat: Adds missing bookingId to Cancel Webhook payload (#11906)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-10-17 09:02:35 +00:00
Hariom Balhara 225055fb0c feat: Support moving a user and it's teams to an org as temporary approach (#11892) 2023-10-17 08:36:46 +05:30
Omar López d46e80c2ac fix: inconsistent timezones in e2e tests (#11841)
* fix: inconsistent timezones in e2e tests

* Update users.ts

* Update playwright.config.ts

* Update playwright.config.ts

* Update users.ts

* fix: timezone sensitive tests
2023-10-17 08:23:54 +05:30
Crowdin Bot fe364bd2da New Crowdin translations by Github Action 2023-10-17 00:12:06 +00:00
Syed Ali Shahbaz 2756dff735 fix: Adds mandatory credentiaId in Destination Calendar API endpoint POST (#11880) 2023-10-16 17:09:22 -07:00
Peer Richelsen 401f64b986 Create README.md 2023-10-16 18:57:06 +01:00
Alex van Andel a02dcf485f fix: Add username idx (#11918)
* Add index on 'users.username'

* Bring back fields that are in main
2023-10-16 17:21:01 +00:00
Greg Pabian f2ecd9818a fix: build locale based on validated codes and regions (#11912)
* fix: build locale based on validated codes and regions

* keep html lang stable

* fix type error
2023-10-16 17:29:35 +01:00
sean-brydonandKeith Williams bc81f659aa test: Tests the order of middleware in API (#11852)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-10-16 10:00:34 -03:00
sean-brydonandUdit Takkar f8f038c5e9 stack-troubleshooter-border-color-support (#11907)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-10-16 11:45:16 +00:00
alannncandCarinaWolli fefb6acc57 feat: download insights raw data as csv (#11645)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-16 13:27:25 +02:00
Chiranjeev Vishnoi 461120ad84 fix: [CAL-2593] Uploading a CSV File does not comma separate the values (#11756)
* csv email validation added

* most common csv delimeters added
2023-10-16 10:39:05 +01:00
Udit TakkarandPeer Richelsen bab72a5d2e test: Non admin members cannot create team in org (#11525)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-16 11:06:08 +02:00
Meenu Yadav e4011b4a23 fix: Webhook settings list not having enough padding on the top (#11878) 2023-10-16 10:00:15 +01:00
Alex van Andel 899b59620d v3.4.0
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-14 15:45:26 +01:00
Raj Patel c75b5bec07 fix: Toast message variable issue (#11879) 2023-10-14 14:39:17 +00:00
Bhanu Singh f0c10ba5ec Update DateOverrideInputDialog.tsx (#11884)
reduced the toast timeout, so button is not overlayed.
2023-10-14 15:24:48 +01:00
mohamed nasser 7f4b95123f Update package.json added types to export (#11767)
updated package to fix the problem of types not properly exported so it fails at build time if you are using typescript
2023-10-14 15:19:38 +01:00
Peer Richelsen 6238c625ae chore: fix AddToHomescreen.tsx (#11888)
* Update AddToHomescreen.tsx

* fixed text
2023-10-14 11:45:23 +00:00
Peer Richelsen dbe387890f chore: upgrade nextjs for ai package only (#11889) 2023-10-14 14:31:14 +03:00
Peer Richelsen 25517ad674 Update pr-assign-team-label.yml 2023-10-14 10:43:29 +01:00
DexterStoreyandtedspare 3047b5319b feat: cal.ai v1.2.0 (#11868)
* bump nextjs version in ai

* lowercase username and email

* onboarding email

* direct user to install app if not installed

* multiple suggested times for link flow

* summary of context prompt engineering

* specify the @username nuance and discourage Ids

* v1.2.0

* Update README

* Change title

* simplify and improve booking link flow

* add build:ai to package.json

* better onboarding copy

* onboarding touches

* remove console logs and temp hacks

* remove env vars in app store and token in AI app

* invited user id should be string

---------

Co-authored-by: tedspare <ted.spare@gmail.com>
2023-10-14 09:52:24 +01:00
Greg Pabian a5fa2ef8d0 chore: add tests for locale and directionality; set html.dir when the session locale is available (#11853) 2023-10-13 10:27:10 -07:00
Hariom Balhara 59faffe0d5 fix: Requires Confirmation when organizer reschedules (#11848) 2023-10-13 10:22:57 -07:00
Carina Wollendorfer b076a7dabc bypass global unsubsribe (#11877) 2023-10-13 13:17:38 +00:00
Chiranjeev Vishnoi cbab279c3b overflow of webhooks container in event settings fixed (#11871) 2023-10-13 11:39:06 +00:00
61be5c9bc1 fix: Button icons tooltips opening out of view due to padding issue (#11847)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-13 11:17:12 +00:00
Peer Richelsen 0a4f7da2df chore: new intercom icon (#11874) 2023-10-13 11:01:31 +01:00
Syed Ali ShahbazandOmar López a87d82fd47 chore: Refactors Destination calendar API endpoint with a bunch of fixes (#11789)
Co-authored-by: Omar López <zomars@me.com>
2023-10-12 13:53:48 -07:00
Omar López 208d040f6f fix: added missing prisma imports (#11867) 2023-10-12 13:22:20 -07:00
Siddharth Movaliya 8ebcfbb8d1 fix: Function sendScheduledSeatsEmails ignore rule 'Disable default confirmation emails for attendees' (#11722) 2023-10-12 16:57:35 +02:00
Omar LópezandPeer Richelsen 20803451de chore: upgrade to prisma 5.3.1 (#11666)
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-10-12 17:46:35 +03:00
DexterStorey 2c5cb6abe4 fix: reduce no-response errors in cal ai with increase of timeout threshold (#11845)
* increase agent loop timeout threshold

* increase api/receive timeout threshold
2023-10-12 12:55:42 +00:00
Syed Ali Shahbaz 522fd64f69 fix: Update slug for zoom app (#11795) 2023-10-12 13:49:25 +01:00
Meenu YadavandPeer Richelsen aaa6616777 fix: Background colour (#11844)
* fix: /event-type-single: Button icons tooltips opening out of view due to padding issue

* fix: background colour

* Remove padding

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-12 12:43:36 +00:00
Peer Richelsen 7bc3591080 chore: cal.ai app store entry (#11854)
* better app store

* added trial text
2023-10-12 13:30:43 +01:00
Hariom Balhara db059d84c3 fix: Duplicate Calendar Invites on rescheduling an accepted booking that requires confirmation (#11827) 2023-10-12 14:29:29 +02:00
Lars Artmann e3a9e61046 fix: missing types export from @calcom/embed-react (ECMAScript modules) (#11432) 2023-10-12 00:15:11 +01:00
Aldrin 03103e1efd chore: fix CR line endings from es lint (prettier/prettier) (#11588) 2023-10-12 00:14:22 +01:00
Kaiwalya KoparkarandPeer Richelsen 08b72e2b90 feat: Adding Elestio as a deployment option (#11759)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-12 00:12:19 +01:00
sean-brydon ed8a14fdd7 Remove overlay from embed (#11817) 2023-10-11 23:01:09 +00:00
sean-brydon 0e50f6d9f8 fix: overlay-switch-media-query (#11819) 2023-10-11 23:58:35 +01:00
Daniel Roe 833fe7c7b7 fix(prisma): don't declare prisma as a global object and explicitly import it (#11839) 2023-10-11 23:53:54 +01:00
Alex van AndelandPeer Richelsen 47bb3000f6 fix: Change sidebar skeleton width to w-full, better for i18n (#11832)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-11 22:53:07 +00:00
Rahul Roy 41dd7bbdde fix: Embed breaks back button behavior (#11775) 2023-10-11 23:50:11 +01:00
sean-brydon dc9a4f0dba feat: Add rate limting to API (#11783) 2023-10-11 23:44:00 +01:00
Daniel Roe 891a656449 fix: import prisma in calendarOverlay handler (#11837) 2023-10-11 23:35:11 +01:00
Abhinav-Developer-23 916ee66be2 fix: [CAL-2612] missing icon: request reschedule fixed #11829 (#11836) 2023-10-11 20:37:04 +01:00
Carina Wollendorfer 9d7d838131 fix: missing last day of month
## What does this PR do?

- Fix that the last day in Oktober was always shown as unavailable
This happened when availability was in Europe/London timezone

- Fix that the last day of November was always shown as unavailable
This happened also for other timezones

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How should this be tested?

I recommend testing it in staging instead of local, as I experienced some different behaviors on local 

- Reproduce the 2 bugs as described before my fix
- If you were able to reproduce it, see if it is fixed with my fixes in this PR (last day of octber and November should be shown as available)
- Check if availability starts and ends at the correct time on days were DST happens
2023-10-11 11:30:35 -07:00
Alex van Andel df29376b0e fix: Revert deletion of getLocaleFromRequest in #11534 (#11835) 2023-10-11 17:36:32 +00:00
Greg Pabian d72a1ee328 hotfix: use session locale when dealing with SSG-ed pages (#11831) 2023-10-11 16:15:11 +01:00
sean-brydon ce532e0ff6 test: api-middleware (#11825)
* tests/api-middleware

* Skip if true

* Remove prisma from response mock
2023-10-11 15:09:13 +01:00
f824490873 Change approach (#11681)
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2023-10-11 06:12:30 -03:00
Udit Takkar ebc70feef0 refactor: org settings (#11682) 2023-10-11 10:04:54 +01:00
Udit Takkar 226ac889c7 refactor: event type settings improvements (#11615) 2023-10-11 10:03:54 +01:00
zomars 4f942e1a13 hotfix: missing locale
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
Nit
2023-10-10 15:46:28 -07:00
Alex van Andel 38f389bce9 fix: newLocale fallback to 'en' locale (#11808) 2023-10-10 15:06:48 -07:00
Greg Pabian 39c3ef0d94 chore: ensure compat type definitions do not break existing types (#11741) 2023-10-10 14:50:15 -07:00
Alex van Andel 0f92afb4bd v3.3.7 2023-10-10 22:39:40 +01:00
Denzil Samuel 6d0bcb709d fix-issue#11797 (#11800) 2023-10-10 22:36:50 +01:00
Alex van AndelandOmar López ff3a9d03a4 fix: Bring back dir attr using Intl (#11806)
Co-authored-by: Omar López <zomars@me.com>
2023-10-10 21:28:41 +00:00
DexterStorey 303073822c hotfix for #11626 (#11807) 2023-10-10 21:27:16 +00:00
Anik Dhabal Babu 0cfbf7159e fix position and add transition (#11799) 2023-10-10 20:15:26 +01:00
Alex van Andel 150dc3acb9 fix: Remove credentials and some ts fixes (#11794) 2023-10-10 10:08:06 -07:00
Greg Pabian 778485b31d refactor: implementation of locale calculated server-side (#11534) 2023-10-10 09:36:28 -07:00
b4c6388ce0 feat: overlay your calendar (#11693)
* Init header + login modal component

* Add calendar settings for authed user

* Local storage and using query params for toggle

* Toggle connect screen if query param present and no session

* Local storage + store + way more than that should be in single commit

* Display busy events on weekly view

* Confirm booking slot of overlap exists

* use chevron right when on column view

* Show hover card - overlapping date times

* Invalidate on switch

* FIx clearing local storage when you login to another account

* Force re-render on url state (atom quirks)

* Add loading screen

* Add dialog close

* Remove extra grid config

* Translations

* [WIP] - tests

* fix: google calendar busy times (#11696)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* New Crowdin translations by Github Action

* fix: rescheduled value DB update on reschedule and insights view cancelleds (#11474)

* v3.3.5

* fix minutes string (#11703)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* Regenerated yarn.lock

* Add error component + loader

* await tests

* disable tests - add note

* Refactor to include selected time

* use no-scrollbar

* Fix i18n

* Fix tablet toolbar

* overflow + i18n

* Export empty object as test is TODO

* Uses booker timezone

* Fix hiding switch too early

* Handle selected timezone

* Fix timezone issues

* Fix timezone issues

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-10 12:05:20 +01:00
Udit Takkar f9af15175d fix: subteam avatar flciker (#11773) 2023-10-10 12:00:25 +01:00
Udit Takkar 84eda78c2c fix: add prisma import (#11781) 2023-10-10 09:18:53 +00:00
Hariom Balhara 2faf24fb98 test: Add collective scheduling tests (#11670) 2023-10-10 09:46:04 +05:30
1456e2d4d5 feat: Embed - Introduce prerender instruction - Lightning fast popup experience (#11303)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-10-10 03:10:04 +00:00
zomars a53ea33168 fix: API build 2023-10-09 19:55:54 -07:00
Jannes Blobel 6c8c3de87c fix: update inlang project settings file (#11442) 2023-10-09 18:39:04 +01:00
Syed Ali ShahbazandPeer Richelsen b4ab710b35 add yarn lockfile section for contributors (#11769)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-09 18:32:15 +01:00
b5cf4e50a9 fix: Hide SAML/OIDC login button if no sso connection exists (Self hosted instance) (#10903)
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2023-10-09 16:30:30 +01:00
Peer Richelsenandsean-brydon d8f6400add feat: added dark mode to login, logout, password reset (#11765)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-10-09 11:51:47 +00:00
Crowdin Bot 018b0e0543 New Crowdin translations by Github Action 2023-10-09 10:47:47 +00:00
a5993061f6 chore: throw toast when adding a new date overwrite (#11764)
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-09 10:44:11 +00:00
Crowdin Bot 0b310c0ea8 New Crowdin translations by Github Action 2023-10-09 10:43:24 +00:00
Crowdin Bot da62984b5c New Crowdin translations by Github Action 2023-10-09 10:40:23 +00:00
Crowdin Bot 16d73fab89 New Crowdin translations by Github Action 2023-10-09 10:37:08 +00:00
Crowdin Bot 873a4e2000 New Crowdin translations by Github Action 2023-10-09 10:33:51 +00:00
AldrinandPeer Richelsen d0d37ba34f docs: add an alternate approach to seed local database (#11307)
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-10-09 11:30:47 +01:00
76d476441f fix: Dialog is open after adding a new overwrite (#11220)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-09 09:55:14 +01:00
9857b46121 chore: add Navigation in storybook (CALCOM-10760) (#10867)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-10-09 08:45:54 +00:00
GitStart-Cal.comandgitstart-calcom a35750734c fix: InputField storybook file is not rendering (fix-inputField) (#10938)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-10-09 09:28:28 +01:00
Crowdin Bot 5b7224e47e New Crowdin translations by Github Action 2023-10-08 20:26:14 +00:00
Saquib AliandSaquib Ali f3623096e4 fix: decreased top and bottom padding (#11750)
Co-authored-by: Saquib Ali <saquib1998>
2023-10-08 21:23:23 +01:00
Roland 88d438f227 fix: icon size on connect with Alby button (#11746) 2023-10-08 17:57:45 +00:00
Keith Williams 8a8d284113 fix: API cold boot issues (#11740) 2023-10-06 19:17:07 +01:00
alannncandKeith Williams b4315b186a fix: seated event type public id cancels/reschedule entire booking without auth (#11667)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-10-06 13:17:15 -03:00
Alex van Andel a90848edcb v3.3.6
Auto Comment Merge Conflicts / auto-comment-merge-conflicts (push) Canceled after 0s
release.yml / release (push) Canceled after 0s
2023-10-06 15:13:59 +01:00
Syed Ali Shahbaz 49b0f48be0 feat: add parentid eventtype API along with children eventTypeIds (#11714)
* add parentId

* Allow POST parentId

* adds eventtypeid of children

* add doc

* adds userId in children

* adds validations

* adds docstring

* fix status codes

* check fix for owners
2023-10-06 13:58:51 +00:00
Udit Takkar ceb8906c27 fix: check if appslug exist (#11735) 2023-10-06 14:43:28 +01:00
Alex van Andel f9c578c87d fix: Backwards compatibility on google reschedule/cancel (#11733) 2023-10-06 14:27:36 +01:00
Peer Richelsen c105793486 more contrast for cal video (#11729) 2023-10-06 12:09:21 +00:00
Peer Richelsen 59b503eb7e yarn 2023-10-06 13:01:22 +01:00
Peer Richelsen b6a28b1950 chore: experimental warning for recurring events (#11727) 2023-10-06 11:00:01 +00:00
Crowdin Bot b6ea551580 New Crowdin translations by Github Action 2023-10-06 10:38:53 +00:00
c5bf75385f fix: add number_sms_notifications translation (#11724)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-10-06 10:35:56 +00:00
AldrinandSyed Ali Shahbaz 1298b05e1a fix: Single Use Private link does not regenerate after use (#11705)
* feat: pass down props for hashedlink

* feat: update booking mapper

---------

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2023-10-06 11:07:55 +01:00
Udit Takkar e0c27d8721 fix: remove subteam logo (#11715) 2023-10-06 09:47:16 +02:00
666428ba53 fix: location icon doesn't change according to theme (#11504)
* Pass icon from eventmeta parent component

* Update readme

* fix: use dark:invert on map pin

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* chore: remove log

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: change svg name

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* Update packages/features/bookings/components/event-meta/AvailableEventLocations.tsx

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-10-06 07:46:21 +00:00
zomars ead11ef009 chore: Update yarn.lock 2023-10-05 13:24:21 -07:00
Siddharth Movaliyaandsean-brydon e7543c10a9 fix: fixes organization table UI flickers and adds sticky search & filter + table header (#11711)
* fix: #11213, #11215

* Update packages/ui/components/data-table/DataTableToolbar.tsx

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-10-05 20:24:03 +00:00
bba52db02f test: RFC: tests for EventTypeAppCardInterface (CALCOM-11005 ) (#11344)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
2023-10-05 11:50:26 -03:00
Ramsay Sewell a86d4ec89d fix: typo in reporting feature sentence (#11685) 2023-10-05 06:06:17 -03:00
Carina WollendorferandCarinaWolli d415944d5b fix minutes string (#11703)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-10-05 09:46:00 +01:00
847 changed files with 36230 additions and 11089 deletions
+36 -3
View File
@@ -25,6 +25,7 @@ CALCOM_LICENSE_KEY=
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
INSIGHTS_DATABASE_URL=
# Uncomment to enable a dedicated connection pool for Prisma using Prisma Data Proxy
# Cold boots will be faster and you'll be able to scale your DB independently of your app.
@@ -87,7 +88,7 @@ CRON_ENABLE_APP_SYNC=false
# Application Key for symmetric encryption and decryption
# must be 32 bytes for AES256 encryption algorithm
# You can use: `openssl rand -base64 24` to generate one
# You can use: `openssl rand -base64 32` to generate one
CALENDSO_ENCRYPTION_KEY=
# Intercom Config
@@ -106,6 +107,19 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
NEXT_PUBLIC_FRESHCHAT_TOKEN=
NEXT_PUBLIC_FRESHCHAT_HOST=
# Google OAuth credentials
# To enable Login with Google you need to:
# 1. Set `GOOGLE_API_CREDENTIALS` above
# 2. Set `GOOGLE_LOGIN_ENABLED` to `true`
# When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
GOOGLE_LOGIN_ENABLED=false
# - GOOGLE CALENDAR/MEET/LOGIN
# Needed to enable Google Calendar integration and Login with Google
# @see https://github.com/calcom/cal.com#obtaining-the-google-api-credentials
GOOGLE_API_CREDENTIALS=
# Inbox to send user feedback
SEND_FEEDBACK_EMAIL=
@@ -115,6 +129,10 @@ SENDGRID_API_KEY=
SENDGRID_EMAIL=
NEXT_PUBLIC_SENDGRID_SENDER_NAME=
# Sentry
# Used for capturing exceptions and logging messages
NEXT_PUBLIC_SENTRY_DSN=
# Twilio
# Used to send SMS reminders in workflows
TWILIO_SID=
@@ -126,7 +144,7 @@ TWILIO_WHATSAPP_PHONE_NUMBER=
NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID=
# Set it to "1" if you need to run E2E tests locally.
# Set it to "1" if you need to run E2E tests locally.
NEXT_PUBLIC_IS_E2E=
# Used for internal billing system
@@ -137,6 +155,7 @@ NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_ORG_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_WEBHOOK_SECRET_APPS=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
@@ -203,6 +222,8 @@ CSP_POLICY=
EDGE_CONFIG=
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
# Control time intervals on a user's Schedule availability
NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL=
# - ORGANIZATIONS *******************************************************************************************
# Enable Organizations non-prod domain setup, works in combination with organizations feature flag
@@ -230,6 +251,12 @@ AUTH_BEARER_TOKEN_VERCEL=
E2E_TEST_APPLE_CALENDAR_EMAIL=""
E2E_TEST_APPLE_CALENDAR_PASSWORD=""
# - CALCOM QA ACCOUNT
# Used for E2E tests on Cal.com that require 3rd party integrations
E2E_TEST_CALCOM_QA_EMAIL="qa@example.com"
# Replace with your own password
E2E_TEST_CALCOM_QA_PASSWORD="password"
# - APP CREDENTIAL SYNC ***********************************************************************************
# Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations
# Under settings/admin/apps ensure that all app secrets are set the same as the parent application
@@ -239,7 +266,7 @@ CALCOM_WEBHOOK_SECRET=""
CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret"
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
# Key should match on Cal.com and your application
# must be 32 bytes for AES256 encryption algorithm
# must be 24 bytes for AES256 encryption algorithm
# You can use: `openssl rand -base64 24` to generate one
CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY=""
@@ -257,3 +284,9 @@ E2E_TEST_OIDC_USER_EMAIL=
E2E_TEST_OIDC_USER_PASSWORD=
# ***********************************************************************************************************
# provide a value between 0 and 100 to ensure the percentage of traffic
# redirected from the legacy to the future pages
AB_TEST_BUCKET_PROBABILITY=50
# whether we redirect to the future/event-types from event-types or not
APP_ROUTER_EVENT_TYPES_ENABLED=1
@@ -47,3 +47,9 @@ assignees: ""
-->
(Share it here.)
---
##### House rules
- If this issue has a `🚨 needs approval` label, don't start coding yet. Wait until a core member approves feature request by removing this label, then you can start coding.
- For clarity: Non-core member issues automatically get the `🚨 needs approval` label.
- Your feature ideas are invaluable to us! However, they undergo review to ensure alignment with the product's direction.
+1 -1
View File
@@ -40,7 +40,7 @@ Fixes # (issue)
## Checklist
<!-- Please remove all the irrelevant bullets to your PR -->
<!-- Remove bullet points below that don't apply to you -->
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
-18
View File
@@ -1,18 +0,0 @@
name: Auto Comment Merge Conflicts
on: push
permissions:
pull-requests: write
jobs:
auto-comment-merge-conflicts:
runs-on: ubuntu-latest
steps:
- uses: codytseng/auto-comment-merge-conflicts@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
comment-body: "Hey there, there is a merge conflict, can you take a look?"
wait-ms: 3000
max-retries: 5
label-name: "🚨 merge conflict"
ignore-authors: dependabot,otherAuthor
+1 -1
View File
@@ -13,4 +13,4 @@ jobs:
with:
repo-token: ${{ secrets.GH_ACCESS_TOKEN }}
organization-name: calcom
ignore-labels: "app-store, authentication, automated-testing, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
ignore-labels: "app-store, ai, authentication, automated-testing, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
+2
View File
@@ -15,3 +15,5 @@ jobs:
- uses: ./.github/actions/yarn-install
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
- run: yarn test
# We could add different timezones here that we need to run our tests in
- run: TZ=America/Los_Angeles yarn test -- --timeZoneDependentTestsOnly
@@ -1,27 +0,0 @@
name: "Welcome new contributors"
on:
issues:
types: opened
pull_request:
types: opened
permissions:
pull-requests: write
issues: write
jobs:
welcome-message:
name: Welcoming New Users
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event.action == 'opened'
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |-
Thank you for making your first Pull Request and taking the time to improve Cal.com ! ❤️🎉
Feel free to join our [discord](https://go.cal.com/discord) and post your PR link to [collect XP and win prizes!](https://cal.com/blog/community-incentives)
issue-message: |
Thank you for opening your first issue, one of our team members will review it as soon as it possible. ❤️🎉
@@ -0,0 +1,15 @@
diff --git a/index.cjs b/index.cjs
index b645707a3549fc298508726e404243499bbed499..f34b0891e99b275a9218e253f303f43d31ef3f73 100644
--- a/index.cjs
+++ b/index.cjs
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
// https://github.com/babel/babel/issues/2212#issuecomment-131827986
// An alternative approach:
// https://www.npmjs.com/package/babel-plugin-add-module-exports
-exports = module.exports = min.parsePhoneNumberFromString
-exports['default'] = min.parsePhoneNumberFromString
+// exports = module.exports = min.parsePhoneNumberFromString
+// exports['default'] = min.parsePhoneNumberFromString
// `parsePhoneNumberFromString()` named export is now considered legacy:
// it has been promoted to a default export due to being too verbose.
@@ -0,0 +1,26 @@
diff --git a/dist/commonjs/serverSideTranslations.js b/dist/commonjs/serverSideTranslations.js
index bcad3d02fbdfab8dacb1d85efd79e98623a0c257..fff668f598154a13c4030d1b4a90d5d9c18214ad 100644
--- a/dist/commonjs/serverSideTranslations.js
+++ b/dist/commonjs/serverSideTranslations.js
@@ -36,7 +36,6 @@ var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _createConfig = require("./config/createConfig");
var _node = _interopRequireDefault(require("./createClient/node"));
-var _appWithTranslation = require("./appWithTranslation");
var _utils = require("./utils");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
@@ -110,12 +109,8 @@ var serverSideTranslations = /*#__PURE__*/function () {
lng: initialLocale
}));
localeExtension = config.localeExtension, localePath = config.localePath, fallbackLng = config.fallbackLng, reloadOnPrerender = config.reloadOnPrerender;
- if (!reloadOnPrerender) {
- _context.next = 18;
- break;
- }
_context.next = 18;
- return _appWithTranslation.globalI18n === null || _appWithTranslation.globalI18n === void 0 ? void 0 : _appWithTranslation.globalI18n.reloadResources();
+ return void 0;
case 18:
_createClient = (0, _node["default"])(_objectSpread(_objectSpread({}, config), {}, {
lng: initialLocale
+57 -1
View File
@@ -2,7 +2,18 @@
Contributions are what makes the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## House rules
- Before submitting a new issue or PR, check if it already exists in [issues](https://github.com/calcom/cal.com/issues) or [PRs](https://github.com/calcom/cal.com/pulls).
- GitHub issues: take note of the `🚨 needs approval` label.
- **For Contributors**:
- Feature Requests: Wait for a core member to approve and remove the `🚨 needs approval` label before you start coding or submit a PR.
- Bugs, Security, Performance, Documentation, etc.: You can start coding immediately, even if the `🚨 needs approval` label is present. This label mainly concerns feature requests.
- **Our Process**:
- Issues from non-core members automatically receive the `🚨 needs approval` label.
- We greatly value new feature ideas. To ensure consistency in the product's direction, they undergo review and approval.
## Priorities
@@ -161,3 +172,48 @@ If you get errors, be sure to fix them before committing.
- If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. See more about [Linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
- Be sure to fill the PR Template accordingly.
- Review [App Contribution Guidelines](./packages/app-store/CONTRIBUTING.md) when building integrations
## Guidelines for committing yarn lockfile
Do not commit your `yarn.lock` unless you've made changes to the `package.json`. If you've already committed `yarn.lock` unintentionally, follow these steps to undo:
If your last commit has the `yarn.lock` file alongside other files and you only wish to uncommit the `yarn.lock`:
```bash
git checkout HEAD~1 yarn.lock
git commit -m "Revert yarn.lock changes"
```
If you've pushed the commit with the `yarn.lock`:
1. Correct the commit locally using the above method.
2. Carefully force push:
```bash
git push origin <your-branch-name> --force
```
If `yarn.lock` was committed a while ago and there have been several commits since, you can use the following steps to revert just the `yarn.lock` changes without impacting the subsequent changes:
1. **Checkout a Previous Version**:
- Find the commit hash before the `yarn.lock` was unintentionally committed. You can do this by viewing the Git log:
```bash
git log yarn.lock
```
- Once you have identified the commit hash, use it to checkout the previous version of `yarn.lock`:
```bash
git checkout <commit_hash> yarn.lock
```
2. **Commit the Reverted Version**:
- After checking out the previous version of the `yarn.lock`, commit this change:
```bash
git commit -m "Revert yarn.lock to its state before unintended changes"
```
3. **Proceed with Caution**:
- If you need to push this change, first pull the latest changes from your remote branch to ensure you're not overwriting other recent changes:
```bash
git pull origin <your-branch-name>
```
- Then push the updated branch:
```bash
git push origin <your-branch-name>
```
+19 -4
View File
@@ -122,7 +122,7 @@ Here is what you need to be able to run Cal.com.
### Setup
1. Clone the repo into a public GitHub repository (or fork https://github.com/calcom/cal.com/fork). If you plan to distribute the code, keep the source code public to comply with [AGPLv3](https://github.com/calcom/cal.com/blob/main/LICENSE). To clone in a private repository, [acquire a commercial license](https://cal.com/sales))
1. Clone the repo into a public GitHub repository (or fork https://github.com/calcom/cal.com/fork). If you plan to distribute the code, keep the source code public to comply with [AGPLv3](https://github.com/calcom/cal.com/blob/main/LICENSE). To clone in a private repository, [acquire a commercial license](https://cal.com/sales)
```sh
git clone https://github.com/calcom/cal.com.git
@@ -221,7 +221,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
1. Set a 32 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set a 24 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
In a development environment, run:
@@ -253,6 +253,8 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
#### Setting up your first user
##### Approach 1
1. Open [Prisma Studio](https://prisma.io/studio) to look at or modify the database content:
```sh
@@ -264,6 +266,17 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `packages/prisma/schema.prisma` file.
1. Open a browser to [http://localhost:3000](http://localhost:3000) and login with your just created, first user.
##### Approach 2
Seed the local db by running
```sh
cd packages/prisma
yarn db-seed
```
The above command will populate the local db with dummy users.
### E2E-Testing
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
@@ -368,6 +381,10 @@ Currently Vercel Pro Plan is required to be able to Deploy this application with
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/calcom/docker)
### Elestio
[![Deploy on Elestio](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/cal.com)
<!-- ROADMAP -->
## Roadmap
@@ -580,8 +597,6 @@ Distributed under the [AGPLv3 License](https://github.com/calcom/cal.com/blob/ma
Special thanks to these amazing projects which help power Cal.com:
[<img src="https://cal.com/powered-by-vercel.svg">](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Vercel](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Next.js](https://nextjs.org/)
- [Day.js](https://day.js.org/)
+4
View File
@@ -0,0 +1,4 @@
# Checkly Tests
Run as `yarn checkly test`
Deploy the tests as `yarn checkly deploy`
+53
View File
@@ -0,0 +1,53 @@
import type { Page } from "@playwright/test";
import { test, expect } from "@playwright/test";
test.describe("Org", () => {
// Because these pages involve next.config.js rewrites, it's better to test them on production
test.describe("Embeds - i.cal.com", () => {
test("Org Profile Page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/embed");
expect(response?.status()).toBe(200);
await page.screenshot({ path: "screenshot.jpg" });
await expectPageToBeServerSideRendered(page);
});
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/peer/embed");
expect(response?.status()).toBe(200);
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
await expectPageToBeServerSideRendered(page);
});
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/peer/meet/embed");
expect(response?.status()).toBe(200);
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
await expectPageToBeServerSideRendered(page);
});
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/sales/embed");
expect(response?.status()).toBe(200);
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
await expectPageToBeServerSideRendered(page);
});
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/sales/hipaa/embed");
expect(response?.status()).toBe(200);
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
await expectPageToBeServerSideRendered(page);
});
});
});
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
async function expectPageToBeServerSideRendered(page: Page) {
expect(
await page.evaluate(() => {
return window.__NEXT_DATA__.props.pageProps.isEmbed;
})
).toBe(true);
}
+4
View File
@@ -83,6 +83,10 @@
"NEXT_PUBLIC_TEAM_IMPERSONATION": {
"description": "Set the following value to true if you wish to enable Team Impersonation",
"value": "false"
},
"NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL": {
"description": "Control time intervals on a user's Schedule availability",
"value": "15"
}
},
"scripts": {
+3
View File
@@ -6,6 +6,9 @@ FRONTEND_URL=http://localhost:3000
APP_ID=cal-ai
APP_URL=http://localhost:3000/apps/cal-ai
# This is for the onboard route. Which domain should we send emails from?
SENDER_DOMAIN=cal.ai
# Used to verify requests from sendgrid. You can generate a new one with: `openssl rand -hex 32`
PARSE_KEY=
+34 -18
View File
@@ -1,12 +1,12 @@
# Cal.com Email Assistant
# Cal.ai
Welcome to the first stage of Cal.ai!
Welcome to [Cal.ai](https://cal.ai)!
This app lets you chat with your calendar via email:
- Turn informal emails into bookings eg. forward "wanna meet tmrw at 2pm?"
- List and rearrange your bookings eg. "Cancel my next meeting"
- Answer basic questions about your busiest times eg. "How does my Tuesday look?"
- List and rearrange your bookings eg. "clear my afternoon"
- Answer basic questions about your busiest times eg. "how does my Tuesday look?"
The core logic is contained in [agent/route.ts](/apps/ai/src/app/api/agent/route.ts). Here, a [LangChain Agent Executor](https://docs.langchain.com/docs/components/agents/agent-executor) is tasked with following your instructions. Given your last-known timezone, working hours, and busy times, it attempts to CRUD your bookings.
@@ -14,7 +14,11 @@ _The AI agent can only choose from a set of tools, without ever seeing your API
Emails are cleaned and routed in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts) using [MailParser](https://nodemailer.com/extras/mailparser/).
Incoming emails are routed by email address. Addresses are verified by [DKIM record](https://support.google.com/a/answer/174124?hl=en), making it hard to spoof them.
Incoming emails are routed by email address. Addresses are verified by [DKIM record](https://support.google.com/a/answer/174124?hl=en), making them hard to spoof.
## Recognition
<a href="https://www.producthunt.com/posts/cal-ai?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-cal&#0045;ai" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=419860&theme=light&period=daily" alt="Cal&#0046;ai - World&#0039;s&#0032;first&#0032;open&#0032;source&#0032;AI&#0032;scheduling&#0032;assistant | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/cal-ai?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cal&#0045;ai" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=419860&theme=light" alt="Cal&#0046;ai - World&#0039;s&#0032;first&#0032;open&#0032;source&#0032;AI&#0032;scheduling&#0032;assistant | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## Getting Started
@@ -22,27 +26,39 @@ Incoming emails are routed by email address. Addresses are verified by [DKIM rec
If you haven't yet, please run the [root setup](/README.md) steps.
Before running the app, please see [env.mjs](./src/env.mjs) for all required environment variables. You'll need:
Before running the app, please see [env.mjs](./src/env.mjs) for all required environment variables. Run `cp .env.example .env` in this folder to get started. You'll need:
- An [OpenAI API key](https://platform.openai.com/account/api-keys) with access to GPT-4
- A [SendGrid API key](https://app.sendgrid.com/settings/api_keys)
- A default sender email (for example, `ai@cal.dev`)
- The Cal.ai's app ID and URL (see [add.ts](/packages/app-store/cal-ai/api/index.ts))
- A default sender email (for example, `me@dev.example.com`)
- The Cal.ai app's ID and URL (see [add.ts](/packages/app-store/cal-ai/api/index.ts))
- A unique value for `PARSE_KEY` with `openssl rand -hex 32`
To stand up the API and AI apps simultaneously, simply run `yarn dev:ai`.
### Agent Architecture
The scheduling agent in [agent/route.ts](/apps/ai/src/app/api/agent/route.ts) calls an LLM (in this case, GPT-4) in a loop to accomplish a multi-step task. We use an [OpenAI Functions agent](https://js.langchain.com/docs/modules/agents/agent_types/openai_functions_agent), which is fine-tuned to output text suited for passing to tools.
Tools (eg. [`createBooking`](/apps/ai/src/tools/createBooking.ts)) are simply JavaScript methods wrapped by Zod schemas, telling the agent what format to output.
Here is the full architecture:
![Cal.ai architecture](/apps/ai/src/public/architecture.png)
### Email Router
To expose the AI app, run `ngrok http 3000` (or the AI app's port number) in a new terminal. You may need to install [nGrok](https://ngrok.com/).
To expose the AI app, run `ngrok http 3005` (or the AI app's port number) in a new terminal. You may need to install [nGrok](https://ngrok.com/).
To forward incoming emails to the Node.js server, one option is to use [SendGrid's Inbound Parse Webhook](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook).
To forward incoming emails to the serverless function at `/agent`, we use [SendGrid's Inbound Parse](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook).
1. [Sign up for an account](https://signup.sendgrid.com/)
2. Go to Settings > [Inbound Parse](https://app.sendgrid.com/settings/parse) > Add Host & URL.
3. For subdomain, use `<sub>.<domain>.com` for now, where `sub` can be any subdomain but `domain.com` will need to be verified via MX records in your environment variables, eg. on [Vercel](https://vercel.com/guides/how-to-add-vercel-environment-variables).
4. Use the nGrok URL from above as the **Destination URL**.
5. Activate "POST the raw, full MIME message".
6. Send an email to `<anyone>@ai.example.com`. You should see a ping on the nGrok listener and Node.js server.
7. Adjust the logic in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts), save to hot-reload, and send another email to test the behaviour.
1. Ensure you have a [SendGrid account](https://signup.sendgrid.com/)
2. Ensure you have an authenticated domain. Go to Settings > Sender Authentication > Authenticate. For DNS host, select `I'm not sure`. Click Next and add your domain, eg. `example.com`. Choose Manual Setup. You'll be given three CNAME records to add to your DNS settings, eg. in [Vercel Domains](https://vercel.com/dashboard/domains). After adding those records, click Verify. To troubleshoot, see the [full instructions](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication).
3. Authorize your domain for email with MX records: one with name `[your domain].com` and value `mx.sendgrid.net.`, and another with name `bounces.[your domain].com` and value `feedback-smtp.us-east-1.amazonses.com`, both with priority `10` if prompted.
4. Go to Settings > [Inbound Parse](https://app.sendgrid.com/settings/parse) > Add Host & URL. Choose your authenticated domain.
5. In the Destination URL field, use the nGrok URL from above along with the path, `/api/receive`, and one param, `parseKey`, which lives in [this app's .env](/apps/ai/.env.example) under `PARSE_KEY`. The full URL should look like `https://abc.ngrok.io/api/receive?parseKey=ABC-123`.
6. Activate "POST the raw, full MIME message".
7. Send an email to `[anyUsername]@example.com`. You should see a ping on the nGrok listener and server.
8. Adjust the logic in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts), save to hot-reload, and send another email to test the behaviour.
Please feel free to improve any part of this architecture.
Please feel free to improve any part of this architecture!
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/ai",
"version": "1.1.1",
"version": "1.2.1",
"private": true,
"author": "Cal.com Inc.",
"dependencies": {
@@ -8,7 +8,7 @@
"@t3-oss/env-nextjs": "^0.6.1",
"langchain": "^0.0.131",
"mailparser": "^3.6.5",
"next": "^13.4.6",
"next": "^13.5.4",
"supports-color": "8.1.1",
"zod": "^3.22.2"
},
+10
View File
@@ -5,6 +5,9 @@ import agent from "../../../utils/agent";
import sendEmail from "../../../utils/sendEmail";
import { verifyParseKey } from "../../../utils/verifyParseKey";
// Allow agent loop to run for up to 5 minutes
export const maxDuration = 300;
/**
* Launches a LangChain agent to process an incoming email,
* then sends the response to the user.
@@ -37,6 +40,13 @@ export const POST = async (request: NextRequest) => {
return new NextResponse("ok");
} catch (error) {
await sendEmail({
subject: `Re: ${subject}`,
text: "Thanks for using Cal.ai! We're experiencing high demand and can't currently process your request. Please try again later.",
to: user.email,
from: agentEmail,
});
return new NextResponse(
(error as Error).message || "Something went wrong. Please try again or reach out for help.",
{ status: 500 }
+44
View File
@@ -0,0 +1,44 @@
import type { NextRequest } from "next/server";
import prisma from "@calcom/prisma";
import { env } from "../../../env.mjs";
import sendEmail from "../../../utils/sendEmail";
export const POST = async (request: NextRequest) => {
const { userId } = await request.json();
const user = await prisma.user.findUnique({
select: {
email: true,
name: true,
username: true,
},
where: {
id: userId,
},
});
if (!user) {
return new Response("User not found", { status: 404 });
}
await sendEmail({
subject: "Welcome to Cal AI",
to: user.email,
from: `${user.username}@${env.SENDER_DOMAIN}`,
text: `Hi ${
user.name || `@${user.username}`
},\n\nI'm Cal AI, your personal booking assistant! I'll be here, 24/7 to help manage your busy schedule and find times to meet with the people you care about.\n\nHere are some things you can ask me:\n\n- "Book a meeting with @someone" (The @ symbol lets you tag Cal.com users)\n- "What meetings do I have today?" (I'll show you your schedule)\n- "Find a time for coffee with someone@gmail.com" (I'll intro and send them some good times)\n\nI'm still learning, so if you have any feedback, please tweet it to @calcom!\n\nRemember, you can always reach me here, at ${
user.username
}@${
env.SENDER_DOMAIN
}.\n\nLooking forward to working together (:\n\n- Cal AI, Your personal booking assistant`,
html: `Hi ${
user.name || `@${user.username}`
},<br><br>I'm Cal AI, your personal booking assistant! I'll be here, 24/7 to help manage your busy schedule and find times to meet with the people you care about.<br><br>Here are some things you can ask me:<br><br>- "Book a meeting with @someone" (The @ symbol lets you tag Cal.com users)<br>- "What meetings do I have today?" (I'll show you your schedule)<br>- "Find a time for coffee with someone@gmail.com" (I'll intro and send them some good times)<br><br>I'm still learning, so if you have any feedback, please send it to <a href="https://twitter.com/calcom">@calcom</a> on X!<br><br>Remember, you can always reach me here, at ${
user.username
}@${env.SENDER_DOMAIN}.<br><br>Looking forward to working together (:<br><br>- Cal AI`,
});
return new Response("OK", { status: 200 });
};
+39 -12
View File
@@ -3,6 +3,7 @@ import { simpleParser } from "mailparser";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import prisma from "@calcom/prisma";
import { env } from "../../../env.mjs";
@@ -14,6 +15,10 @@ import now from "../../../utils/now";
import sendEmail from "../../../utils/sendEmail";
import { verifyParseKey } from "../../../utils/verifyParseKey";
// Allow receive loop to run for up to 30 seconds
// Why so long? the rate determining API call (getAvailability, getEventTypes) can take up to 15 seconds at peak times so we give it a little extra time to complete.
export const maxDuration = 30;
/**
* Verifies email signature and app authorization,
* then hands off to booking agent.
@@ -27,18 +32,37 @@ export const POST = async (request: NextRequest) => {
const formData = await request.formData();
const body = Object.fromEntries(formData);
// body.dkim looks like {@domain-com.22222222.gappssmtp.com : pass}
const signature = (body.dkim as string).includes(" : pass");
const envelope = JSON.parse(body.envelope as string);
const aiEmail = envelope.to[0];
const subject = body.subject || "";
try {
await checkRateLimitAndThrowError({
identifier: `ai:email:${envelope.from}`,
rateLimitingType: "ai",
});
} catch (error) {
await sendEmail({
subject: `Re: ${subject}`,
text: "Thanks for using Cal.ai! You've reached your daily limit. Please try again tomorrow.",
to: envelope.from,
from: aiEmail,
});
return new NextResponse("Exceeded rate limit", { status: 200 }); // Don't return 429 to avoid triggering retry logic in SendGrid
}
// Parse email from mixed MIME type
const parsed: ParsedMail = await simpleParser(body.email as Source);
if (!parsed.text && !parsed.subject) {
await sendEmail({
subject: `Re: ${subject}`,
text: "Thanks for using Cal.ai! It looks like you forgot to include a message. Please try again.",
to: envelope.from,
from: aiEmail,
});
return new NextResponse("Email missing text and subject", { status: 400 });
}
@@ -55,14 +79,17 @@ export const POST = async (request: NextRequest) => {
},
},
},
where: { email: envelope.from, credentials: { some: { appId: env.APP_ID } } },
where: { email: envelope.from },
});
// body.dkim looks like {@domain-com.22222222.gappssmtp.com : pass}
const signature = (body.dkim as string).includes(" : pass");
// User is not a cal.com user or is using an unverified email.
if (!signature || !user) {
await sendEmail({
html: `Thanks for your interest in Cal.ai! To get started, Make sure you have a <a href="https://cal.com/signup" target="_blank">cal.com</a> account with this email address.`,
subject: `Re: ${body.subject}`,
html: `Thanks for your interest in Cal.ai! To get started, Make sure you have a <a href="https://cal.com/signup" target="_blank">cal.com</a> account with this email address and then install Cal.ai here: <a href="https://go.cal.com/ai" target="_blank">go.cal.com/ai</a>.`,
subject: `Re: ${subject}`,
text: `Thanks for your interest in Cal.ai! To get started, Make sure you have a cal.com account with this email address. You can sign up for an account at: https://cal.com/signup`,
to: envelope.from,
from: aiEmail,
@@ -79,7 +106,7 @@ export const POST = async (request: NextRequest) => {
await sendEmail({
html: `Thanks for using Cal.ai! To get started, the app must be installed. <a href=${url} target="_blank">Click this link</a> to install it.`,
subject: `Re: ${body.subject}`,
subject: `Re: ${subject}`,
text: `Thanks for using Cal.ai! To get started, the app must be installed. Click this link to install the Cal.ai app: ${url}`,
to: envelope.from,
from: aiEmail,
@@ -106,7 +133,7 @@ export const POST = async (request: NextRequest) => {
if ("error" in availability) {
await sendEmail({
subject: `Re: ${body.subject}`,
subject: `Re: ${subject}`,
text: "Sorry, there was an error fetching your availability. Please try again.",
to: user.email,
from: aiEmail,
@@ -117,7 +144,7 @@ export const POST = async (request: NextRequest) => {
if ("error" in eventTypes) {
await sendEmail({
subject: `Re: ${body.subject}`,
subject: `Re: ${subject}`,
text: "Sorry, there was an error fetching your event types. Please try again.",
to: user.email,
from: aiEmail,
@@ -135,8 +162,8 @@ export const POST = async (request: NextRequest) => {
body: JSON.stringify({
apiKey,
userId: user.id,
message: parsed.text,
subject: parsed.subject,
message: parsed.text || "",
subject: parsed.subject || "",
replyTo: aiEmail,
user: {
email: user.email,
+2
View File
@@ -20,6 +20,7 @@ export const env = createEnv({
FRONTEND_URL: process.env.FRONTEND_URL,
APP_ID: process.env.APP_ID,
APP_URL: process.env.APP_URL,
SENDER_DOMAIN: process.env.SENDER_DOMAIN,
PARSE_KEY: process.env.PARSE_KEY,
NODE_ENV: process.env.NODE_ENV,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
@@ -36,6 +37,7 @@ export const env = createEnv({
FRONTEND_URL: z.string().url(),
APP_ID: z.string().min(1),
APP_URL: z.string().url(),
SENDER_DOMAIN: z.string().min(1),
PARSE_KEY: z.string().min(1),
NODE_ENV: z.enum(["development", "test", "production"]),
OPENAI_API_KEY: z.string().min(1),
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+1 -1
View File
@@ -47,7 +47,7 @@ const createBooking = async ({
}
const responses = {
id: invite,
id: invite.toString(),
name: user.username,
email: user.email,
};
+124
View File
@@ -0,0 +1,124 @@
import { DynamicStructuredTool } from "langchain/tools";
import { z } from "zod";
import { env } from "~/src/env.mjs";
import type { User, UserList } from "~/src/types/user";
import sendEmail from "~/src/utils/sendEmail";
export const sendBookingEmail = async ({
user,
agentEmail,
subject,
to,
message,
eventTypeSlug,
slots,
date,
}: {
apiKey: string;
user: User;
users: UserList;
agentEmail: string;
subject: string;
to: string;
message: string;
eventTypeSlug: string;
slots?: {
time: string;
text: string;
}[];
date: {
date: string;
text: string;
};
}) => {
// const url = `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?date=${date}`;
const timeUrls = slots?.map(({ time, text }) => {
return {
url: `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?slot=${time}`,
text,
};
});
const dateUrl = {
url: `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?date=${date.date}`,
text: date.text,
};
await sendEmail({
subject,
to,
cc: user.email,
from: agentEmail,
text: message
.split("[[[Slots]]]")
.join(timeUrls?.map(({ url, text }) => `${text}: ${url}`).join("\n"))
.split("[[[Link]]]")
.join(`${dateUrl.text}: ${dateUrl.url}`),
html: message
.split("\n")
.join("<br>")
.split("[[[Slots]]]")
.join(timeUrls?.map(({ url, text }) => `<a href="${url}">${text}</a>`).join("<br>"))
.split("[[[Link]]]")
.join(`<a href="${dateUrl.url}">${dateUrl.text}</a>`),
});
return "Booking link sent";
};
const sendBookingEmailTool = (apiKey: string, user: User, users: UserList, agentEmail: string) => {
return new DynamicStructuredTool({
description:
"Send a booking link via email. Useful for scheduling with non cal users. Be confident, suggesting a good date/time with a fallback to a link to select a date/time.",
func: async ({ message, subject, to, eventTypeSlug, slots, date }) => {
return JSON.stringify(
await sendBookingEmail({
apiKey,
user,
users,
agentEmail,
subject,
to,
message,
eventTypeSlug,
slots,
date,
})
);
},
name: "sendBookingEmail",
schema: z.object({
message: z
.string()
.describe(
"A polite and professional email with an intro and signature at the end. Specify you are the AI booking assistant of the primary user. Use [[[Slots]]] and a fallback [[[Link]]] to inject good times and 'see all times' into messages"
),
subject: z.string(),
to: z
.string()
.describe("email address to send the booking link to. Primary user is automatically CC'd"),
eventTypeSlug: z.string().describe("the slug of the event type to book"),
slots: z
.array(
z.object({
time: z.string().describe("YYYY-MM-DDTHH:mm in UTC"),
text: z.string().describe("minimum readable label. Ex. 4pm."),
})
)
.optional()
.describe("Time slots the external user can click"),
date: z
.object({
date: z.string().describe("YYYY-MM-DD"),
text: z.string().describe('"See all times" or similar'),
})
.describe(
"A booking link that allows the external user to select a date / time. Should be a fallback to time slots"
),
}),
});
};
export default sendBookingEmailTool;
-81
View File
@@ -1,81 +0,0 @@
import { DynamicStructuredTool } from "langchain/tools";
import { z } from "zod";
import { env } from "~/src/env.mjs";
import type { User, UserList } from "~/src/types/user";
import sendEmail from "~/src/utils/sendEmail";
export const sendBookingLink = async ({
user,
agentEmail,
subject,
to,
message,
eventTypeSlug,
date,
}: {
apiKey: string;
user: User;
users: UserList;
agentEmail: string;
subject: string;
to: string[];
message: string;
eventTypeSlug: string;
date: string;
}) => {
const url = `${env.FRONTEND_URL}/${user.username}/${eventTypeSlug}?date=${date}`;
await sendEmail({
subject,
to,
cc: user.email,
from: agentEmail,
text: message.split("[[[Booking Link]]]").join(url),
html: message
.split("\n")
.join("<br>")
.split("[[[Booking Link]]]")
.join(`<a href="${url}">Booking Link</a>`),
});
return "Booking link sent";
};
const sendBookingLinkTool = (apiKey: string, user: User, users: UserList, agentEmail: string) => {
return new DynamicStructuredTool({
description: "Send a booking link via email. Useful for scheduling with non cal users.",
func: async ({ message, subject, to, eventTypeSlug, date }) => {
return JSON.stringify(
await sendBookingLink({
apiKey,
user,
users,
agentEmail,
subject,
to,
message,
eventTypeSlug,
date,
})
);
},
name: "sendBookingLink",
schema: z.object({
message: z
.string()
.describe(
"Make sure to nicely format the message and introduce yourself as the primary user's booking assistant. Make sure to include a spot for the link using: [[[Booking Link]]]"
),
subject: z.string(),
to: z
.array(z.string())
.describe("array of emails to send the booking link to. Primary user is automatically CC'd"),
eventTypeSlug: z.string().describe("the slug of the event type to book"),
date: z.string().describe("the date (yyyy-mm-dd) to suggest for the booking"),
}),
});
};
export default sendBookingLinkTool;
+4 -2
View File
@@ -6,7 +6,7 @@ import createBookingIfAvailable from "../tools/createBooking";
import deleteBooking from "../tools/deleteBooking";
import getAvailability from "../tools/getAvailability";
import getBookings from "../tools/getBookings";
import sendBookingLink from "../tools/sendBookingLink";
import sendBookingEmail from "../tools/sendBookingEmail";
import updateBooking from "../tools/updateBooking";
import type { EventType } from "../types/eventType";
import type { User, UserList } from "../types/user";
@@ -35,7 +35,7 @@ const agent = async (
createBookingIfAvailable(apiKey, userId, users),
updateBooking(apiKey, userId),
deleteBooking(apiKey),
sendBookingLink(apiKey, user, users, agentEmail),
sendBookingEmail(apiKey, user, users, agentEmail),
];
const model = new ChatOpenAI({
@@ -53,6 +53,8 @@ const agent = async (
Make sure your final answers are definitive, complete and well formatted.
Sometimes, tools return errors. In this case, try to handle the error intelligently or ask the user for more information.
Tools will always handle times in UTC, but times sent to users should be formatted per that user's timezone.
In responses to users, always summarize necessary context and open the door to follow ups. For example "I have booked your chat with @username for 3pm on Wednesday, December 20th, 2023 EST. Please let me know if you need to reschedule."
If you can't find a referenced user, ask the user for their email or @username. Make sure to specify that usernames require the @username format. Users don't know other users' userIds.
The primary user's id is: ${userId}
The primary user's username is: ${user.username}
+6 -2
View File
@@ -6,8 +6,12 @@ import type { UserList } from "../types/user";
* Extracts usernames (@Example) and emails (hi@example.com) from a string
*/
export const extractUsers = async (text: string) => {
const usernames = text.match(/(?<![a-zA-Z0-9_.])@[a-zA-Z0-9_]+/g)?.map((username) => username.slice(1));
const emails = text.match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/g);
const usernames = text
.match(/(?<![a-zA-Z0-9_.])@[a-zA-Z0-9_]+/g)
?.map((username) => username.slice(1).toLowerCase());
const emails = text
.match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/g)
?.map((email) => email.toLowerCase());
const dbUsersFromUsernames = usernames
? await prisma.user.findMany({
+15
View File
@@ -0,0 +1,15 @@
import type { NextMiddleware } from "next-api-middleware";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// TODO: Add a way to add trusted api keys
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
});
await next();
};
+1 -1
View File
@@ -4,7 +4,7 @@ import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { isAdminGuard } from "~/lib/utils/isAdmin";
import { isAdminGuard } from "../utils/isAdmin";
// Used to check if the apiKey is not expired, could be extracted if reused. but not for now.
export const dateNotInPast = function (date: Date) {
+25 -18
View File
@@ -12,24 +12,29 @@ import {
HTTP_GET_OR_POST,
HTTP_GET_DELETE_PATCH,
} from "./httpMethods";
import { rateLimitApiKey } from "./rateLimitApiKey";
import { verifyApiKey } from "./verifyApiKey";
import { withPagination } from "./withPagination";
const withMiddleware = label(
{
HTTP_GET_OR_POST,
HTTP_GET_DELETE_PATCH,
HTTP_GET,
HTTP_PATCH,
HTTP_POST,
HTTP_DELETE,
addRequestId,
verifyApiKey,
customPrismaClient,
extendRequest,
pagination: withPagination,
captureErrors,
},
const middleware = {
HTTP_GET_OR_POST,
HTTP_GET_DELETE_PATCH,
HTTP_GET,
HTTP_PATCH,
HTTP_POST,
HTTP_DELETE,
addRequestId,
verifyApiKey,
rateLimitApiKey,
customPrismaClient,
extendRequest,
pagination: withPagination,
captureErrors,
};
type Middleware = keyof typeof middleware;
const middlewareOrder =
// The order here, determines the order of execution
[
"extendRequest",
@@ -37,8 +42,10 @@ const withMiddleware = label(
// - Put customPrismaClient before verifyApiKey always.
"customPrismaClient",
"verifyApiKey",
"rateLimitApiKey",
"addRequestId",
] // <-- Provide a list of middleware to call automatically
);
] as Middleware[]; // <-- Provide a list of middleware to call automatically
export { withMiddleware };
const withMiddleware = label(middleware, middlewareOrder);
export { withMiddleware, middleware, middlewareOrder };
@@ -0,0 +1,14 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
export function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
/** Guard: Only admins can query other users */
if (!isAdmin) {
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
}
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
return Array.isArray(userIdOrUserIds) ? userIdOrUserIds : [userIdOrUserIds];
}
+4
View File
@@ -0,0 +1,4 @@
export function isValidBase64Image(input: string): boolean {
const regex = /^data:image\/[^;]+;base64,(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
return regex.test(input);
}
+2
View File
@@ -26,6 +26,7 @@ const schemaAvailabilityCreateParams = z
startTime: z.date().or(z.string()),
endTime: z.date().or(z.string()),
days: z.array(z.number()).optional(),
date: z.date().or(z.string()).optional(),
})
.strict();
@@ -34,6 +35,7 @@ const schemaAvailabilityEditParams = z
startTime: z.date().or(z.string()).optional(),
endTime: z.date().or(z.string()).optional(),
days: z.array(z.number()).optional(),
date: z.date().or(z.string()).optional(),
})
.strict();
+1 -1
View File
@@ -58,7 +58,7 @@ export const schemaBookingReadPublic = Booking.extend({
})
)
.optional(),
responses: z.record(z.any()),
responses: z.record(z.any()).nullable(),
}).pick({
id: true,
userId: true,
@@ -14,9 +14,9 @@ const schemaDestinationCalendarCreateParams = z
.object({
integration: z.string(),
externalId: z.string(),
eventTypeId: z.number(),
bookingId: z.number(),
userId: z.number(),
eventTypeId: z.number().optional(),
bookingId: z.number().optional(),
userId: z.number().optional(),
})
.strict();
+16 -1
View File
@@ -24,6 +24,11 @@ const hostSchema = _HostModel.pick({
userId: true,
});
export const childrenSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
});
export const schemaEventTypeBaseBodyParams = EventType.pick({
title: true,
description: true,
@@ -45,6 +50,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
disableGuests: true,
hideCalendarNotes: true,
minimumBookingNotice: true,
parentId: true,
beforeEventBuffer: true,
afterEventBuffer: true,
teamId: true,
@@ -56,7 +62,12 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
bookingLimits: true,
durationLimits: true,
})
.merge(z.object({ hosts: z.array(hostSchema).optional().default([]) }))
.merge(
z.object({
children: z.array(childrenSchema).optional().default([]),
hosts: z.array(hostSchema).optional().default([]),
})
)
.partial()
.strict();
@@ -73,6 +84,7 @@ const schemaEventTypeCreateParams = z
seatsShowAvailabilityCount: z.boolean().optional(),
bookingFields: eventTypeBookingFields.optional(),
scheduleId: z.number().optional(),
parentId: z.number().optional(),
})
.strict();
@@ -125,6 +137,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
price: true,
currency: true,
slotInterval: true,
parentId: true,
successRedirectUrl: true,
description: true,
locations: true,
@@ -137,6 +150,8 @@ export const schemaEventTypeReadPublic = EventType.pick({
durationLimits: true,
}).merge(
z.object({
children: z.array(childrenSchema).optional().default([]),
hosts: z.array(hostSchema).optional().default([]),
locations: z
.array(
z.object({
+10 -1
View File
@@ -21,7 +21,16 @@ export const schemaSchedulePublic = z
.merge(
z.object({
availability: z
.array(Availability.pick({ id: true, eventTypeId: true, days: true, startTime: true, endTime: true }))
.array(
Availability.pick({
id: true,
eventTypeId: true,
date: true,
days: true,
startTime: true,
endTime: true,
})
)
.transform((v) =>
v.map((item) => ({
...item,
+7
View File
@@ -4,6 +4,7 @@ import { checkUsername } from "@calcom/lib/server/checkUsername";
import { _UserModel as User } from "@calcom/prisma/zod";
import { iso8601 } from "@calcom/prisma/zod-utils";
import { isValidBase64Image } from "~/lib/utils/isValidBase64Image";
import { timeZone } from "~/lib/validations/shared/timeZone";
// @note: These are the ONLY values allowed as weekStart. So user don't introduce bad data.
@@ -75,6 +76,7 @@ export const schemaUserBaseBodyParams = User.pick({
theme: true,
defaultScheduleId: true,
locale: true,
hideBranding: true,
timeFormat: true,
brandColor: true,
darkBrandColor: true,
@@ -95,6 +97,7 @@ const schemaUserEditParams = z.object({
weekStart: z.nativeEnum(weekdays).optional(),
brandColor: z.string().min(4).max(9).regex(/^#/).optional(),
darkBrandColor: z.string().min(4).max(9).regex(/^#/).optional(),
hideBranding: z.boolean().optional(),
timeZone: timeZone.optional(),
theme: z.nativeEnum(theme).optional().nullable(),
timeFormat: z.nativeEnum(timeFormat).optional(),
@@ -104,6 +107,7 @@ const schemaUserEditParams = z.object({
.optional()
.nullable(),
locale: z.nativeEnum(locales).optional().nullable(),
avatar: z.string().refine(isValidBase64Image).optional(),
});
// @note: These are the values that are editable via PATCH method on the user Model,
@@ -115,6 +119,7 @@ const schemaUserCreateParams = z.object({
weekStart: z.nativeEnum(weekdays).optional(),
brandColor: z.string().min(4).max(9).regex(/^#/).optional(),
darkBrandColor: z.string().min(4).max(9).regex(/^#/).optional(),
hideBranding: z.boolean().optional(),
timeZone: timeZone.optional(),
theme: z.nativeEnum(theme).optional().nullable(),
timeFormat: z.nativeEnum(timeFormat).optional(),
@@ -125,6 +130,7 @@ const schemaUserCreateParams = z.object({
.nullable(),
locale: z.nativeEnum(locales).optional(),
createdDate: iso8601.optional(),
avatar: z.string().refine(isValidBase64Image).optional(),
});
// @note: These are the values that are editable via PATCH method on the user Model,
@@ -157,6 +163,7 @@ export const schemaUserReadPublic = User.pick({
defaultScheduleId: true,
locale: true,
timeFormat: true,
hideBranding: true,
brandColor: true,
darkBrandColor: true,
allowDynamicBooking: true,
+2
View File
@@ -20,6 +20,7 @@ export const schemaWebhookCreateParams = z
payloadTemplate: z.string().optional().nullable(),
eventTypeId: z.number().optional(),
userId: z.number().optional(),
secret: z.string().optional().nullable(),
// API shouldn't mess with Apps webhooks yet (ie. Zapier)
// appId: z.string().optional().nullable(),
})
@@ -31,6 +32,7 @@ export const schemaWebhookEditBodyParams = schemaWebhookBaseBodyParams
.merge(
z.object({
eventTriggers: z.enum(WEBHOOK_TRIGGER_EVENTS).array().optional(),
secret: z.string().optional().nullable(),
})
)
.partial()
+2 -1
View File
@@ -13,6 +13,7 @@
"lint": "eslint . --ignore-path .gitignore",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
"start": "PORT=3002 next start",
"docker-start-api": "PORT=80 next start",
"type-check": "tsc --pretty --noEmit"
},
"devDependencies": {
@@ -29,7 +30,7 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/trpc": "*",
"@sentry/nextjs": "^7.20.0",
"@sentry/nextjs": "^7.73.0",
"bcryptjs": "^2.4.3",
"memory-cache": "^0.2.0",
"next": "^13.4.6",
@@ -56,10 +56,6 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* <td>The provided id didn't correspond to any existing booking.</td>
* </tr>
* <tr>
* <td>Cannot cancel past events</td>
* <td>The provided id matched an existing booking with a past startDate.</td>
* </tr>
* <tr>
* <td>User not found</td>
* <td>The userId did not matched an existing user.</td>
* </tr>
+2 -2
View File
@@ -205,8 +205,8 @@ import { defaultResponder } from "@calcom/lib/server";
async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
if (isAdmin) req.userId = req.body.userId || userId;
const booking = await handleNewBooking(req);
return booking;
return await handleNewBooking(req);
}
export default defaultResponder(handler);
@@ -1,240 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
import type { DestinationCalendarResponse } from "~/lib/types";
import {
schemaDestinationCalendarEditBodyParams,
schemaDestinationCalendarReadPublic,
} from "~/lib/validations/destination-calendar";
import {
schemaQueryIdParseInt,
withValidQueryIdTransformParseInt,
} from "~/lib/validations/shared/queryIdTransformParseInt";
export async function destionationCalendarById(
{ method, query, body, userId, prisma }: NextApiRequest,
res: NextApiResponse<DestinationCalendarResponse>
) {
const safeQuery = schemaQueryIdParseInt.safeParse(query);
const safeBody = schemaDestinationCalendarEditBodyParams.safeParse(body);
if (!safeQuery.success) {
res.status(400).json({ message: "Your query was invalid" });
return;
}
const data = await prisma.destinationCalendar.findMany({ where: { userId } });
const userDestinationCalendars = data.map((destinationCalendar) => destinationCalendar.id);
// FIXME: Should we also check ownership of bokingId and eventTypeId to avoid users cross-pollinating other users calendars.
// On a related note, moving from sequential integer IDs to UUIDs would be a good idea. and maybe help avoid having this problem.
if (userDestinationCalendars.includes(safeQuery.data.id)) res.status(401).json({ message: "Unauthorized" });
else {
switch (method) {
/**
* @swagger
* /destination-calendars/{id}:
* get:
* summary: Find a destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to get
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: DestinationCalendar was not found
* patch:
* summary: Edit an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to edit
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* requestBody:
* description: Create a new booking related to one of your event-types
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* integration:
* type: string
* description: 'The integration'
* externalId:
* type: string
* description: 'The external ID of the integration'
* eventTypeId:
* type: integer
* description: 'The ID of the eventType it is associated with'
* bookingId:
* type: integer
* description: 'The booking ID it is associated with'
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destinationCalendar edited successfuly
* 400:
* description: Bad request. DestinationCalendar body is invalid.
* 401:
* description: Authorization information is missing or invalid.
* delete:
* summary: Remove an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to delete
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destinationCalendar removed successfuly
* 400:
* description: Bad request. DestinationCalendar id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
case "GET":
await prisma.destinationCalendar
.findUnique({ where: { id: safeQuery.data.id } })
.then((data) => schemaDestinationCalendarReadPublic.parse(data))
.then((destination_calendar) => res.status(200).json({ destination_calendar }))
.catch((error: Error) =>
res.status(404).json({
message: `DestinationCalendar with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
/**
* @swagger
* /destination-calendars/{id}:
* patch:
* summary: Edit an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to edit
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destinationCalendar edited successfuly
* 400:
* description: Bad request. DestinationCalendar body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
case "PATCH":
if (!safeBody.success) {
{
res.status(400).json({ message: "Invalid request body" });
return;
}
}
await prisma.destinationCalendar
.update({ where: { id: safeQuery.data.id }, data: safeBody.data })
.then((data) => schemaDestinationCalendarReadPublic.parse(data))
.then((destination_calendar) => res.status(200).json({ destination_calendar }))
.catch((error: Error) =>
res.status(404).json({
message: `DestinationCalendar with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
/**
* @swagger
* /destination-calendars/{id}:
* delete:
* summary: Remove an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to delete
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destinationCalendar removed successfuly
* 400:
* description: Bad request. DestinationCalendar id is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
case "DELETE":
await prisma.destinationCalendar
.delete({
where: { id: safeQuery.data.id },
})
.then(() =>
res.status(200).json({
message: `DestinationCalendar with id: ${safeQuery.data.id} deleted`,
})
)
.catch((error: Error) =>
res.status(404).json({
message: `DestinationCalendar with id: ${safeQuery.data.id} not found`,
error,
})
);
break;
default:
res.status(405).json({ message: "Method not allowed" });
break;
}
}
}
export default withMiddleware("HTTP_GET_DELETE_PATCH")(
withValidQueryIdTransformParseInt(destionationCalendarById)
);
@@ -0,0 +1,32 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
if (isAdmin) return;
const userEventTypes = await prisma.eventType.findMany({
where: { userId },
select: { id: true },
});
const userEventTypeIds = userEventTypes.map((eventType) => eventType.id);
const destinationCalendar = await prisma.destinationCalendar.findFirst({
where: {
AND: [
{ id },
{
OR: [{ userId }, { eventTypeId: { in: userEventTypeIds } }],
},
],
},
});
if (!destinationCalendar)
throw new HttpError({ statusCode: 404, message: "Destination calendar not found" });
}
export default authMiddleware;
@@ -0,0 +1,42 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /destination-calendars/{id}:
* delete:
* summary: Remove an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to delete
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK, destinationCalendar removed successfully
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: Destination calendar not found
*/
export async function deleteHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
await prisma.destinationCalendar.delete({ where: { id } });
return { message: `OK, Destination Calendar removed successfully` };
}
export default defaultResponder(deleteHandler);
@@ -0,0 +1,47 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { schemaDestinationCalendarReadPublic } from "~/lib/validations/destination-calendar";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /destination-calendars/{id}:
* get:
* summary: Find a destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to get
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: Destination calendar not found
*/
export async function getHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const destinationCalendar = await prisma.destinationCalendar.findUnique({
where: { id },
});
return { destinationCalendar: schemaDestinationCalendarReadPublic.parse({ ...destinationCalendar }) };
}
export default defaultResponder(getHandler);
@@ -0,0 +1,312 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import type { z } from "zod";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import type { PrismaClient } from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import {
schemaDestinationCalendarEditBodyParams,
schemaDestinationCalendarReadPublic,
} from "~/lib/validations/destination-calendar";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
/**
* @swagger
* /destination-calendars/{id}:
* patch:
* summary: Edit an existing destination calendar
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: ID of the destination calendar to edit
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* requestBody:
* description: Create a new booking related to one of your event-types
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* integration:
* type: string
* description: 'The integration'
* externalId:
* type: string
* description: 'The external ID of the integration'
* eventTypeId:
* type: integer
* description: 'The ID of the eventType it is associated with'
* bookingId:
* type: integer
* description: 'The booking ID it is associated with'
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: Destination calendar not found
*/
type DestinationCalendarType = {
userId?: number | null;
eventTypeId?: number | null;
credentialId: number | null;
};
type UserCredentialType = {
id: number;
appId: string | null;
type: string;
userId: number | null;
user: {
email: string;
} | null;
teamId: number | null;
key: Prisma.JsonValue;
invalid: boolean | null;
};
export async function patchHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma, query, body } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
const assignedUserId = isAdmin ? parsedBody.userId || userId : userId;
validateIntegrationInput(parsedBody);
const destinationCalendarObject: DestinationCalendarType = await getDestinationCalendar(id, prisma);
await validateRequestAndOwnership({ destinationCalendarObject, parsedBody, assignedUserId, prisma });
const userCredentials = await getUserCredentials({
credentialId: destinationCalendarObject.credentialId,
userId: assignedUserId,
prisma,
});
const credentialId = await verifyCredentialsAndGetId({
parsedBody,
userCredentials,
currentCredentialId: destinationCalendarObject.credentialId,
});
// If the user has passed eventTypeId, we need to remove userId from the update data to make sure we don't link it to user as well
if (parsedBody.eventTypeId) parsedBody.userId = undefined;
const destinationCalendar = await prisma.destinationCalendar.update({
where: { id },
data: { ...parsedBody, credentialId },
});
return { destinationCalendar: schemaDestinationCalendarReadPublic.parse(destinationCalendar) };
}
/**
* Retrieves user credentials associated with a given credential ID and user ID and validates if the credentials belong to this user
*
* @param credentialId - The ID of the credential to fetch. If not provided, an error is thrown.
* @param userId - The user ID against which the credentials need to be verified.
* @param prisma - An instance of PrismaClient for database operations.
*
* @returns - An array containing the matching user credentials.
*
* @throws HttpError - If `credentialId` is not provided or no associated credentials are found in the database.
*/
async function getUserCredentials({
credentialId,
userId,
prisma,
}: {
credentialId: number | null;
userId: number;
prisma: PrismaClient;
}) {
if (!credentialId) {
throw new HttpError({
statusCode: 404,
message: `Destination calendar missing credential id`,
});
}
const userCredentials = await prisma.credential.findMany({
where: { id: credentialId, userId },
select: credentialForCalendarServiceSelect,
});
if (!userCredentials || userCredentials.length === 0) {
throw new HttpError({
statusCode: 400,
message: `Bad request, no associated credentials found`,
});
}
return userCredentials;
}
/**
* Verifies the provided credentials and retrieves the associated credential ID.
*
* This function checks if the `integration` and `externalId` properties from the parsed body are present.
* If both properties exist, it fetches the connected calendar credentials using the provided user credentials
* and checks for a matching external ID and integration from the list of connected calendars.
*
* If a match is found, it updates the `credentialId` with the one from the connected calendar.
* Otherwise, it throws an HTTP error with a 400 status indicating an invalid credential ID.
*
* If the parsed body does not contain the necessary properties, the function
* returns the `credentialId` from the destination calendar object.
*
* @param parsedBody - The parsed body from the incoming request, validated against a predefined schema.
* Checked if it contain properties like `integration` and `externalId`.
* @param userCredentials - An array of user credentials used to fetch the connected calendar credentials.
* @param destinationCalendarObject - An object representing the destination calendar. Primarily used
* to fetch the default `credentialId`.
*
* @returns - The verified `credentialId` either from the matched connected calendar in case of updating the destination calendar,
* or the provided destination calendar object in other cases.
*
* @throws HttpError - If no matching connected calendar is found for the given `integration` and `externalId`.
*/
async function verifyCredentialsAndGetId({
parsedBody,
userCredentials,
currentCredentialId,
}: {
parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>;
userCredentials: UserCredentialType[];
currentCredentialId: number | null;
}) {
if (parsedBody.integration && parsedBody.externalId) {
const calendarCredentials = getCalendarCredentials(userCredentials);
const { connectedCalendars } = await getConnectedCalendars(
calendarCredentials,
[],
parsedBody.externalId
);
const eligibleCalendars = connectedCalendars[0]?.calendars?.filter((calendar) => !calendar.readOnly);
const calendar = eligibleCalendars?.find(
(c) => c.externalId === parsedBody.externalId && c.integration === parsedBody.integration
);
if (!calendar?.credentialId)
throw new HttpError({
statusCode: 400,
message: "Bad request, credential id invalid",
});
return calendar?.credentialId;
}
return currentCredentialId;
}
/**
* Validates the request for updating a destination calendar.
*
* This function checks the validity of the provided eventTypeId against the existing destination calendar object
* in the sense that if the destination calendar is not linked to an event type, the eventTypeId can not be provided.
*
* It also ensures that the eventTypeId, if provided, belongs to the assigned user.
*
* @param destinationCalendarObject - An object representing the destination calendar.
* @param parsedBody - The parsed body from the incoming request, validated against a predefined schema.
* @param assignedUserId - The user ID assigned for the operation, which might be an admin or a regular user.
* @param prisma - An instance of PrismaClient for database operations.
*
* @throws HttpError - If the validation fails or inconsistencies are detected in the request data.
*/
async function validateRequestAndOwnership({
destinationCalendarObject,
parsedBody,
assignedUserId,
prisma,
}: {
destinationCalendarObject: DestinationCalendarType;
parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>;
assignedUserId: number;
prisma: PrismaClient;
}) {
if (parsedBody.eventTypeId) {
if (!destinationCalendarObject.eventTypeId) {
throw new HttpError({
statusCode: 400,
message: `The provided destination calendar can not be linked to an event type`,
});
}
const userEventType = await prisma.eventType.findFirst({
where: { id: parsedBody.eventTypeId },
select: { userId: true },
});
if (!userEventType || userEventType.userId !== assignedUserId) {
throw new HttpError({
statusCode: 404,
message: `Event type with ID ${parsedBody.eventTypeId} not found`,
});
}
}
if (!parsedBody.eventTypeId) {
if (destinationCalendarObject.eventTypeId) {
throw new HttpError({
statusCode: 400,
message: `The provided destination calendar can only be linked to an event type`,
});
}
if (destinationCalendarObject.userId !== assignedUserId) {
throw new HttpError({
statusCode: 403,
message: `Forbidden`,
});
}
}
}
/**
* Fetches the destination calendar based on the provided ID as the path parameter, specifically `credentialId` and `eventTypeId`.
*
* If no matching destination calendar is found for the provided ID, an HTTP error with a 404 status
* indicating that the desired destination calendar was not found is thrown.
*
* @param id - The ID of the destination calendar to be retrieved.
* @param prisma - An instance of PrismaClient for database operations.
*
* @returns - An object containing details of the matching destination calendar, specifically `credentialId` and `eventTypeId`.
*
* @throws HttpError - If no destination calendar matches the provided ID.
*/
async function getDestinationCalendar(id: number, prisma: PrismaClient) {
const destinationCalendarObject = await prisma.destinationCalendar.findFirst({
where: {
id,
},
select: { userId: true, eventTypeId: true, credentialId: true },
});
if (!destinationCalendarObject) {
throw new HttpError({
statusCode: 404,
message: `Destination calendar with ID ${id} not found`,
});
}
return destinationCalendarObject;
}
function validateIntegrationInput(parsedBody: z.infer<typeof schemaDestinationCalendarEditBodyParams>) {
if (parsedBody.integration && !parsedBody.externalId) {
throw new HttpError({ statusCode: 400, message: "External Id is required with integration value" });
}
if (!parsedBody.integration && parsedBody.externalId) {
throw new HttpError({ statusCode: 400, message: "Integration value is required with external ID" });
}
}
export default defaultResponder(patchHandler);
@@ -0,0 +1,18 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
import authMiddleware from "./_auth-middleware";
export default withMiddleware()(
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
await authMiddleware(req);
return defaultHandler({
GET: import("./_get"),
PATCH: import("./_patch"),
DELETE: import("./_delete"),
})(req, res);
})
);
@@ -0,0 +1,58 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { extractUserIdsFromQuery } from "~/lib/utils/extractUserIdsFromQuery";
import { schemaDestinationCalendarReadPublic } from "~/lib/validations/destination-calendar";
/**
* @swagger
* /destination-calendars:
* get:
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* summary: Find all destination calendars
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No destination calendars were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, prisma } = req;
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
const userEventTypes = await prisma.eventType.findMany({
where: { userId: { in: userIds } },
select: { id: true },
});
const userEventTypeIds = userEventTypes.map((eventType) => eventType.id);
const allDestinationCalendars = await prisma.destinationCalendar.findMany({
where: {
OR: [{ userId: { in: userIds } }, { eventTypeId: { in: userEventTypeIds } }],
},
});
if (allDestinationCalendars.length === 0)
new HttpError({ statusCode: 404, message: "No destination calendars were found" });
return {
destinationCalendars: allDestinationCalendars.map((destinationCalendar) =>
schemaDestinationCalendarReadPublic.parse(destinationCalendar)
),
};
}
export default defaultResponder(getHandler);
@@ -0,0 +1,141 @@
import type { NextApiRequest } from "next";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import {
schemaDestinationCalendarReadPublic,
schemaDestinationCalendarCreateBodyParams,
} from "~/lib/validations/destination-calendar";
/**
* @swagger
* /destination-calendars:
* post:
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* summary: Creates a new destination calendar
* requestBody:
* description: Create a new destination calendar for your events
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - integration
* - externalId
* - credentialId
* properties:
* integration:
* type: string
* description: 'The integration'
* externalId:
* type: string
* description: 'The external ID of the integration'
* eventTypeId:
* type: integer
* description: 'The ID of the eventType it is associated with'
* bookingId:
* type: integer
* description: 'The booking ID it is associated with'
* userId:
* type: integer
* description: 'The user it is associated with'
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destination calendar created
* 400:
* description: Bad request. DestinationCalendar body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma, body } = req;
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
await checkPermissions(req, userId);
const assignedUserId = isAdmin && parsedBody.userId ? parsedBody.userId : userId;
/* Check if credentialId data matches the ownership and integration passed in */
const userCredentials = await prisma.credential.findMany({
where: {
type: parsedBody.integration,
userId: assignedUserId,
},
select: credentialForCalendarServiceSelect,
});
if (userCredentials.length === 0)
throw new HttpError({
statusCode: 400,
message: "Bad request, credential id invalid",
});
const calendarCredentials = getCalendarCredentials(userCredentials);
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, [], parsedBody.externalId);
const eligibleCalendars = connectedCalendars[0]?.calendars?.filter((calendar) => !calendar.readOnly);
const calendar = eligibleCalendars?.find(
(c) => c.externalId === parsedBody.externalId && c.integration === parsedBody.integration
);
if (!calendar?.credentialId)
throw new HttpError({
statusCode: 400,
message: "Bad request, credential id invalid",
});
const credentialId = calendar.credentialId;
if (parsedBody.eventTypeId) {
const eventType = await prisma.eventType.findFirst({
where: { id: parsedBody.eventTypeId, userId: parsedBody.userId },
});
if (!eventType)
throw new HttpError({
statusCode: 400,
message: "Bad request, eventTypeId invalid",
});
parsedBody.userId = undefined;
}
const destination_calendar = await prisma.destinationCalendar.create({
data: { ...parsedBody, credentialId },
});
return {
destinationCalendar: schemaDestinationCalendarReadPublic.parse(destination_calendar),
message: "Destination calendar created successfully",
};
}
async function checkPermissions(req: NextApiRequest, userId: number) {
const { isAdmin } = req;
const body = schemaDestinationCalendarCreateBodyParams.parse(req.body);
/* Non-admin users can only create destination calendars for themselves */
if (!isAdmin && body.userId)
throw new HttpError({
statusCode: 401,
message: "ADMIN required for `userId`",
});
/* Admin users are required to pass in a userId */
if (isAdmin && !body.userId) throw new HttpError({ statusCode: 400, message: "`userId` required" });
/* User should only be able to create for their own destination calendars*/
if (!isAdmin && body.eventTypeId) {
const ownsEventType = await req.prisma.eventType.findFirst({ where: { id: body.eventTypeId, userId } });
if (!ownsEventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
}
// TODO:: Add support for team event types with validation
}
export default defaultResponder(postHandler);
@@ -1,114 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { defaultHandler } from "@calcom/lib/server";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
import type { DestinationCalendarResponse, DestinationCalendarsResponse } from "~/lib/types";
import {
schemaDestinationCalendarCreateBodyParams,
schemaDestinationCalendarReadPublic,
} from "~/lib/validations/destination-calendar";
async function createOrlistAllDestinationCalendars(
{ method, body, userId, prisma }: NextApiRequest,
res: NextApiResponse<DestinationCalendarsResponse | DestinationCalendarResponse>
) {
if (method === "GET") {
/**
* @swagger
* /destination-calendars:
* get:
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* summary: Find all destination calendars
* tags:
* - destination-calendars
* responses:
* 200:
* description: OK
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: No destination calendars were found
*/
const data = await prisma.destinationCalendar.findMany({ where: { userId } });
const destination_calendars = data.map((destinationCalendar) =>
schemaDestinationCalendarReadPublic.parse(destinationCalendar)
);
if (data) res.status(200).json({ destination_calendars });
else
(error: Error) =>
res.status(404).json({
message: "No DestinationCalendars were found",
error,
});
} else if (method === "POST") {
/**
* @swagger
* /destination-calendars:
* post:
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* summary: Creates a new destination calendar
* requestBody:
* description: Create a new destination calendar for your events
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - integration
* - externalId
* properties:
* integration:
* type: string
* description: 'The integration'
* externalId:
* type: string
* description: 'The external ID of the integration'
* eventTypeId:
* type: integer
* description: 'The ID of the eventType it is associated with'
* bookingId:
* type: integer
* description: 'The booking ID it is associated with'
* tags:
* - destination-calendars
* responses:
* 201:
* description: OK, destination calendar created
* 400:
* description: Bad request. DestinationCalendar body is invalid.
* 401:
* description: Authorization information is missing or invalid.
*/
const safe = schemaDestinationCalendarCreateBodyParams.safeParse(body);
if (!safe.success) {
res.status(400).json({ message: "Invalid request body" });
return;
}
const data = await prisma.destinationCalendar.create({ data: { ...safe.data, userId } });
const destination_calendar = schemaDestinationCalendarReadPublic.parse(data);
if (destination_calendar)
res.status(201).json({ destination_calendar, message: "DestinationCalendar created successfully" });
else
(error: Error) =>
res.status(400).json({
message: "Could not create new destinationCalendar",
error,
});
} else res.status(405).json({ message: `Method ${method} not allowed` });
}
export default withMiddleware("HTTP_GET_OR_POST")(createOrlistAllDestinationCalendars);
export default withMiddleware()(
defaultHandler({
GET: import("./_get"),
POST: import("./_post"),
})
);
+6 -1
View File
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { MembershipRole } from "@calcom/prisma/enums";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
@@ -51,7 +52,9 @@ export async function getHandler(req: NextApiRequest) {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
});
await checkPermissions(req, eventType);
@@ -87,7 +90,9 @@ async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
if (req.isAdmin) return true;
if (eventType?.teamId) {
req.query.teamId = String(eventType.teamId);
await canAccessTeamEventOrThrow(req, "MEMBER");
await canAccessTeamEventOrThrow(req, {
in: [MembershipRole.OWNER, MembershipRole.ADMIN, MembershipRole.MEMBER],
});
}
if (eventType?.userId === req.userId) return true; // is owner.
throw new HttpError({ statusCode: 403, message: "Forbidden" });
@@ -209,6 +209,8 @@ export async function patchHandler(req: NextApiRequest) {
hosts = [],
bookingLimits,
durationLimits,
/** FIXME: Updating event-type children from API not supported for now */
children: _,
...parsedBody
} = schemaEventTypeEditBodyParams.parse(body);
+2
View File
@@ -45,7 +45,9 @@ async function getHandler(req: NextApiRequest) {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
});
// this really should return [], but backwards compatibility..
+22 -4
View File
@@ -6,7 +6,9 @@ import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import checkParentEventOwnership from "./_utils/checkParentEventOwnership";
import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission";
import checkUserMembership from "./_utils/checkUserMembership";
import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
/**
@@ -118,10 +120,13 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* schedulingType:
* type: string
* description: The type of scheduling if a Team event. Required for team events only
* enum: [ROUND_ROBIN, COLLECTIVE]
* enum: [ROUND_ROBIN, COLLECTIVE, MANAGED]
* price:
* type: integer
* description: Price of the event type booking
* parentId:
* type: integer
* description: EventTypeId of the parent managed event
* currency:
* type: string
* description: Currency acronym. Eg- usd, eur, gbp, etc.
@@ -263,6 +268,8 @@ async function postHandler(req: NextApiRequest) {
hosts = [],
bookingLimits,
durationLimits,
/** FIXME: Adding event-type children from API not supported for now */
children: _,
...parsedBody
} = schemaEventTypeCreateBodyParams.parse(body || {});
@@ -276,6 +283,11 @@ async function postHandler(req: NextApiRequest) {
await checkPermissions(req);
if (parsedBody.parentId) {
await checkParentEventOwnership(req);
await checkUserMembership(req);
}
if (isAdmin && parsedBody.userId) {
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
}
@@ -287,7 +299,7 @@ async function postHandler(req: NextApiRequest) {
data.hosts = { createMany: { data: hosts } };
}
const eventType = await prisma.eventType.create({ data });
const eventType = await prisma.eventType.create({ data, include: { hosts: true } });
return {
event_type: schemaEventTypeReadPublic.parse(eventType),
@@ -304,8 +316,14 @@ async function checkPermissions(req: NextApiRequest) {
statusCode: 401,
message: "ADMIN required for `userId`",
});
/* Admin users are required to pass in a userId */
if (isAdmin && !body.userId) throw new HttpError({ statusCode: 400, message: "`userId` required" });
if (!isAdmin && body.teamId)
throw new HttpError({
statusCode: 401,
message: "ADMIN required for `teamId`",
});
/* Admin users are required to pass in a userId or teamId */
if (isAdmin && !body.userId && !body.teamId)
throw new HttpError({ statusCode: 400, message: "`userId` or `teamId` required" });
}
export default defaultResponder(postHandler);
@@ -0,0 +1,56 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
/**
* Checks if a user, identified by the provided userId, has ownership (or admin rights) over
* the team associated with the event type identified by the parentId.
*
* @param req - The current request
*
* @throws {HttpError} If the parent event type is not found,
* if the parent event type doesn't belong to any team,
* or if the user doesn't have ownership or admin rights to the associated team.
*/
export default async function checkParentEventOwnership(req: NextApiRequest) {
const { userId, prisma, body } = req;
/** These are already parsed upstream, we can assume they're good here. */
const parentId = Number(body.parentId);
const parentEventType = await prisma.eventType.findUnique({
where: {
id: parentId,
},
select: {
teamId: true,
},
});
if (!parentEventType) {
throw new HttpError({
statusCode: 404,
message: "Parent event type not found.",
});
}
if (!parentEventType.teamId) {
throw new HttpError({
statusCode: 400,
message: "This event type is not capable of having children",
});
}
const teamMember = await prisma.membership.findFirst({
where: {
teamId: parentEventType.teamId,
userId: userId,
OR: [{ role: "OWNER" }, { role: "ADMIN" }],
},
});
if (!teamMember) {
throw new HttpError({
statusCode: 403,
message: "User is not authorized to access the team to which the parent event type belongs.",
});
}
}
@@ -0,0 +1,57 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
/**
* Checks if a user, identified by the provided userId, is a member of the team associated
* with the event type identified by the parentId.
*
* @param req - The current request
*
* @throws {HttpError} If the event type is not found,
* if the event type doesn't belong to any team,
* or if the user isn't a member of the associated team.
*/
export default async function checkUserMembership(req: NextApiRequest) {
const { prisma, body } = req;
/** These are already parsed upstream, we can assume they're good here. */
const parentId = Number(body.parentId);
const userId = Number(body.userId);
const parentEventType = await prisma.eventType.findUnique({
where: {
id: parentId,
},
select: {
teamId: true,
},
});
if (!parentEventType) {
throw new HttpError({
statusCode: 404,
message: "Event type not found.",
});
}
if (!parentEventType.teamId) {
throw new HttpError({
statusCode: 400,
message: "This event type is not capable of having children.",
});
}
const teamMember = await prisma.membership.findFirst({
where: {
teamId: parentEventType.teamId,
userId: userId,
accepted: true,
},
});
if (!teamMember) {
throw new HttpError({
statusCode: 400,
message: "User is not a team member.",
});
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from "next";
export default async function CalcomApi(_: NextApiRequest, res: NextApiResponse) {
res.status(201).json({ message: "Welcome to Cal.com API - docs are at https://developer.cal.com/api" });
res.status(200).json({ message: "Welcome to Cal.com API - docs are at https://developer.cal.com/api" });
}
+4 -5
View File
@@ -3,18 +3,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
import { getScheduleSchema } from "@calcom/trpc/server/routers/viewer/slots/types";
import { getAvailableSlots } from "@calcom/trpc/server/routers/viewer/slots/util";
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
async function handler(req: NextApiRequest, res: NextApiResponse) {
/** @see https://trpc.io/docs/server-side-calls */
const ctx = await createContext({ req, res });
const caller = viewerRouter.createCaller(ctx);
try {
const input = getScheduleSchema.parse(req.query);
return await getAvailableSlots({ ctx: await createContext({ req, res }), input });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await caller.slots.getSchedule(req.query as any /* Let tRPC handle this */);
} catch (cause) {
if (cause instanceof TRPCError) {
const statusCode = getHTTPStatusCodeFromError(cause);
@@ -22,7 +22,11 @@ export async function checkPermissions(
role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER
) {
const { userId, prisma, isAdmin } = req;
const { teamId } = schemaQueryTeamId.parse(req.query);
const { teamId } = schemaQueryTeamId.parse({
teamId: req.query.teamId,
version: req.query.version,
apiKey: req.query.apiKey,
});
const args: Prisma.TeamFindFirstArgs = { where: { id: teamId } };
/** If not ADMIN then we check if the actual user belongs to team and matches the required role */
if (!isAdmin) args.where = { ...args.where, members: { some: { userId, role } } };
@@ -56,6 +56,14 @@ async function getHandler(req: NextApiRequest) {
members: { some: { userId } },
},
},
include: {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
};
const data = await prisma.eventType.findMany(args);
+11 -2
View File
@@ -1,5 +1,6 @@
import type { NextApiRequest } from "next";
import { deleteUser } from "@calcom/features/users/lib/userDeletionService";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
@@ -41,10 +42,18 @@ export async function deleteHandler(req: NextApiRequest) {
// Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user
if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" });
const user = await prisma.user.findUnique({ where: { id: query.userId } });
const user = await prisma.user.findUnique({
where: { id: query.userId },
select: {
id: true,
email: true,
metadata: true,
},
});
if (!user) throw new HttpError({ statusCode: 404, message: "User not found" });
await prisma.user.delete({ where: { id: user.id } });
await deleteUser(user);
return { message: `User with id: ${user.id} deleted successfully` };
}
+11 -4
View File
@@ -53,6 +53,9 @@ import { schemaUserEditBodyParams, schemaUserReadPublic } from "~/lib/validation
* timeZone:
* description: The user's time zone
* type: string
* hideBranding:
* description: Remove branding from the user's calendar page
* type: boolean
* theme:
* description: Default theme for the user. Acceptable values are one of [DARK, LIGHT]
* type: string
@@ -62,6 +65,9 @@ import { schemaUserEditBodyParams, schemaUserReadPublic } from "~/lib/validation
* locale:
* description: The user's locale. Acceptable values are one of [EN, FR, IT, RU, ES, DE, PT, RO, NL, PT_BR, ES_419, KO, JA, PL, AR, IW, ZH_CH, ZH_TW, CS, SR, SV, VI]
* type: string
* avatar:
* description: The user's avatar, in base64 format
* type: string
* examples:
* user:
* summary: An example of USER
@@ -79,7 +85,7 @@ import { schemaUserEditBodyParams, schemaUserReadPublic } from "~/lib/validation
* - users
* responses:
* 200:
* description: OK, user edited successfuly
* description: OK, user edited successfully
* 400:
* description: Bad request. User body is invalid.
* 401:
@@ -94,9 +100,10 @@ export async function patchHandler(req: NextApiRequest) {
if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 403, message: "Forbidden" });
const body = await schemaUserEditBodyParams.parseAsync(req.body);
// disable role changes unless admin.
if (!isAdmin && body.role) {
body.role = undefined;
// disable role or branding changes unless admin.
if (!isAdmin) {
if (body.role) body.role = undefined;
if (body.hideBranding) body.hideBranding = undefined;
}
const userSchedules = await prisma.schedule.findMany({
+6
View File
@@ -42,6 +42,9 @@ import { schemaUserCreateBodyParams } from "~/lib/validations/user";
* darkBrandColor:
* description: The new user's brand color for dark mode
* type: string
* hideBranding:
* description: Remove branding from the user's calendar page
* type: boolean
* weekStart:
* description: Start of the week. Acceptable values are one of [SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]
* type: string
@@ -57,6 +60,9 @@ import { schemaUserCreateBodyParams } from "~/lib/validations/user";
* locale:
* description: The new user's locale. Acceptable values are one of [EN, FR, IT, RU, ES, DE, PT, RO, NL, PT_BR, ES_419, KO, JA, PL, AR, IW, ZH_CH, ZH_TW, CS, SR, SV, VI]
* type: string
* avatar:
* description: The user's avatar, in base64 format
* type: string
* examples:
* user:
* summary: An example of USER
@@ -51,6 +51,9 @@ import { schemaWebhookEditBodyParams, schemaWebhookReadPublic } from "~/lib/vali
* eventTypeId:
* type: number
* description: The event type ID if this webhook should be associated with only that event type
* secret:
* type: string
* description: The secret to verify the authenticity of the received payload
* tags:
* - webhooks
* externalDocs:
+3
View File
@@ -49,6 +49,9 @@ import { schemaWebhookCreateBodyParams, schemaWebhookReadPublic } from "~/lib/va
* eventTypeId:
* type: number
* description: The event type ID if this webhook should be associated with only that event type
* secret:
* type: string
* description: The secret to verify the authenticity of the received payload
* tags:
* - webhooks
* externalDocs:
+5 -3
View File
@@ -1,3 +1,4 @@
// TODO: Fix tests (These test were never running due to the vitest workspace config)
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
import type { Request, Response } from "express";
@@ -7,6 +8,7 @@ import { describe, expect, test, vi } from "vitest";
import dayjs from "@calcom/dayjs";
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
import prisma from "@calcom/prisma";
@@ -21,7 +23,7 @@ vi.mock("@calcom/lib/server/i18n", () => {
};
});
describe("POST /api/bookings", () => {
describe.skipIf(true)("POST /api/bookings", () => {
describe("Errors", () => {
test("Missing required data", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
@@ -31,7 +33,7 @@ describe("POST /api/bookings", () => {
await handler(req, res);
expect(res._getStatusCode()).toBe(400);
expect(res.statusCode).toBe(400);
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
message:
@@ -147,7 +149,7 @@ describe("POST /api/bookings", () => {
expect(res._getStatusCode()).toBe(500);
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
message: "No available users found.",
message: ErrorCode.NoAvailableUsersFound,
})
);
});
@@ -0,0 +1,36 @@
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, vi, it, expect, afterEach } from "vitest";
import { addRequestId } from "../../../lib/helpers/addRequestid";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
afterEach(() => {
vi.resetAllMocks();
});
describe("Adds a request ID", () => {
it("Should attach a request ID to the request", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const middleware = {
fn: addRequestId,
};
const serverNext = vi.fn((next: void) => Promise.resolve(next));
const middlewareSpy = vi.spyOn(middleware, "fn");
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(200);
expect(res.getHeader("Calcom-Response-ID")).toBeDefined();
});
});
@@ -0,0 +1,53 @@
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, vi, it, expect, afterEach } from "vitest";
import { httpMethod } from "../../../lib/helpers/httpMethods";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
afterEach(() => {
vi.resetAllMocks();
});
describe("HTTP Methods function only allows the correct HTTP Methods", () => {
it("Should allow the passed in Method", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const middleware = {
fn: httpMethod("POST"),
};
const serverNext = vi.fn((next: void) => Promise.resolve(next));
const middlewareSpy = vi.spyOn(middleware, "fn");
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(200);
});
it("Should allow the passed in Method", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const middleware = {
fn: httpMethod("GET"),
};
const serverNext = vi.fn((next: void) => Promise.resolve(next));
const middlewareSpy = vi.spyOn(middleware, "fn");
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(405);
});
});
@@ -0,0 +1,76 @@
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, vi, it, expect, afterEach } from "vitest";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import { isAdminGuard } from "~/lib/utils/isAdmin";
import { verifyApiKey } from "../../../lib/helpers/verifyApiKey";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
afterEach(() => {
vi.resetAllMocks();
});
vi.mock("@calcom/features/ee/common/server/checkLicense", () => {
return {
default: vi.fn(),
};
});
vi.mock("~/lib/utils/isAdmin", () => {
return {
isAdminGuard: vi.fn(),
};
});
describe("Verify API key", () => {
it("It should throw an error if the api key is not valid", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const middleware = {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(false);
vi.mocked(isAdminGuard).mockResolvedValue(false);
const serverNext = vi.fn((next: void) => Promise.resolve(next));
const middlewareSpy = vi.spyOn(middleware, "fn");
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
it("It should thow an error if no api key is provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const middleware = {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue(false);
const serverNext = vi.fn((next: void) => Promise.resolve(next));
const middlewareSpy = vi.spyOn(middleware, "fn");
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
});
@@ -0,0 +1,17 @@
import { describe, vi, it, expect, afterEach } from "vitest";
import { middlewareOrder } from "../../../lib/helpers/withMiddleware";
afterEach(() => {
vi.resetAllMocks();
});
// Not sure if there is much point testing this order is actually applied via an integration test:
// It is tested internally https://github.com/htunnicliff/next-api-middleware/blob/368b12aa30e79f4bd7cfe7aacc18da263cc3de2f/lib/label.spec.ts#L62
describe("API - withMiddleware test", () => {
it("Custom prisma should be before verifyApiKey", async () => {
const customPrismaClientIndex = middlewareOrder.indexOf("customPrismaClient");
const verifyApiKeyIndex = middlewareOrder.indexOf("verifyApiKey");
expect(customPrismaClientIndex).toBeLessThan(verifyApiKeyIndex);
});
});
+7
View File
@@ -0,0 +1,7 @@
{
"functions": {
"pages/api/slots/*.ts": {
"memory": 512
}
}
}
+1
View File
@@ -0,0 +1 @@
Hello World
+2 -2
View File
@@ -10,7 +10,7 @@
"@calcom/config": "*",
"@calcom/dayjs": "*",
"@calcom/ui": "*",
"@radix-ui/react-avatar": "^1.0.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
@@ -49,7 +49,7 @@
"storybook-addon-designs": "^6.3.1",
"storybook-addon-next": "^1.6.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.3.1",
"tailwindcss": "^3.3.3",
"typescript": "^4.9.4",
"vite": "^4.1.2"
}
+68
View File
@@ -0,0 +1,68 @@
import { getBucket } from "abTest/utils";
import type { NextMiddleware, NextRequest } from "next/server";
import { NextResponse, URLPattern } from "next/server";
import z from "zod";
const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
].map(([pathname, enabled]) => [
new URLPattern({
pathname,
}),
enabled,
]);
const FUTURE_ROUTES_OVERRIDE_COOKIE_NAME = "x-calcom-future-routes-override";
const FUTURE_ROUTES_ENABLED_COOKIE_NAME = "x-calcom-future-routes-enabled";
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
export const abTestMiddlewareFactory =
(next: (req: NextRequest) => Promise<NextResponse<unknown>>): NextMiddleware =>
async (req: NextRequest) => {
const response = await next(req);
const { pathname } = req.nextUrl;
const override = req.cookies.has(FUTURE_ROUTES_OVERRIDE_COOKIE_NAME);
const route = ROUTES.find(([regExp]) => regExp.test(req.url)) ?? null;
const enabled = route !== null ? route[1] || override : false;
if (pathname.includes("future") || !enabled) {
return response;
}
const safeParsedBucket = override
? { success: true as const, data: "future" as const }
: bucketSchema.safeParse(req.cookies.get(FUTURE_ROUTES_ENABLED_COOKIE_NAME)?.value);
if (!safeParsedBucket.success) {
// cookie does not exist or it has incorrect value
const bucket = getBucket();
response.cookies.set(FUTURE_ROUTES_ENABLED_COOKIE_NAME, bucket, {
expires: Date.now() + 1000 * 60 * 30,
httpOnly: true,
}); // 30 min in ms
if (bucket === "legacy") {
return response;
}
const url = req.nextUrl.clone();
url.pathname = `future${pathname}/`;
return NextResponse.rewrite(url, response);
}
if (safeParsedBucket.data === "legacy") {
return response;
}
const url = req.nextUrl.clone();
url.pathname = `future${pathname}/`;
return NextResponse.rewrite(url, response);
};
+9
View File
@@ -0,0 +1,9 @@
import { AB_TEST_BUCKET_PROBABILITY } from "@calcom/lib/constants";
const cryptoRandom = () => {
return crypto.getRandomValues(new Uint8Array(1))[0] / 0xff;
};
export const getBucket = () => {
return cryptoRandom() * 100 < AB_TEST_BUCKET_PROBABILITY ? "future" : "legacy";
};
+8
View File
@@ -0,0 +1,8 @@
"use client";
import { createHydrateClient } from "app/_trpc/createHydrateClient";
import superjson from "superjson";
export const HydrateClient = createHydrateClient({
transformer: superjson,
});
+5
View File
@@ -0,0 +1,5 @@
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { createTRPCReact } from "@trpc/react-query";
export const trpc = createTRPCReact<AppRouter>({});
@@ -0,0 +1,21 @@
"use client";
import { type DehydratedState, Hydrate } from "@tanstack/react-query";
import { useMemo } from "react";
import type { DataTransformer } from "@trpc/server";
export function createHydrateClient(opts: { transformer?: DataTransformer }) {
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
const { state, children } = props;
const transformedState: DehydratedState = useMemo(() => {
if (opts.transformer) {
return opts.transformer.deserialize(state);
}
return state;
}, [state]);
return <Hydrate state={transformedState}>{children}</Hydrate>;
};
}
+128
View File
@@ -0,0 +1,128 @@
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { HydrateClient } from "app/_trpc/HydrateClient";
import { trpc } from "app/_trpc/client";
import { useState } from "react";
import superjson from "superjson";
import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink";
import { httpLink } from "@calcom/trpc/client/links/httpLink";
import { loggerLink } from "@calcom/trpc/client/links/loggerLink";
import { splitLink } from "@calcom/trpc/client/links/splitLink";
const ENDPOINTS = [
"admin",
"apiKeys",
"appRoutingForms",
"apps",
"auth",
"availability",
"appBasecamp3",
"bookings",
"deploymentSetup",
"eventTypes",
"features",
"insights",
"payments",
"public",
"saml",
"slots",
"teams",
"organizations",
"users",
"viewer",
"webhook",
"workflows",
"appsRouter",
"googleWorkspace",
] as const;
export type Endpoint = (typeof ENDPOINTS)[number];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const resolveEndpoint = (links: any) => {
// TODO: Update our trpc routes so they are more clear.
// This function parses paths like the following and maps them
// to the correct API endpoints.
// - viewer.me - 2 segment paths like this are for logged in requests
// - viewer.public.i18n - 3 segments paths can be public or authed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (ctx: any) => {
const parts = ctx.op.path.split(".");
let endpoint;
let path = "";
if (parts.length == 2) {
endpoint = parts[0] as keyof typeof links;
path = parts[1];
} else {
endpoint = parts[1] as keyof typeof links;
path = parts.splice(2, parts.length - 2).join(".");
}
return links[endpoint]({ ...ctx, op: { ...ctx.op, path } });
};
};
export const TrpcProvider: React.FC<{ children: React.ReactNode; dehydratedState?: DehydratedState }> = ({
children,
dehydratedState,
}) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
);
const url =
typeof window !== "undefined"
? "/api/trpc"
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}/api/trpc`
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
// adds pretty logs to your console in development and logs errors in production
loggerLink({
enabled: (opts) =>
!!process.env.NEXT_PUBLIC_DEBUG || (opts.direction === "down" && opts.result instanceof Error),
}),
splitLink({
// check for context property `skipBatch`
condition: (op) => !!op.context.skipBatch,
// when condition is true, use normal request
true: (runtime) => {
const links = Object.fromEntries(
ENDPOINTS.map((endpoint) => [
endpoint,
httpLink({
url: `${url}/${endpoint}`,
})(runtime),
])
);
return resolveEndpoint(links);
},
// when condition is false, use batch request
false: (runtime) => {
const links = Object.fromEntries(
ENDPOINTS.map((endpoint) => [
endpoint,
httpBatchLink({
url: `${url}/${endpoint}`,
})(runtime),
])
);
return resolveEndpoint(links);
},
}),
],
transformer: superjson,
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{dehydratedState ? <HydrateClient state={dehydratedState}>{children}</HydrateClient> : children}
</QueryClientProvider>
</trpc.Provider>
);
};
+40
View File
@@ -0,0 +1,40 @@
import { type TFunction } from "i18next";
import { headers } from "next/headers";
import { constructGenericImage } from "@calcom/lib/OgImages";
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
import { getFixedT } from "@calcom/lib/server/getFixedT";
import { preparePageMetadata } from "@lib/metadata";
export const _generateMetadata = async (
getTitle: (t: TFunction<string, undefined>) => string,
getDescription: (t: TFunction<string, undefined>) => string
) => {
const h = headers();
const canonical = h.get("x-pathname") ?? "";
const locale = h.get("x-locale") ?? "en";
const t = await getFixedT(locale, "common");
const title = getTitle(t);
const description = getDescription(t);
const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL);
const image =
SEO_IMG_OGIMG +
constructGenericImage({
title,
description,
});
return preparePageMetadata({
title,
canonical,
image,
description,
siteName: APP_NAME,
metadataBase,
});
};
+64
View File
@@ -0,0 +1,64 @@
"use client";
/**
* Typescript class based component for custom-error
* @link https://nextjs.org/docs/advanced-features/custom-error-page
*/
import type { NextPage } from "next";
import type { ErrorProps } from "next/error";
import React from "react";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { redactError } from "@calcom/lib/redactError";
import { ErrorPage } from "@components/error/error-page";
type NextError = Error & { digest?: string };
// Ref: https://nextjs.org/docs/app/api-reference/file-conventions/error#props
export type DefaultErrorProps = {
error: NextError;
reset: () => void; // A function to reset the error boundary
};
type AugmentedError = NextError | HttpError | null;
type CustomErrorProps = {
err?: AugmentedError;
statusCode?: number;
message?: string;
} & Omit<ErrorProps, "err" | "statusCode">;
const log = logger.getSubLogger({ prefix: ["[error]"] });
const CustomError: NextPage<DefaultErrorProps> = (props) => {
const { error } = props;
let errorObject: CustomErrorProps = {
message: error.message,
err: error,
};
if (error instanceof HttpError) {
const redactedError = redactError(error);
errorObject = {
statusCode: error.statusCode,
title: redactedError.name,
message: redactedError.message,
err: {
...redactedError,
...error,
},
};
}
// `error.digest` property contains an automatically generated hash of the error that can be used to match the corresponding error in server-side logs
log.debug(`${error?.toString() ?? JSON.stringify(error)}`);
log.info("errorObject: ", errorObject);
return (
<ErrorPage statusCode={errorObject.statusCode} error={errorObject.err} message={errorObject.message} />
);
};
export default CustomError;
@@ -0,0 +1,10 @@
import EventTypes from "@pages/event-types";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("event_types_page_title"),
(t) => t("event_types_page_subtitle")
);
export default EventTypes;
@@ -0,0 +1,22 @@
// pages without layout (e.g., /availability/index.tsx) are supposed to go under (layout) folder
import { headers } from "next/headers";
import { type ReactElement } from "react";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import PageWrapper from "@components/PageWrapperAppDir";
type WrapperWithLayoutProps = {
children: ReactElement;
};
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
{children}
</PageWrapper>
);
}
+17
View File
@@ -0,0 +1,17 @@
"use client";
import { type NextPage } from "next";
import CustomError, { type DefaultErrorProps } from "./error";
export const GlobalError: NextPage<DefaultErrorProps> = (props) => {
return (
<html>
<body>
<CustomError {...props} />
</body>
</html>
);
};
export default GlobalError;
+88
View File
@@ -0,0 +1,88 @@
import { dir } from "i18next";
import { headers, cookies } from "next/headers";
import Script from "next/script";
import React from "react";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { prepareRootMetadata } from "@lib/metadata";
import "../styles/globals.css";
export const generateMetadata = () =>
prepareRootMetadata({
twitterCreator: "@calcom",
twitterSite: "@calcom",
robots: {
index: false,
follow: false,
},
});
const getInitialProps = async (url: string) => {
const { pathname, searchParams } = new URL(url);
const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null;
const embedColorScheme = searchParams?.get("ui.color-scheme");
const req = { headers: headers(), cookies: cookies() };
const newLocale = await getLocale(req);
const direction = dir(newLocale);
return { isEmbed, embedColorScheme, locale: newLocale, direction };
};
const getFallbackProps = () => ({
locale: "en",
direction: "ltr",
isEmbed: false,
embedColorScheme: false,
});
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const h = headers();
const fullUrl = h.get("x-url") ?? "";
const nonce = h.get("x-csp") ?? "";
const isSSG = !fullUrl;
const { locale, direction, isEmbed, embedColorScheme } = isSSG
? getFallbackProps()
: await getInitialProps(fullUrl);
return (
<html
lang={locale}
dir={direction}
style={embedColorScheme ? { colorScheme: embedColorScheme as string } : undefined}
data-nextjs-router="app">
<head nonce={nonce}>
{!IS_PRODUCTION && process.env.VERCEL_ENV === "preview" && (
// eslint-disable-next-line @next/next/no-sync-scripts
<Script
data-project-id="KjpMrKTnXquJVKfeqmjdTffVPf1a6Unw2LZ58iE4"
src="https://snippet.meticulous.ai/v1/stagingMeticulousSnippet.js"
/>
)}
</head>
<body
className="dark:bg-darkgray-50 desktop-transparent bg-subtle antialiased"
style={
isEmbed
? {
background: "transparent",
// Keep the embed hidden till parent initializes and
// - gives it the appropriate styles if UI instruction is there.
// - gives iframe the appropriate height(equal to document height) which can only be known after loading the page once in browser.
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
visibility: "hidden",
}
: {}
}>
{children}
</body>
</html>
);
}
+1 -2
View File
@@ -631,8 +631,7 @@ paths:
type: string
role:
type: string
sendEmailInvitation:
type: boolean
parameters:
- schema:
type: string
+3 -3
View File
@@ -20,7 +20,7 @@ export default function AddToHomescreen() {
<div className="flex w-0 flex-1 items-center">
<span className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast flex rounded-lg bg-opacity-30 p-2">
<svg
className="h-7 w-7 fill-current text-indigo-500"
className="h-7 w-7 fill-current text-[#5B93F9]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 50 50"
enableBackground="new 0 0 50 50">
@@ -29,7 +29,7 @@ export default function AddToHomescreen() {
<path d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3z" />
</svg>
</span>
<p className="text-inverted ms-3 text-xs font-medium">
<p className="text-inverted ms-3 text-xs font-medium dark:text-white">
<span className="inline">{t("add_to_homescreen")}</span>
</p>
</div>
@@ -40,7 +40,7 @@ export default function AddToHomescreen() {
type="button"
className="-mr-1 flex rounded-md p-2 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white">
<span className="sr-only">{t("dismiss")}</span>
<X className="text-inverted h-6 w-6" aria-hidden="true" />
<X className="text-inverted h-6 w-6 dark:text-white" aria-hidden="true" />
</button>
</div>
</div>
+16 -10
View File
@@ -1,4 +1,4 @@
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
import { z } from "zod";
@@ -6,6 +6,7 @@ import { z } from "zod";
import type { CredentialOwner } from "@calcom/app-store/types";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { Badge, ListItemText, Avatar } from "@calcom/ui";
@@ -31,6 +32,7 @@ type AppListCardProps = {
invalidCredential?: boolean;
children?: ReactNode;
credentialOwner?: CredentialOwner;
className?: string;
} & ShouldHighlight;
const schema = z.object({ hl: z.string().optional() });
@@ -49,6 +51,7 @@ export default function AppListCard(props: AppListCardProps) {
invalidCredential,
children,
credentialOwner,
className,
} = props;
const {
data: { hl },
@@ -56,18 +59,22 @@ export default function AppListCard(props: AppListCardProps) {
const router = useRouter();
const [highlight, setHighlight] = useState(shouldHighlight && hl === slug);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const searchParams = useSearchParams();
const searchParams = useCompatSearchParams();
const pathname = usePathname();
useEffect(() => {
if (shouldHighlight && highlight) {
const timer = setTimeout(() => {
setHighlight(false);
if (shouldHighlight && highlight && searchParams !== null && pathname !== null) {
timeoutRef.current = setTimeout(() => {
const _searchParams = new URLSearchParams(searchParams);
_searchParams.delete("hl");
router.replace(`${pathname}?${_searchParams.toString()}`);
_searchParams.delete("category"); // this comes from params, not from search params
setHighlight(false);
const stringifiedSearchParams = _searchParams.toString();
router.replace(`${pathname}${stringifiedSearchParams !== "" ? `?${stringifiedSearchParams}` : ""}`);
}, 3000);
timeoutRef.current = timer;
}
return () => {
if (timeoutRef.current) {
@@ -75,11 +82,10 @@ export default function AppListCard(props: AppListCardProps) {
timeoutRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [highlight, pathname, router, searchParams, shouldHighlight]);
return (
<div className={classNames(highlight && "dark:bg-muted bg-yellow-100")}>
<div className={classNames(highlight && "dark:bg-muted bg-yellow-100", className)}>
<div className="flex items-center gap-x-3 px-4 py-4 sm:px-6">
{logo ? (
<img
+1 -49
View File
@@ -1,12 +1,7 @@
import { lookup } from "bcp-47-match";
import { useSession } from "next-auth/react";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import { trpc } from "@calcom/trpc/react";
function useViewerI18n(locale: string) {
export function useViewerI18n(locale: string) {
return trpc.viewer.public.i18n.useQuery(
{ locale, CalComVersion: CALCOM_VERSION },
{
@@ -19,46 +14,3 @@ function useViewerI18n(locale: string) {
}
);
}
function useClientLocale(locales: string[]) {
const session = useSession();
// If the user is logged in, use their locale
if (session.data?.user.locale) return session.data.user.locale;
// If the user is not logged in, use the browser locale
if (typeof window !== "undefined") {
// This is the only way I found to ensure the prefetched locale is used on first render
// FIXME: Find a better way to pick the best matching locale from the browser
return lookup(locales, window.navigator.language) || window.navigator.language;
}
// If the browser is not available, use English
return "en";
}
export function useClientViewerI18n(locales: string[]) {
const clientLocale = useClientLocale(locales);
return useViewerI18n(clientLocale);
}
/**
* Auto-switches locale client-side to the logged in user's preference
*/
const I18nLanguageHandler = (props: { locales: string[] }) => {
const { locales } = props;
const { i18n } = useTranslation("common");
const locale = useClientViewerI18n(locales).data?.locale || i18n.language;
useEffect(() => {
// bail early when i18n = {}
if (Object.keys(i18n).length === 0) return;
// if locale is ready and the i18n.language does != locale - changeLanguage
if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale);
}
// set dir="rtl|ltr"
document.dir = i18n.dir();
document.documentElement.setAttribute("lang", locale);
}, [locale, i18n]);
return null;
};
export default I18nLanguageHandler;
+1 -4
View File
@@ -13,8 +13,6 @@ import type { AppProps } from "@lib/app-providers";
import AppProviders from "@lib/app-providers";
import { seoConfig } from "@lib/config/next-seo.config";
import I18nLanguageHandler from "@components/I18nLanguageHandler";
export interface CalPageWrapper {
(props?: AppProps): JSX.Element;
PageWrapper?: AppProps["Component"]["PageWrapper"];
@@ -60,7 +58,7 @@ function PageWrapper(props: AppProps) {
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover"
/>
</Head>
<DefaultSeo
@@ -72,7 +70,6 @@ function PageWrapper(props: AppProps) {
}
{...seoConfig.defaultNextSeo}
/>
<I18nLanguageHandler locales={props.router.locales || []} />
<Script
nonce={nonce}
id="page-status"
+89
View File
@@ -0,0 +1,89 @@
"use client";
import { type DehydratedState } from "@tanstack/react-query";
import type { SSRConfig } from "next-i18next";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
// import I18nLanguageHandler from "@components/I18nLanguageHandler";
import { usePathname } from "next/navigation";
import Script from "next/script";
import type { ReactNode } from "react";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import type { AppProps } from "@lib/app-providers-app-dir";
import AppProviders from "@lib/app-providers-app-dir";
export interface CalPageWrapper {
(props?: AppProps): JSX.Element;
PageWrapper?: AppProps["Component"]["PageWrapper"];
}
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
const calFont = localFont({
src: "../fonts/CalSans-SemiBold.woff2",
variable: "--font-cal",
preload: true,
display: "swap",
});
export type PageWrapperProps = Readonly<{
getLayout: ((page: React.ReactElement) => ReactNode) | null;
children: React.ReactElement;
requiresLicense: boolean;
nonce: string | undefined;
themeBasis: string | null;
dehydratedState?: DehydratedState;
isThemeSupported?: boolean;
isBookingPage?: boolean;
i18n?: SSRConfig;
}>;
function PageWrapper(props: PageWrapperProps) {
const pathname = usePathname();
let pageStatus = "200";
if (pathname === "/404") {
pageStatus = "404";
} else if (pathname === "/500") {
pageStatus = "500";
}
// On client side don't let nonce creep into DOM
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
// See https://github.com/kentcdodds/nonce-hydration-issues
// Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error
const nonce = typeof window !== "undefined" ? (props.nonce ? "" : undefined) : props.nonce;
const providerProps: PageWrapperProps = {
...props,
nonce,
};
const getLayout: (page: React.ReactElement) => ReactNode = props.getLayout ?? ((page) => page);
return (
<AppProviders {...providerProps}>
{/* <I18nLanguageHandler locales={props.router.locales || []} /> */}
<>
<Script
nonce={nonce}
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
<style jsx global>{`
:root {
--font-inter: ${interFont.style.fontFamily};
--font-cal: ${calFont.style.fontFamily};
}
`}</style>
{getLayout(
props.requiresLicense ? <LicenseRequired>{props.children}</LicenseRequired> : props.children
)}
</>
</AppProviders>
);
}
export default PageWrapper;
+39 -18
View File
@@ -42,6 +42,7 @@ export type AppPageProps = {
disableInstall?: boolean;
dependencies?: string[];
concurrentMeetings: AppType["concurrentMeetings"];
paid?: AppType["paid"];
};
export const AppPage = ({
@@ -67,6 +68,7 @@ export const AppPage = ({
isTemplate,
dependencies,
concurrentMeetings,
paid,
}: AppPageProps) => {
const { t, i18n } = useLocale();
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
@@ -163,6 +165,19 @@ export const AppPage = ({
className="bg-subtle text-emphasis rounded-md p-1 text-xs capitalize">
{categories[0]}
</Link>{" "}
{paid && (
<>
<Badge className="mr-1">
{Intl.NumberFormat(i18n.language, {
style: "currency",
currency: "USD",
useGrouping: false,
maximumFractionDigits: 0,
}).format(paid.priceInUsd)}
/{t("month")}
</Badge>
</>
)}
•{" "}
<a target="_blank" rel="noreferrer" href={website}>
{t("published_by", { author })}
@@ -206,6 +221,7 @@ export const AppPage = ({
addAppMutationInput={{ type, variant, slug }}
multiInstall
concurrentMeetings={concurrentMeetings}
paid={paid}
{...props}
/>
);
@@ -244,6 +260,7 @@ export const AppPage = ({
addAppMutationInput={{ type, variant, slug }}
credentials={appDbQuery.data?.credentials}
concurrentMeetings={concurrentMeetings}
paid={paid}
{...props}
/>
);
@@ -263,7 +280,7 @@ export const AppPage = ({
<SkeletonButton className="mt-6 h-20 grow" />
))}
{price !== 0 && (
{price !== 0 && !paid && (
<span className="block text-right">
{feeType === "usage-based" ? `${commission}% + ${priceInDollar}/booking` : priceInDollar}
{feeType === "monthly" && `/${t("month")}`}
@@ -273,23 +290,27 @@ export const AppPage = ({
<div className="prose-sm prose prose-a:text-default prose-headings:text-emphasis prose-code:text-default prose-strong:text-default text-default mt-8">
{body}
</div>
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
<span className="text-default">
{teamsPlanRequired ? (
t("teams_plan_required")
) : price === 0 ? (
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat(i18n.language, {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && `/${t("month")}`}
</>
)}
</span>
{!paid && (
<>
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
<span className="text-default">
{teamsPlanRequired ? (
t("teams_plan_required")
) : price === 0 ? (
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat(i18n.language, {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && `/${t("month")}`}
</>
)}
</span>
</>
)}
<h4 className="text-emphasis mb-2 mt-8 font-semibold ">{t("contact")}</h4>
<ul className="prose-sm -ml-1 -mr-1 leading-5">
+115 -104
View File
@@ -14,8 +14,9 @@ import {
List,
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
Label,
} from "@calcom/ui";
import { Calendar, Plus } from "@calcom/ui/components/icon";
import { Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -27,6 +28,7 @@ type Props = {
onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean;
destinationCalendarId?: string;
isLoading?: boolean;
};
function CalendarList(props: Props) {
@@ -70,8 +72,9 @@ function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.connectedCalendars.useQuery(undefined, {
suspense: true,
refetchOnWindowFocus: false,
});
const { fromOnboarding } = props;
const { fromOnboarding, isLoading } = props;
return (
<QueryCell
query={query}
@@ -82,74 +85,94 @@ function ConnectedCalendarsList(props: Props) {
}
return (
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<AppListCard
shouldHighlight
slug={item.integration.slug}
title={item.integration.name}
logo={item.integration.logo}
description={item.primary?.email ?? item.integration.description}
actions={
<div className="flex w-32 justify-end">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}>
<div className="border-subtle border-t">
{!fromOnboarding && (
<>
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-4 px-5 py-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isChecked={cal.isSelected}
destination={cal.externalId === props.destinationCalendarId}
credentialId={cal.credentialId}
/>
))}
</ul>
</>
)}
<div className="border-subtle mt-6 rounded-lg border">
<div className="border-subtle border-b p-6">
<div className="flex items-center justify-between">
<div>
<h4 className="text-emphasis text-base font-semibold leading-5">
{t("check_for_conflicts")}
</h4>
<p className="text-default text-sm leading-tight">{t("select_calendars")}</p>
</div>
<div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && (
<div className="flex items-center">
<AdditionalCalendarSelector isLoading={isLoading} />
</div>
</AppListCard>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={
<span>
<Link href={`/apps/${item.integration.slug}`}>{item.integration.name}</Link>:{" "}
{t("calendar_error")}
</span>
}
iconClassName="h-10 w-10 ml-2 mr-1 mt-0.5"
actions={
<div className="flex w-32 justify-end md:pr-1">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
)}
</div>
</div>
</div>
<List noBorderTreatment className="p-6 pt-2">
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<AppListCard
shouldHighlight
slug={item.integration.slug}
title={item.integration.name}
logo={item.integration.logo}
description={item.primary?.email ?? item.integration.description}
className="border-subtle mt-4 rounded-lg border"
actions={
<div className="flex w-32 justify-end">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}>
<div className="border-subtle border-t">
{!fromOnboarding && (
<>
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-4 px-5 py-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isChecked={cal.isSelected}
destination={cal.externalId === props.destinationCalendarId}
credentialId={cal.credentialId}
/>
))}
</ul>
</>
)}
</div>
}
/>
)}
</Fragment>
))}
</List>
</AppListCard>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={
<span>
<Link href={`/apps/${item.integration.slug}`}>{item.integration.name}</Link>:{" "}
{t("calendar_error")}
</span>
}
iconClassName="h-10 w-10 ml-2 mr-1 mt-0.5"
actions={
<div className="flex w-32 justify-end md:pr-1">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}
/>
)}
</Fragment>
))}
</List>
</div>
);
}}
/>
@@ -187,41 +210,28 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
{!!data.connectedCalendars.length || !!installedCalendars.data?.items.length ? (
<>
{heading && (
<div className="border-default flex flex-col gap-6 rounded-md border p-7">
<ShellSubHeading
title={t("calendar")}
subtitle={t("installed_app_calendar_description")}
className="mb-0 flex flex-wrap items-center gap-4 sm:flex-nowrap md:mb-3 md:gap-0"
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && (
<div className="flex items-center">
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
</div>
)}
</div>
}
/>
<div className="bg-muted border-subtle flex justify-between rounded-md border p-4">
<div className="flex w-full flex-col items-start gap-4 md:flex-row md:items-center">
<div className="bg-default border-subtle relative rounded-md border p-1.5">
<Calendar className="text-default h-8 w-8" strokeWidth="1" />
<Plus
className="text-emphasis absolute left-4 top-1/2 ml-0.5 mt-[1px] h-2 w-2"
strokeWidth="4"
/>
</div>
<div className="md:w-6/12">
<h1 className="text-emphasis text-sm font-semibold">{t("create_events_on")}</h1>
<p className="text-default text-sm font-normal">{t("set_calendar")}</p>
</div>
<div className="justify-end md:w-6/12">
<DestinationCalendarSelector
onChange={mutation.mutate}
hidePlaceholder
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
/>
<>
<div className="border-subtle mb-6 mt-8 rounded-lg border">
<div className="p-6">
<h2 className="text-emphasis mb-1 text-base font-bold leading-5 tracking-wide">
{t("add_to_calendar")}
</h2>
<p className="text-subtle text-sm leading-tight">
{t("add_to_calendar_description")}
</p>
</div>
<div className="border-t">
<div className="border-subtle flex w-full flex-col space-y-3 border-y-0 p-6">
<div>
<Label className="text-default mb-0 font-medium">{t("add_events_to")}</Label>
<DestinationCalendarSelector
hidePlaceholder
value={data.destinationCalendar?.externalId}
onChange={mutation.mutate}
isLoading={mutation.isLoading}
/>
</div>
</div>
</div>
</div>
@@ -229,8 +239,9 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
onChanged={onChanged}
fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId}
isLoading={mutation.isLoading}
/>
</div>
</>
)}
</>
) : fromOnboarding ? (
@@ -26,6 +26,7 @@ export const InstallAppButtonChild = ({
multiInstall,
credentials,
concurrentMeetings,
paid,
...props
}: {
userAdminTeams?: UserAdminTeams;
@@ -34,6 +35,7 @@ export const InstallAppButtonChild = ({
multiInstall?: boolean;
credentials?: RouterOutputs["viewer"]["appCredentialsByType"]["credentials"];
concurrentMeetings?: boolean;
paid?: AppFrontendPayload["paid"];
} & ButtonProps) => {
const { t } = useLocale();
@@ -46,8 +48,27 @@ export const InstallAppButtonChild = ({
if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error");
},
});
const shouldDisableInstallation = !multiInstall ? !!(credentials && credentials.length) : false;
if (!userAdminTeams?.length || !doesAppSupportTeamInstall(appCategories, concurrentMeetings)) {
// Paid apps don't support team installs at the moment
// Also, cal.ai(the only paid app at the moment) doesn't support team install either
if (paid) {
return (
<Button
data-testid="install-app-button"
{...props}
disabled={shouldDisableInstallation}
color="primary"
size="base">
{paid.trial ? t("start_paid_trial") : t("subscribe")}
</Button>
);
}
if (
!userAdminTeams?.length ||
!doesAppSupportTeamInstall({ appCategories, concurrentMeetings, isPaid: !!paid })
) {
return (
<Button
data-testid="install-app-button"
@@ -55,6 +76,7 @@ export const InstallAppButtonChild = ({
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
color="primary"
disabled={shouldDisableInstallation}
size="base">
{multiInstall ? t("install_another") : t("install_app")}
</Button>
+67 -45
View File
@@ -32,8 +32,6 @@ import {
} from "@calcom/ui";
import { Ban, Check, Clock, CreditCard, MapPin, RefreshCcw, Send, X } from "@calcom/ui/components/icon";
import useMeQuery from "@lib/hooks/useMeQuery";
import { ChargeCardDialog } from "@components/dialog/ChargeCardDialog";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
@@ -45,14 +43,18 @@ type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringInfo: RouterOutputs["viewer"]["bookings"]["get"]["recurringInfo"][number] | undefined;
loggedInUser: {
userId: number | undefined;
userTimeZone: string | undefined;
userTimeFormat: number | null | undefined;
userEmail: string | undefined;
};
};
function BookingListItem(booking: BookingItemProps) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const bookerUrl = useBookerUrl();
const { userId, userTimeZone, userTimeFormat, userEmail } = booking.loggedInUser;
const user = query.data;
const {
t,
i18n: { language },
@@ -141,17 +143,6 @@ function BookingListItem(booking: BookingItemProps) {
: []),
];
const showRecordingActions: ActionType[] = [
{
id: "view_recordings",
label: t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
disabled: mutation.isLoading,
},
];
let bookedActions: ActionType[] = [
{
id: "cancel",
@@ -226,6 +217,7 @@ function BookingListItem(booking: BookingItemProps) {
};
const startTime = dayjs(booking.startTime)
.tz(userTimeZone)
.locale(language)
.format(isUpcoming ? "ddd, D MMM" : "D MMMM YYYY");
const [isOpenRescheduleDialog, setIsOpenRescheduleDialog] = useState(false);
@@ -269,11 +261,25 @@ function BookingListItem(booking: BookingItemProps) {
const bookingLink = buildBookingLink();
const title = booking.title;
// To be used after we run query on legacy bookings
// const showRecordingsButtons = booking.isRecorded && isPast && isConfirmed;
const showRecordingsButtons =
(booking.location === "integrations:daily" || booking?.location?.trim() === "") && isPast && isConfirmed;
const showViewRecordingsButton = !!(booking.isRecorded && isPast && isConfirmed);
const showCheckRecordingButton =
isPast &&
isConfirmed &&
!booking.isRecorded &&
(!booking.location || booking.location === "integrations:daily" || booking?.location?.trim() === "");
const showRecordingActions: ActionType[] = [
{
id: "view_recordings",
label: showCheckRecordingButton ? t("check_for_recordings") : t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
color: showCheckRecordingButton ? "secondary" : "primary",
disabled: mutation.isLoading,
},
];
return (
<>
@@ -298,12 +304,12 @@ function BookingListItem(booking: BookingItemProps) {
paymentCurrency={booking.payment[0].currency}
/>
)}
{showRecordingsButtons && (
{(showViewRecordingsButton || showCheckRecordingButton) && (
<ViewRecordingsDialog
booking={booking}
isOpenDialog={viewRecordingsDialogIsOpen}
setIsOpenDialog={setViewRecordingsDialogIsOpen}
timeFormat={user?.timeFormat ?? null}
timeFormat={userTimeFormat ?? null}
/>
)}
{/* NOTE: Should refactor this dialog component as is being rendered multiple times */}
@@ -343,11 +349,11 @@ function BookingListItem(booking: BookingItemProps) {
<div className="cursor-pointer py-4">
<div className="text-emphasis text-sm leading-6">{startTime}</div>
<div className="text-subtle text-sm">
{formatTime(booking.startTime, user?.timeFormat, user?.timeZone)} -{" "}
{formatTime(booking.endTime, user?.timeFormat, user?.timeZone)}
{formatTime(booking.startTime, userTimeFormat, userTimeZone)} -{" "}
{formatTime(booking.endTime, userTimeFormat, userTimeZone)}
<MeetingTimeInTimezones
timeFormat={user?.timeFormat}
userTimezone={user?.timeZone}
timeFormat={userTimeFormat}
userTimezone={userTimeZone}
startTime={booking.startTime}
endTime={booking.endTime}
attendees={booking.attendees}
@@ -374,7 +380,12 @@ function BookingListItem(booking: BookingItemProps) {
) : null}
{recurringDates !== undefined && (
<div className="text-muted mt-2 text-sm">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
<RecurringBookingsTooltip
userTimeFormat={userTimeFormat}
userTimeZone={userTimeZone}
booking={booking}
recurringDates={recurringDates}
/>
</div>
)}
</div>
@@ -387,11 +398,11 @@ function BookingListItem(booking: BookingItemProps) {
<div className="flex w-full items-center justify-between sm:hidden">
<div className="text-emphasis text-sm leading-6">{startTime}</div>
<div className="text-subtle pr-2 text-sm">
{formatTime(booking.startTime, user?.timeFormat, user?.timeZone)} -{" "}
{formatTime(booking.endTime, user?.timeFormat, user?.timeZone)}
{formatTime(booking.startTime, userTimeFormat, userTimeZone)} -{" "}
{formatTime(booking.endTime, userTimeFormat, userTimeZone)}
<MeetingTimeInTimezones
timeFormat={user?.timeFormat}
userTimezone={user?.timeZone}
timeFormat={userTimeFormat}
userTimezone={userTimeZone}
startTime={booking.startTime}
endTime={booking.endTime}
attendees={booking.attendees}
@@ -416,7 +427,12 @@ function BookingListItem(booking: BookingItemProps) {
)}
{recurringDates !== undefined && (
<div className="text-muted text-sm sm:hidden">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
<RecurringBookingsTooltip
userTimeFormat={userTimeFormat}
userTimeZone={userTimeZone}
booking={booking}
recurringDates={recurringDates}
/>
</div>
)}
</div>
@@ -448,7 +464,7 @@ function BookingListItem(booking: BookingItemProps) {
<DisplayAttendees
attendees={booking.attendees}
user={booking.user}
currentEmail={user?.email}
currentEmail={userEmail}
/>
)}
{isCancelled && booking.rescheduled && (
@@ -462,13 +478,15 @@ function BookingListItem(booking: BookingItemProps) {
<td className="flex w-full justify-end py-4 pl-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4 sm:pl-0">
{isUpcoming && !isCancelled ? (
<>
{isPending && user?.id === booking.user?.id && <TableActions actions={pendingActions} />}
{isPending && userId === booking.user?.id && <TableActions actions={pendingActions} />}
{isConfirmed && <TableActions actions={bookedActions} />}
{isRejected && <div className="text-subtle text-sm">{t("rejected")}</div>}
</>
) : null}
{isPast && isPending && !isConfirmed ? <TableActions actions={bookedActions} /> : null}
{showRecordingsButtons && <TableActions actions={showRecordingActions} />}
{(showViewRecordingsButton || showCheckRecordingButton) && (
<TableActions actions={showRecordingActions} />
)}
{isCancelled && booking.rescheduled && (
<div className="hidden h-full items-center md:flex">
<RequestSentMessage />
@@ -488,12 +506,16 @@ function BookingListItem(booking: BookingItemProps) {
interface RecurringBookingsTooltipProps {
booking: BookingItemProps;
recurringDates: Date[];
userTimeZone: string | undefined;
userTimeFormat: number | null | undefined;
}
const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookingsTooltipProps) => {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
const RecurringBookingsTooltip = ({
booking,
recurringDates,
userTimeZone,
userTimeFormat,
}: RecurringBookingsTooltipProps) => {
const {
t,
i18n: { language },
@@ -503,8 +525,8 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
return (
recurringDate >= now &&
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(recurringDate.toDateString())
.map((date) => date.toString())
.includes(recurringDate.toString())
);
}).length;
@@ -521,11 +543,11 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
const pastOrCancelled =
aDate < now ||
booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(aDate.toDateString());
.map((date) => date.toString())
.includes(aDate.toString());
return (
<p key={key} className={classNames(pastOrCancelled && "line-through")}>
{formatTime(aDate, user?.timeFormat, user?.timeZone)}
{formatTime(aDate, userTimeFormat, userTimeZone)}
{" - "}
{dayjs(aDate).locale(language).format("D MMMM YYYY")}
</p>
@@ -538,7 +560,7 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
/>
<p className="mt-1 pl-5 text-xs">
{booking.status === BookingStatus.ACCEPTED
? `${t("event_remaining", {
? `${t("event_remaining_other", {
count: recurringCount,
})}`
: getEveryFreqFor({
@@ -1,4 +1,4 @@
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -26,9 +26,6 @@ type Props = {
};
export default function CancelBooking(props: Props) {
const pathname = usePathname();
const searchParams = useSearchParams();
const asPath = `${pathname}?${searchParams.toString()}`;
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
@@ -44,6 +41,7 @@ export default function CancelBooking(props: Props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{error && (
@@ -100,7 +98,8 @@ export default function CancelBooking(props: Props) {
});
if (res.status >= 200 && res.status < 300) {
router.replace(asPath);
// tested by apps/web/playwright/booking-pages.e2e.ts
router.refresh();
} else {
setLoading(false);
setError(
@@ -47,7 +47,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<div className=" bg-subtle flex h-10 w-10 flex-shrink-0 justify-center rounded-full">
<CreditCard className="m-auto h-6 w-6" />
</div>
<div className="pt-1">
@@ -356,9 +356,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
if (val.credential) {
locationFormMethods.setValue("credentialId", val.credential.id);
locationFormMethods.setValue("teamName", val.credential.team?.name);
if (!!val.credentialId) {
locationFormMethods.setValue("credentialId", val.credentialId);
locationFormMethods.setValue("teamName", val.teamName);
}
locationFormMethods.unregister([

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