Compare commits

...
440 Commits
Author SHA1 Message Date
Crowdin Bot ebe60c36bd New Crowdin translations by Github Action
release.yml / release (push) Canceled after 0s
2023-07-19 14:33:31 +00:00
Monto 26e458be6d feat: dynamic time format rendering in workflows based on user settings (#10229) 2023-07-19 10:30:37 -04:00
Crowdin Bot 1ad4f7247e New Crowdin translations by Github Action 2023-07-19 12:37:55 +00:00
Crowdin Bot e0237acac9 New Crowdin translations by Github Action 2023-07-19 12:35:34 +00:00
Crowdin Bot 2dc0bb4888 New Crowdin translations by Github Action 2023-07-19 12:33:09 +00:00
sean-brydon dfffd8b577 fix: revert i18n hook changes (#10249) 2023-07-19 12:30:25 +00:00
Crowdin Bot e6f2ba2c25 New Crowdin translations by Github Action 2023-07-19 09:22:27 +00:00
sean-brydonandKeith Williams 628a29e89c chore: fixing some initial re-renders (#10185)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-07-19 09:19:53 +00:00
Crowdin Bot 2fd9ef2733 New Crowdin translations by Github Action 2023-07-19 09:05:08 +00:00
Crowdin Bot c9f2d6bc93 New Crowdin translations by Github Action 2023-07-19 09:01:24 +00:00
Peer Richelsen a83aea30da Update package.json 2023-07-19 10:58:27 +02:00
Hariom BalharaandShivam Kalra b516a2af44 Get OAuth working for local (#10054)
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-19 09:15:05 +02:00
Crowdin Bot d0e831975d New Crowdin translations by Github Action 2023-07-19 06:40:31 +00:00
Crowdin Bot b906c7caf7 New Crowdin translations by Github Action 2023-07-19 06:37:40 +00:00
Crowdin Bot 152dbe58c9 New Crowdin translations by Github Action 2023-07-19 06:34:49 +00:00
Crowdin Bot b4c154a6af New Crowdin translations by Github Action 2023-07-19 06:32:11 +00:00
Crowdin Bot 99cea7a326 New Crowdin translations by Github Action 2023-07-19 06:29:40 +00:00
Crowdin Bot d0ae519851 New Crowdin translations by Github Action 2023-07-19 06:27:16 +00:00
Crowdin Bot d185c665b8 New Crowdin translations by Github Action 2023-07-19 06:24:21 +00:00
Crowdin Bot baa9ddf46a New Crowdin translations by Github Action 2023-07-19 06:21:52 +00:00
Alex van Andel e8f5090acc Fix: Flaky 2FA test due to waiting redundant Enter (#10236) 2023-07-19 08:19:16 +02:00
Crowdin Bot 0661b96cd2 New Crowdin translations by Github Action 2023-07-19 05:10:16 +00:00
Crowdin Bot 3a64506c9e New Crowdin translations by Github Action 2023-07-19 05:07:31 +00:00
Crowdin Bot f1b705e022 New Crowdin translations by Github Action 2023-07-19 05:05:00 +00:00
Crowdin Bot c768d52539 New Crowdin translations by Github Action 2023-07-19 05:02:24 +00:00
Crowdin Bot 7d3ba5c55c New Crowdin translations by Github Action 2023-07-19 04:59:34 +00:00
Crowdin Bot e643afaa80 New Crowdin translations by Github Action 2023-07-19 04:57:05 +00:00
Crowdin Bot 4cd3479cfd New Crowdin translations by Github Action 2023-07-19 04:54:39 +00:00
Crowdin Bot 7efddfd213 New Crowdin translations by Github Action 2023-07-19 04:51:59 +00:00
Crowdin Bot a8c362dbf3 New Crowdin translations by Github Action 2023-07-19 04:49:27 +00:00
Crowdin Bot f6b3cc0c7c New Crowdin translations by Github Action 2023-07-19 04:46:55 +00:00
Hariom Balhara 902bb71273 Prefill logged in info (#10240) 2023-07-19 06:44:13 +02:00
db021cc696 fix: update list for countries that don't support alphanumeric sender ids (#10232)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-18 21:31:45 +00:00
Jaideep GuntupalliandUdit Takkar a2645c2ed4 feat: replaced slack icon with Discord (#10193)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-07-18 21:30:17 +00:00
Anik Dhabal Babu 1ec9a6924c feddback added (#10221) 2023-07-18 21:29:56 +00:00
Leo Giovanetti 09068b39e6 perf: Event-type config page (#10238) 2023-07-18 23:18:08 +02:00
Crowdin Bot 9069a2412d New Crowdin translations by Github Action 2023-07-18 20:30:45 +00:00
zomars 6b5c8935c5 fix: formatting 2023-07-18 13:27:54 -07:00
Crowdin Bot d27ff41682 New Crowdin translations by Github Action 2023-07-18 19:53:12 +00:00
Crowdin Bot c6ca736daa New Crowdin translations by Github Action 2023-07-18 19:50:00 +00:00
Carina WollendorferandCarinaWolli cedb2b72e9 fix: add missing booking paid webhook trigger to dropdwon (#10237)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-18 19:46:53 +00:00
Hariom Balhara 11081cb5d8 fix: Embed - Non transparent background when color-scheme of embedding document and cal.com iframe don't match (#10033)
## What does this PR do?

Fixes #10032

Before:
https://www.loom.com/share/1890e4eebf41426aaf66d8b4909d0765
After:
https://www.loom.com/share/8b00c41587f2467ab05ef48e4ed523ef


## Type of change

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

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

## How should this be tested?

- See loom

## Mandatory Tasks

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

## Checklist
- I haven't added tests that prove my fix is effective or that my feature works
2023-07-18 09:46:35 -07:00
Hariom Balhara 34d65f67a9 fix: Avoid resetting state one every render. (#10210) 2023-07-18 16:42:40 +00:00
Keith Williams e4c49d0e0b fix: Added step to create env file for bundle analysis (#10224) 2023-07-18 09:20:17 -07:00
Peer Richelsen 43a30aa51d Update package.json
release.yml / release (push) Canceled after 0s
2023-07-18 18:14:14 +02:00
Jay Pitroda eb13057157 fix: booking error on invalid timezone (#10209) 2023-07-18 16:09:31 +00:00
Syed Ali Shahbaz bb4d587359 fix: lint errors introduced in PR#10225 (#10230) 2023-07-18 15:58:32 +00:00
Crowdin Bot 3d7c32c0d8 New Crowdin translations by Github Action 2023-07-18 15:41:03 +00:00
39508b3daa fix: booking page: location icon is not showing up (CALCOM-9951) (#9993)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-18 15:37:39 +00:00
Richard PoelderlandKeith Williams e79c818edb chore: Add website deps to yarn.lock (#10219)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-07-18 14:37:16 +00:00
Mehul 8a1023087f fix: inconsistent duration labels (#10223) 2023-07-18 15:10:28 +01:00
Crowdin Bot 9343aee778 New Crowdin translations by Github Action 2023-07-18 13:52:15 +00:00
Syed Ali Shahbaz 89f645c450 fix: Hotfix-bookings list breaks on unsupported timezone (#10225)
* Adds a check for supportedTimezone values

* adds timezone support check for attendees
2023-07-18 13:49:23 +00:00
Crowdin Bot da5d90eb07 New Crowdin translations by Github Action 2023-07-18 11:29:18 +00:00
Crowdin Bot 9f30f95dd9 New Crowdin translations by Github Action 2023-07-18 11:26:41 +00:00
Crowdin Bot bc0c1341f6 New Crowdin translations by Github Action 2023-07-18 11:24:09 +00:00
Crowdin Bot 1c0a51cc02 New Crowdin translations by Github Action 2023-07-18 11:21:42 +00:00
Crowdin Bot 7cfe7647f3 New Crowdin translations by Github Action 2023-07-18 11:17:58 +00:00
Crowdin Bot cc91834ddd New Crowdin translations by Github Action 2023-07-18 11:14:53 +00:00
Crowdin Bot 2cb1a62906 New Crowdin translations by Github Action 2023-07-18 11:12:13 +00:00
Crowdin Bot 6135d449ec New Crowdin translations by Github Action 2023-07-18 11:09:31 +00:00
Crowdin Bot 073b29c214 New Crowdin translations by Github Action 2023-07-18 11:06:19 +00:00
Crowdin Bot ea99dc5520 New Crowdin translations by Github Action 2023-07-18 11:03:30 +00:00
Crowdin Bot c3280a62b7 New Crowdin translations by Github Action 2023-07-18 11:00:47 +00:00
Crowdin Bot a41b42359a New Crowdin translations by Github Action 2023-07-18 10:57:56 +00:00
Crowdin Bot cc15fb5285 New Crowdin translations by Github Action 2023-07-18 10:55:10 +00:00
Crowdin Bot e554268592 New Crowdin translations by Github Action 2023-07-18 10:52:35 +00:00
Crowdin Bot f4c2764cbf New Crowdin translations by Github Action 2023-07-18 10:49:58 +00:00
Crowdin Bot ac54ccdd70 New Crowdin translations by Github Action 2023-07-18 10:47:26 +00:00
Hariom BalharaandShivam Kalra b3216634c9 fix: Add missing auth package to yarn.lock (#10214)
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-18 12:44:38 +02:00
Crowdin Bot 6c437edc0e New Crowdin translations by Github Action 2023-07-18 10:11:17 +00:00
Crowdin Bot bed153b497 New Crowdin translations by Github Action 2023-07-18 10:08:12 +00:00
2be245eefc add CAL_URL to fix og image issue (#10172)
Co-authored-by: Raghul D <v-raghuld@microsoft.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-18 10:05:28 +00:00
Crowdin Bot caf3b40687 New Crowdin translations by Github Action 2023-07-18 09:33:33 +00:00
Crowdin Bot 5650bd6409 New Crowdin translations by Github Action 2023-07-18 09:30:53 +00:00
Crowdin Bot 94a86acdaa New Crowdin translations by Github Action 2023-07-18 09:28:07 +00:00
Crowdin Bot defd49f986 New Crowdin translations by Github Action 2023-07-18 09:25:37 +00:00
Crowdin Bot 679d3da957 New Crowdin translations by Github Action 2023-07-18 09:23:02 +00:00
Crowdin Bot e84fd39e11 New Crowdin translations by Github Action 2023-07-18 09:20:12 +00:00
Crowdin Bot 1d74e9c201 New Crowdin translations by Github Action 2023-07-18 09:17:11 +00:00
Crowdin Bot 3356cbc1df New Crowdin translations by Github Action 2023-07-18 09:13:52 +00:00
Crowdin Bot 4584f666e0 New Crowdin translations by Github Action 2023-07-18 09:10:50 +00:00
Crowdin Bot c9078cfd94 New Crowdin translations by Github Action 2023-07-18 09:07:49 +00:00
Crowdin Bot 4ae1c2aa8c New Crowdin translations by Github Action 2023-07-18 09:05:11 +00:00
Crowdin Bot cf15f5f2a2 New Crowdin translations by Github Action 2023-07-18 09:01:52 +00:00
Crowdin Bot d21f42c257 New Crowdin translations by Github Action 2023-07-18 08:58:31 +00:00
Crowdin Bot 92a0dff2c7 New Crowdin translations by Github Action 2023-07-18 08:55:56 +00:00
Udit Takkar 47add7e7fc fix: modals regression (#10211)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-07-18 09:53:01 +01:00
Hariom Balhara 6dfc19247e fix: Embed theme not working using Embed API (#10163)
## What does this PR do?

Fixes #10187
See [Tests Done](https://www.loom.com/share/f03e0191b60143d8b45a505042dbfa11)

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

## How should this be tested?
- [x] Configure embed to use `dark` theme and verify that dark theme is shown on event booking page(when user has light theme set). This is failing in main
- Additional Tests for embed to avoid any new regression
	- [x] - Configure "auto" theme using embed API and see it reacts to system theme
	- [x] - Don't configure any theme and see that "light" theme is shown even when we switch system theme(Because User has configured light theme in App)
-  Tests outside embed to avoid any new regression
	- [x] - See that light theme is shown even after switching system theme
	- [x] - Now, switch the user theme to dark and see that it reflects the change. 

## Mandatory Tasks

 [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-07-17 18:02:42 -07:00
2db4998eaa fix: wrong slot starting times for half hour times zones (#10161)
* fix timezone zone issue for first slot

* fix failed test in getSchedule

* Update apps/web/test/lib/getSchedule.test.ts

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-07-18 00:57:34 +00:00
alannncandLeo Giovanetti dad4fd17fb fix: Improve continue button on org-add-team and validations [CAL-2015] (#10122)
* Improve continue button on team add and validations

* Fix server validations for orgs create team

* Remove unused prop form input

* add comment for context

* Fix duplicatedSlugs not working properly

* Fix continue button disabled state, added more examples for team names

* Fix initial state for inputValues on addTeamForm

* Don't allow whitespaces only inputs on create teams

* Use rhf instead of react useState

---------

Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-17 17:17:29 -07:00
c26efcabe4 fix: customTemplate() function fails when no locale is given (#10201)
* fallback to en for locale

* remove console.log

* Fix nit changes

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-07-17 16:38:37 -07:00
Matthias GemperliandPeer Richelsen 6a8db56dbb fix: locales in dates utils (#10067)
release.yml / release (push) Canceled after 0s
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-17 20:19:00 +00:00
6c058d041a fix: check for single email in TextAreaField (#10149)
Co-authored-by: Dewansh Chaudhari <codewansh@Dewanshs-MacBook-Pro.local>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-17 20:12:12 +00:00
Andrew Milich 07b43e107e feat: Add Skiff to app store (#10079) 2023-07-17 13:07:35 -07:00
Peer Richelsen ab8ad6434f Update package.json 2023-07-17 13:05:08 -07:00
4d7a3ac21a fix: modal footer issue (#10020)
Co-authored-by: Raghul D <v-raghuld@microsoft.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-07-17 13:02:34 -07:00
sean-brydon 2ac709630f feat: Page loading indicator (#10195) 2023-07-17 13:00:44 -07:00
Crowdin Bot 64147d3618 New Crowdin translations by Github Action 2023-07-17 15:39:08 +00:00
alannnc fb0e89ab22 fix: possible to have same slug for team and user in an organization 9985 cal 2122 (#10170)
* Added rule to validate username vs team slugs in team considering orgs too

* Validate valid username vs team slugs

* Add or clause to find org team and child teams
2023-07-17 08:36:14 -07:00
Crowdin Bot 7bee930983 New Crowdin translations by Github Action 2023-07-17 13:30:56 +00:00
Hariom Balhara 9678361f5c fix: Embed/Show header in weekly view and column view (#10192) 2023-07-17 18:58:04 +05:30
sean-brydon a404ba471d Move to minmax instead of auto grid (#10190) 2023-07-17 12:58:30 +00:00
Crowdin Bot 9a8aab2e54 New Crowdin translations by Github Action 2023-07-17 12:20:10 +00:00
Satya NishanthandSatya Nishanth 4c54e9c384 CAL-2126 url layout query param is source of truth for layout (#10100)
Co-authored-by: Satya Nishanth <satya@Satyas-MacBook-Pro.local>
2023-07-17 14:16:50 +02:00
Peer Richelsen 8dd2135656 fix: iw to he (hebrew) (#10184) 2023-07-17 09:57:29 +00:00
Crowdin Bot 4ba72425b9 New Crowdin translations by Github Action 2023-07-17 09:54:27 +00:00
Jaideep GuntupalliandPeer Richelsen f4b0772746 refactor: renamed getMostPopularApps function to getInstallCountPerApp (#10140)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-17 09:51:35 +00:00
Crowdin Bot 1a605b555a New Crowdin translations by Github Action 2023-07-17 03:18:34 +00:00
Pradumn KumarandHariom Balhara 17324e406c feat: unit tests for embed pages (#10014)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-07-17 08:46:00 +05:30
Crowdin Bot 833459cabb New Crowdin translations by Github Action 2023-07-16 21:34:09 +00:00
Peer RichelsenandLeo Giovanetti 6076fd7ba0 reduced duplicate event type to 1 min (#10159)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-16 23:31:34 +02:00
Crowdin Bot ca8b380815 New Crowdin translations by Github Action 2023-07-15 16:03:37 +00:00
Omar López d7953aa53a v3.1.0
release.yml / release (push) Canceled after 0s
2023-07-15 09:01:03 -07:00
Joe Au-Yeung 494012484e fix: Add feedback when user installs app [CAL-2144] (#10169)
* Prevent multiple clicks on app store

* Disable multiple clicks on app page

* Disable multiple clicks on app page

* Clean up
2023-07-15 05:43:10 +02:00
Crowdin Bot 8c6669e204 New Crowdin translations by Github Action 2023-07-15 01:22:39 +00:00
Leo Giovanetti d5f3623377 chore: Relying on feature flag (#10162) 2023-07-14 22:20:03 -03:00
Crowdin Bot ef483a6520 New Crowdin translations by Github Action 2023-07-14 23:11:42 +00:00
Crowdin Bot 339f00f6aa New Crowdin translations by Github Action 2023-07-14 23:09:32 +00:00
e98bebb9b2 feat: Zapier For Teams (#9851)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-07-14 16:06:57 -07:00
Crowdin Bot dcd7e80e6a New Crowdin translations by Github Action 2023-07-14 23:04:48 +00:00
zomars fcc13da23d Update rateLimit.ts 2023-07-14 16:02:10 -07:00
zomars 67a210d439 Sync packages 2023-07-14 16:00:59 -07:00
zomars e813f8bb17 Update rateLimit.ts 2023-07-14 15:59:04 -07:00
Carina WollendorferandCarinaWolli 5ff8d2db6e fix: slot start times with interrupted availabilities (#10164)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-14 15:57:05 -07:00
Crowdin Bot 7953551189 New Crowdin translations by Github Action 2023-07-14 17:04:38 +00:00
jemiluv8 879bc3f169 fix: minor bug in schedule sms and whatsapp reminders cron job (#10135) 2023-07-14 19:02:06 +02:00
Purushottam Khedreandsean-brydon 4aaf5da36d fix: no feedback on buttons on /getting-started page. (#10152)
* add isLoading type to ButtonProps type

* add loading feedback

* Update apps/web/components/getting-started/components/AppConnectionItem.tsx

Not neeeded as buttonProps.isLoading is false by default :)

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-07-14 09:03:11 +00:00
alannnc fb16d8e900 Added distinct clause when getting user list in team (#10144) 2023-07-14 00:04:30 +02:00
Afzal SayedandAlex van Andel 58d6bb840a feat: Auto submit 2FA if all inputs have a value (#9729)
* auto submit 2FA if all inputs have a value

* organize imports

* Fix duplicate text issue, this label is part of TwoFactor

* Fixed test that was failing due to enable-2fa not clickable anymore due to auto-submit

* Remove extra newline for clarity

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-07-13 20:54:57 +00:00
Carina WollendorferandCarinaWolli 1419c534c8 fix: webhooks for events with seats (#10143)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-13 22:40:16 +02:00
Pradumn Kumar 4de1de3418 fix: added slugify the eventType slugs on the api aswell (#10007) 2023-07-13 17:33:26 +00:00
Rama Krishna Reddyandrkreddy99 c23a2102f4 remove max width from bookings page (#10126)
Co-authored-by: rkreddy99 <rreddy@e2clouds.com>
2023-07-13 19:17:02 +02:00
Keith Williams df686370ab chore: Update GitHub env caching to use current PR details (#10096) 2023-07-13 19:08:50 +02:00
Alex van Andel 0bc7d4ceda feat: Override any day of the week (#10138)
* feat: Override any day of the week

* Having multiple workingHours would duplicate the default
2023-07-13 15:33:27 +00:00
Hariom Balhara 9444f925e5 fix: Important Booking Page fixes (#10130) 2023-07-13 19:38:41 +05:30
Hariom Balhara 2918a34e1a fix: More instances of Avatar and booker links fixed (#10083) 2023-07-13 19:22:21 +05:30
213caa722e feat: Added order by most popular in Onboarding screen (#9891)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-13 19:17:46 +05:30
Crowdin Bot 6c7d7be9cc New Crowdin translations by Github Action 2023-07-13 13:32:30 +00:00
Nafees Nazikandsean-brydon f1ef50e606 fix: upload csv button not clickable (#9981)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-07-13 15:29:29 +02:00
Alex van Andel 22c51ba483 v3.0.16
release.yml / release (push) Canceled after 0s
2023-07-13 14:40:20 +02:00
Alimurtuza b6eaafa39f fix: "change username" dialog design (#10095) 2023-07-13 14:28:16 +02:00
98707e66e1 fix: Refresh session (#10019)
* Refresh session 🔄

* Update apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx

Co-authored-by: Omar López <zomars@me.com>

* Update apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx

Co-authored-by: Omar López <zomars@me.com>

* Update packages/features/auth/lib/next-auth-options.ts

Co-authored-by: Omar López <zomars@me.com>

---------

Co-authored-by: Ansari <thameem.ns@zohocorp.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-07-13 14:21:28 +02:00
Crowdin Bot 44308cbfa0 New Crowdin translations by Github Action 2023-07-13 12:12:23 +00:00
Purushottam Khedre 352a9e2fec fix: incorrect error messsage for invalid login credentials (#10117)
* fir ErrorCode enum

* update incorrect credentails message
2023-07-13 12:09:19 +00:00
Crowdin Bot c1a9f4b656 New Crowdin translations by Github Action 2023-07-13 12:02:12 +00:00
Leo GiovanettiandShivam Kalra 32c6ca0ad5 fix: Creating a default availability for org owner [CAL-2142] (#10108)
* Creating a default availability for org owner

* Full width empty state

---------

Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-13 13:59:12 +02:00
e79b4168f2 fix: Improved recurring event booking speed (#9600)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-07-13 17:28:20 +05:30
Hariom Balhara 54833b65ff fix: Hotfix/consider new conferencing and old video app categories (#10134)
* Consider new conferencing and old video app categories

* Add comment
2023-07-13 11:57:49 +00:00
Rama Krishna Reddyandrkreddy99 26afc077a7 disable guests field when offer seats is enabled (#10132)
Co-authored-by: rkreddy99 <rreddy@e2clouds.com>
2023-07-13 13:57:30 +02:00
Jaideep Guntupalli 0093b6faa5 feat: Added e2e test paid booking should be able to be rescheduled (#10069)
* feat: Added e2e test paid booking should be able to be rescheduled

* refactor: abstracted and simplied the logic

* fix: stripe iframe isnt the first frame

* fix: import expect
2023-07-13 02:47:06 +02:00
Alex van Andel ef49fc4ce1 fix: Remove/Update behaviour of date-overrides
## What does this PR do?

This is not the cleanest approach, but the existing mechanism didn't work in a few situations because of the date sort. The update caused a re-render that caused an invalid unsortedFieldArrayMap.

Reproduction/how to test: 

**Updating bug was hit when you changed the date and it went before another date (re-ordering)**; which then modified the wrong date override. This PR replaces the entire array which retains the correct sort order by brute force.

**Delete bug was encountered if the sorted order was not the same as the filtered order, which depends on the order you created the date overrides in**. This PR implements an original order before the sort causing the right item to be removed.

Fixes #8043
2023-07-12 17:45:30 -07:00
Alex van Andel 605f7d35ec fix: Ensure rendering as UTC to prevent browser tz from messing up times
## What does this PR do?

Fixes all date shifts that are due browser time, by ensuring everything is UTC so it's not transformed to different times.

The times weren't actually shifting, dates were, but after casting these to UTC properly the times were also shifting; so that was a bug after a bugfix, time wise it was only a problem to determine all day events (which didn't completely work)

### Yes, but how do I test this?

Before:
Create a all day date override in +05:30
Shift your local system time to -04:00
Note that the day is different

After - do the same:
The day is the same!

Fixes #8365
Fixes #6978
Fixes #6482
2023-07-12 17:32:11 -07:00
Omar López 9b37d65503 fix: team invite links (#10110) 2023-07-12 14:24:47 -07:00
alannnc a1f9012501 fix: hasTeamPlan with accepted prop (#10109) 2023-07-12 16:40:41 -04:00
Ritik KumarandLeo Giovanetti 008016902e fix: incomplete vercel api error message on org creation (#9875)
* fix incomplete vercel api error message on org creation

* remove specified type from vercelCreateDomain

* have common error message for org domain registration

---------

Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-12 20:29:13 +00:00
Crowdin Bot 9f33d85903 New Crowdin translations by Github Action 2023-07-12 20:22:52 +00:00
alannnc 496b03e92f fix: onboard-pricing-options-self-host (#10105) 2023-07-12 20:20:03 +00:00
Keith Williams 358da42a9b fix: yaml syntax for new contributors action (#10094) 2023-07-12 19:17:40 +00:00
Carina WollendorferandCarinaWolli 8812580f72 fix: slot reservation uses wrong event length (#10103)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-12 12:06:15 -07:00
Alex van Andel 3009f17cb0 fix: Use user locale for sending password resets (#10104) 2023-07-12 12:04:07 -07:00
Crowdin Bot 3328cb2226 New Crowdin translations by Github Action 2023-07-12 17:30:09 +00:00
Alex van Andel 7b57b4bcda fix: Password reset tests & better expiry checking (#10102) 2023-07-12 10:27:41 -07:00
Peer Richelsen 6d413f5721 fix: ternary operator with lenght causing 0 (#10090) 2023-07-12 12:17:30 +00:00
Crowdin Bot c9fc88ddc7 New Crowdin translations by Github Action 2023-07-12 10:17:03 +00:00
6d284ae15c fix: Allow editing guests when rescheduling or editing a booking (#9878)
* Allow editing guests when rescheduling or editing

* lint fix

* modify reschedule logic that assigned originalRescheduledBooking.attendees to the "rescheduled" booking to ensure that guests added or removed are used for the "rescheduled" booking

---------

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-12 15:44:11 +05:30
Peer Richelsen c6791a8d3c fix: unnecessary whitespace in profile (#10071) 2023-07-11 21:26:28 +00:00
Leo Giovanetti aa1370b9c7 Enabling console for staging (#10072) 2023-07-11 21:21:30 +00:00
greatmaxy db27c7e4bc fix: Empty spaces in text field do not trigger inline field error (#10038) 2023-07-11 16:44:10 -04:00
Crowdin Bot 325c6ddc86 New Crowdin translations by Github Action 2023-07-11 20:28:56 +00:00
Udit TakkarandShivam Kalra 9c7428571d feat: added organizer daily video recording email (#9667)
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-11 16:26:30 -04:00
Crowdin Bot 9391bb5b64 New Crowdin translations by Github Action
release.yml / release (push) Canceled after 0s
2023-07-11 15:51:22 +00:00
d58924ecad feat: New workflow action to send Whatsapp message (#8818)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-11 15:48:44 +00:00
Hariom Balhara fdef15712a fix: Org booking pages link fixes for Routing Forms and Event type (#10052)
* Fix links

* Fix team event for org URL

* Fix avatar and user link
2023-07-11 17:46:13 +02:00
Joe Au-Yeung a5b5382306 fix: seats regression [CAL-2041]
## 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. -->

- Passes the proper seats data in the new booker component between states and to the backend

Fixes #9779
Fixes #9749
Fixes #7967 
Fixes #9942 

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

## Type of change

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

- 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 -->

**As the organizer** 
- Create a seated event type
- Book at least 2 seats
- Reschedule the booking
    - All attendees should be moved to the new booking
- Cancel the booking
    - The event should be cancelled for all attendees

**As an attendee**
- [x] Book a seated event
- [x] Reschedule that booking to an empty slot
    - [x] The attendee should be moved to that new slot
- [x] Reschedule onto a booking with occupied seats
    - [x] The attendees should be merged
- [x] On that slot reschedule all attendees to a new slot
    - [x] The former booking should be deleted
- [x] As the attendee cancel the booking
    - [x] Only that attendee should be removed 

## Mandatory Tasks

- [x] 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 -->
2023-07-11 20:41:08 +05:30
Hariom Balhara c2fceb2e0f fix: avatars not loading for org events (#10062) 2023-07-11 14:23:07 +00:00
Crowdin Bot a73065ba5d New Crowdin translations by Github Action 2023-07-11 14:22:36 +00:00
Alex van Andel 7b0f677bdb Revert "fix: TS error in main (#10063)"
This reverts commit da80a2bae8.
2023-07-11 16:19:59 +02:00
Peer Richelsen 4166a0ab02 Update package.json 2023-07-11 15:47:46 +02:00
Hariom BalharaandKeith Williams da80a2bae8 fix: TS error in main (#10063)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-07-11 15:32:52 +02:00
Leo GiovanettiandPeer Richelsen c6fd91d181 fix: 404 links issue (#9928)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-11 13:05:41 +00:00
f62e6d9586 feat: enable crowdin 2-way sync (#9353)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-11 13:01:24 +00:00
Crowdin Bot b03c6d5040 New Crowdin translations by Github Action 2023-07-11 12:58:36 +00:00
Jeroen ReumkensandPeer Richelsen 172d77dcc3 feat: Added back to today button for new booker week and column views (#9684)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-11 12:57:48 +00:00
Syed Ali Shahbaz 1a905aa330 Add team example to POST (#10041) 2023-07-11 13:40:41 +02:00
Alex van Andel a8da4b87f0 Meticulous experiment & audit (#10050) 2023-07-11 13:37:59 +02:00
Crowdin Bot eb679a8e7d New Crowdin translations by Github Action 2023-07-11 11:32:50 +00:00
b364a85ed8 chore: removed old booker and make new booker as a default (#10053)
* removed old booker and make new booker as a default

* fixes merge conflict

* fixed tests

* fixed tests for old-booker

* fixed typo in @calcom/lib/defaultEvents.ts

---------

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-11 13:31:55 +02:00
Omar López a2e0d44fb8 fix: prevents warnings on branding call when unauthed (#10051) 2023-07-11 12:06:44 +02:00
Mehul a9ee5d6f7f fixes: about section unnecessary scrollbar (#10043) 2023-07-11 11:26:43 +02:00
sean-brydonandLeo Giovanetti 6d35750784 feat: Auto accept on org verify (#10037)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-11 10:30:45 +02:00
Anik Dhabal BabuandLeo Giovanetti 4f423ba692 fix: Reject Organization button is not working (#10010)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-11 10:27:53 +02:00
Pradumn Kumar d097c1fada fix: fixes issue with webhook not being triggered for managed events (#10025) 2023-07-11 13:16:24 +05:30
Crowdin Bot fc049e12b3 New Crowdin translations by Github Action 2023-07-11 07:42:07 +00:00
Joe Au-YeungandLeo Giovanetti 267422a1f4 fix: Enable Salesforce & CRMs for teams & orgs [CAL-2116] (#9973)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-11 13:11:21 +05:30
Crowdin Bot 6e15de31aa New Crowdin translations by Github Action 2023-07-10 22:33:08 +00:00
Carina Wollendorfer 2f6b5ced04 fix: better slot starting times
## What does this PR do?

Currently, we start the first slot always at the nearest 15 minutes. This is not ideal as for some duration other slot starting time make more sense. So with this PR the starting times are defined as follow: 

- Frequency is exact hours (60, 120, 180, ...), slot start time is a full hour
- Frequency is half hours (30, 90, ...), slot start time is half or full hours (8:00, 8:30, ...)
- Same with 20-minute events (20, 40, ...) and 10-minute events 
- Everything else will start at the nearest 15 min slot

It also fixes that slot times are shifted when there is a busy slot with a different duration. Here is a before and after of a 30-min event with a 5-minute busy slot at 1:00 pm

Before: 
![Screenshot 2023-07-07 at 13 31 45](https://github.com/calcom/cal.com/assets/30310907/b92d4ff4-49f1-48f4-a973-99266f61d919)

After
![Screenshot 2023-07-07 at 13 34 01](https://github.com/calcom/cal.com/assets/30310907/042c7ef7-8c2a-4cd9-b663-183bc07b5864)


#### 30 Minute events, availability starting at 7:15

Before: 
![Screenshot 2023-07-06 at 12 40 00](https://github.com/calcom/cal.com/assets/30310907/752ed978-83cf-4ee9-a38d-b5795df6daec)

After:
![Screenshot 2023-07-06 at 12 40 42](https://github.com/calcom/cal.com/assets/30310907/5d51ec15-5be8-4f3b-b374-46dad35216b8)

## Type of change
- Bug fix (non-breaking change which fixes an issue)

## How should this be tested?

- Check if slot times are shown as described
- Test with different intervals/durations
- Test with busy times
- Test with different availabilities 

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-07-10 15:32:26 -07:00
Omar López cf57be2aef fix: Calcom UI deployments (#10047) 2023-07-10 19:52:15 +00:00
Hariom Balhara c2996cd2f8 fix: 404 booking pages when org support is enabled locally
## What does this PR do?
Fix 404 booking pages when org support is enabled locally

## Type of change

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

- 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 -->

-  Add localhost:3000 to ALLOWED_HOSTNAMES which is the default recommendation for enabling org support locally.
<img width="1035" alt="Screenshot 2023-07-10 at 11 04 35 AM" src="https://github.com/calcom/cal.com/assets/1780212/7272437b-afae-4073-905b-eafc39f9fafc">
- Simply restart the server and visit http://localhost:3000/pro. It will give 404. After this change it would work

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-07-10 12:19:33 -07:00
Crowdin Bot 79944dfed1 New Crowdin translations by Github Action 2023-07-10 19:14:15 +00:00
sean-brydonandzomars e83647c652 feat: Add password checks for password-reset (#10042)
Co-authored-by: zomars <zomars@me.com>
2023-07-10 12:13:20 -07:00
Crowdin Bot 556bf346f0 New Crowdin translations by Github Action 2023-07-10 14:45:23 +00:00
Leo Giovanetti b98f2045d0 chore: Teams empty screen (#9997) 2023-07-10 10:44:22 -04:00
Eric DantasandPeer Richelsen a13c050e9d chore: Improve info that should be filled out for bugs and features (#9978)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-10 16:17:52 +02:00
Ujwal Kumar e088196c8d fix: vertical-scrollbar-issue-when-organization-table-contains-no-records (#10008) 2023-07-10 16:16:20 +02:00
Leo Giovanetti 9456ffbb48 chore: Credits with commit hash (#9999) 2023-07-10 16:10:30 +02:00
Sai Krishna 6d19cc22a5 fix: cancel button not working on payment page (#10006) 2023-07-10 15:58:38 +02:00
Akshay c69c9d6314 Fix #10021 Changes save button color from secondary to primary. (#10022) 2023-07-10 15:30:00 +02:00
e0ef30eb52 fix: /routing-form/single - Don't clear edits when moving from one tab to the other (#8125)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-07-10 09:24:04 -04:00
Vinoth Kumar VandVinoth Kumar V 045828d4ab fix: location icon (#9994)
Co-authored-by: Vinoth Kumar V <vinoth_kumar_v@Vinoths-MacBook-Pro.local>
2023-07-10 12:57:34 +02:00
Hariom Balhara 354d57f393 Add border in embed always (#10034) 2023-07-10 09:56:41 +00:00
650d0082a3 feat: from org team invite (#9569)
* Initial commit

* Adding feature flag

* Desktop first banner, mobile pending

* Removing dead code and img

* AppInstallButtonBase

* WIP

* Adds Email verification template+translations for organizations (#9202)

* feat: Orgs Schema Changing `scopedMembers` to `orgUsers` (#9209)

* Change scopedMembers to orgMembers

* Change to orgUsers

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Session logic to show org label

* Step 2 done, avatar not working

* List orgs and list teams specific if orgs exist

* Conditionally show org - fix settings layout - add labels for all pages

* Profile Page + update

* Org specific team creation

* appearance page

* Ensure members cant of org cant update settings in UI

* Fix update handler imports

* hide billing on sub teams

* Update profile slug page

* Letting duplicate slugs for teams to support orgs

* Add slug coliisions for org

* Covering null on unique clauses

* Covering null on unique clauses

* Extract to utils

* Update settings to use subdomain path in team url , team + org

* Supporting having the orgId in the session cookie

* Onboarding admins step

* Last step to create teams

* Update handler comments

* Upgrade ORG banner - disabled team banner for child teams

* Handle publishing ORGS

* Fix licenese issue

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

* Split into function calls to make this file more explisit

* Update parents stripe sub not teamID

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Create org membership also - billing portal page

* A11ly

* Hide create team if no valid permisisons

* Get Org members router

* Handle updating subscription if orgId

* Fix double upgrade banner

* Update constants

* Feedback

* Copy change

* Making an org avatar (temp)

* Add slug colission detection for user and team name

* Fix Import

* Remove update password func

* Fix module import over relative

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* Fix import again

* Throw no team found before auth error

* Check if invited found user is already in differnt org

* feat: organization settings general page

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

* feat: add members page

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

* chore: remove

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

* fix: use invalidate

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

* Move to for of loop to throw errors in usenamelist

* fix: delete mutation

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

* fix: remove organization id

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

* chore

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

* Remove app install button sa its in 9337

* Remove i18n key not being used

* feat: Onboarding process to create an organization (#9184)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Making sure we check requestedSlug now

---------

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

* feat: [CAL-1816] Organization subdomain support (#9345)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* Covering users and subteams, excluding non-org users

* Unpublished teams shows correctly

* Create subdomain in Vercel

* feedback

* Renaming Vercel env vars

* Vercel domain check before creation

* Supporting cal-staging.com

* Change to have vercel detect it

* vercel domain check data message error

* Remove check domain

* Making sure we check requestedSlug now

* Feedback and unneeded code

* Reverting unneeded changes

* Unneeded changes

---------

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

* Vercel subdomain creation in PROD only

* Fix router

* fix: use zod schema

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

* feat: organization settings general and members page (#9266)

* feat: organization settings general page

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

* feat: add members page

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

* chore: remove

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

* fix: use invalidate

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

* fix: delete mutation

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

* fix: remove organization id

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

* chore

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

* fix: use zod schema

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

---------

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

* Type fixes

* Use org Stripe product when upgrading

* Removed unused code

* UI completed

* Reverting changes

* Update UsernameTextfield.tsx

* More reverts

* Update next-auth-options.ts

* Update common.json

* Type fixes

* Include invite token for orgs

* Update org schema

* Make token settings optional as it isnt used in orgs yet

* Reverts

* Add correct array logic and move to controlled component

* Fix toggle group default | Update toggle group bg

* Darkmode toggle group

* Distinct user

* Hide modal if no org members

* Extract toggle logic

* Update packages/features/ee/organizations/components/TeamInviteFromOrg.tsx

* remove yarn.lock from commit

* Fix types

* Add getMember router back

* As a query lol

* Fix types

* Fix accepted param defaulting to true as we want both

* Fix list not pulling back people who have not joined the org yet

* Fix tests to handle invite a existing org user to a team

* Fix test

* Fix an error sometimes when existing org user is invited

* Updates radix & fixes bug

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-07-10 15:16:51 +05:30
Crowdin Bot 7c4b7209c2 New Crowdin translations by Github Action 2023-07-07 23:58:52 +00:00
sydwardrae 5736821466 fixed typo string to strong (#10001) 2023-07-07 16:58:01 -07:00
Joe Au-Yeungandzomars 75f76c130a fix: Orgs/create child teams CAL-1986 (#9631)
* Re-applied valid changes

* Update packages/trpc/server/middlewares/sessionMiddleware.ts

* Type fix

* Type fix

---------

Co-authored-by: zomars <zomars@me.com>
2023-07-07 16:48:51 +00:00
Anik Dhabal Babu 936432a730 fix: Back to Bookings button only shows for organizer (#9983) 2023-07-07 11:50:44 -04:00
Hariom Balharaandalannnc 4fda760e5e fix: Organizations/ Fixes related to Routing Form, Booking Pages and Embeds (#9860)
* Fix Team Events not working

* Get embed for org events working

* Fix rewrites

* Update next-config.test.ts

Spacing changes

* Update next-config.test.ts

Scan the file through prettier and update

---------

Co-authored-by: alannnc <alannnc@gmail.com>
2023-07-07 21:20:15 +05:30
Ritik Kumar c467fd41ac fix: hide invite member button from list tile for team members (#9963) 2023-07-07 09:34:57 -04:00
sean-brydon 0969d5960a feat: temp admin user management
## What does this PR do?
Loom: https://www.loom.com/share/361ca6ca9b5748079cba59ed2f348d90

Not all items on edit from update the user. This is very temp page to help Milos

How to test:
Login as admin user
settings > admin > users
search - see filtered users
scroll to bottom of list - notice we refetch before you hit the bottom so you dont notice items loading
delete user -> user is deleted and removed from list (radix bug in main causes dialog to freeze refresh to make this go away)
edit user -> Edit users email/username/identityprovider. Not all items on this update work just yet. These will be picked up when we come to implement the proper user table.
2023-07-07 18:25:21 +05:30
Alex van Andel cbe1907118 Only enable when defaultScheduleId is truthy (#9968) 2023-07-06 17:06:37 -07:00
Alex van Andel afd41475c9 fix: update should be based on email only (#9977) 2023-07-06 23:55:00 +00:00
Peer Richelsen f07c0b09fa fix: fixed layout shift in booker (#9976)
release.yml / release (push) Canceled after 0s
2023-07-06 22:52:55 +00:00
d0707422b9 fix: Credential of type video wrong id on bookingReference (#9281)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-07-06 21:39:33 +00:00
Peer Richelsen cc0c686e65 Update pr-assign-team-label.yml 2023-07-06 22:05:45 +02:00
Crowdin Bot 7057da7327 New Crowdin translations by Github Action 2023-07-06 18:02:04 +00:00
Alex van Andel fbb27ceab0 Default to VERCEL_URL (#9969) 2023-07-06 20:01:12 +02:00
Crowdin Bot 01a5d588f3 New Crowdin translations by Github Action 2023-07-06 16:49:37 +00:00
5003ada671 feat: Enable Apps for Teams & Orgs [CAL-1782] (#9337)
* Initial commit

* Adding feature flag

* Add schema relation for teams and credentials

* feat: Orgs Schema Changing `scopedMembers` to `orgUsers` (#9209)

* Change scopedMembers to orgMembers

* Change to orgUsers

* Create getUserAdminTeams function & tRPC endpoint

* Get user admin teams on app store page

* Create UserAdminTeams type

* Add user query to getUserAdminTeams

* Letting duplicate slugs for teams to support orgs

* Covering null on unique clauses

* Add dropdown to install button on app store

* Supporting having the orgId in the session cookie

* On app page, only show dropdown if there are teams

* Add teamId to OAuth state

* Create team credential for OAuth flow

* Create team credential for GCal

* Add create user or team credential for Stripe

* Create webex credentials for users or teams

* Fix type error on useAddAppMutation

* Hubspot create credential on user or team

* Zoho create create credential for user or team

* Zoom create credentials on user or team

* Salesforce create credential on user or teams

* OAuth create credentials for user or teams

* Revert Outlook changes

* Revert GCal changes

* Default app instal, create credential on user or team

* Add teamId to credential creation

* Disable installing for teams for calendars

* Include teams when querying installed apps

* Render team credentials on installed page

* Uninstall team apps

* Type fix on app card

* Add input to include user in teams query

* Add dropdown to install app page  for user or team

* Type fixes on category page

* Install app from eventType page to user or team

* Render user and team apps on event type app page

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* Render user and team apps on event type app page

* Add credentialOwner to eventTypeAppCard types

* Type fixes

* Create hook to check if app is enabled

* Clean up console.logs

* Fix useIsAppEnabled by returning not an array

* Convert event type apps to useIsAppEnabled

* Abstract credential owner type

* Remove console.logs

* On installed app page, show apps if only team credential is installed

* Clean up commented lines

* Handle installing app to just an team event from event type page

* Fix early return when creating team app credential

* Zoom add state to callback

* Get team location credentials and save credential id to location

* feat: Onboarding process to create an organization (#9184)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Making sure we check requestedSlug now

---------

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

* Type fix

* Grab team location credentials

* Add isInstalled to eventType apps query

* feat: [CAL-1816] Organization subdomain support (#9345)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* Covering users and subteams, excluding non-org users

* Unpublished teams shows correctly

* Create subdomain in Vercel

* feedback

* Renaming Vercel env vars

* Vercel domain check before creation

* Supporting cal-staging.com

* Change to have vercel detect it

* vercel domain check data message error

* Remove check domain

* Making sure we check requestedSlug now

* Feedback and unneeded code

* Reverting unneeded changes

* Unneeded changes

---------

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

* Vercel subdomain creation in PROD only

* Enable payment apps for team credentials

* Fix for team-user apps for event types

* Fix layout and add teamId to app card

* Disable apps on managed event types

* Add managed event type fields to event type apps

* Include organizations in query

* Change createAppCredential to createOAuthAppCredential

* Show app installed on teams

* Making sure we let localhost still work

* UI show installed for which team

* Type fixes

* For team events move use host location to top

* Add around to appStore

* New team event types organizer default conf app

* Fix app card bug

* Clean up

* Search for teamId or userId when deleting credential

* Type fixes

* Type fixes

* Type fixes

* Type fixes

* Address feedback

* Feedback

* Type check fixes

* feat: Organization branding in side menu (#9279)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

* Making sure we show the set up profile on org only

* Profile username availability rely on org hook

* Update apps/web/pages/team/[slug].tsx

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

* Update apps/web/pages/team/[slug].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>

* feat: Organization support for event types page (#9449)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

* Processing orgs and children as profile options

* Reverting change not belonging to this PR

* Making sure we show the set up profile on org only

* Removing console.log

* Comparing memberships to choose the highest one

---------

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

* Type errors

* Refactor and type fixes

* Update orgDomains.ts

* Feedback

* Reverting

* NIT

* Address feedback

* fix issue getting org slug from domain

* Improving orgDomains util

* Host comes with port

* Update useRouterQuery.ts

* Fix app card bug

* Fix schema

* Type fixes

* Revert changes to location apps

* Remove console.log

* Fix app store test

* Handle install app dropdown

* Add CalendarApp to `getCalendar`

* Add PaymentApp type fix

* Payment type fix

* Type fixes

* Match with main

* Change type to account for team

* Fix app count for team events

* Type fixes

* More type fixes

* Type fix?

* Fix the type fix

* Remove UserAdminTeams empty array union

* Type fix

* Type fix

* Type fix

* Uses type predicates

* Use teamId. Fixes installation for teams after user installation

* Fix Team Events not working

* Get embed for org events working

* Fix rewrites

* Address feedback

* Type fix

* Fixes

* Add useAppContextWithSchema in useIsAppEnabled

* Type fix for apps using useIsAppEnabled

* Integrations.handler change credentialIds to userCredentialIds

* Remove apps endpoint

* Add LockedIcon and disabled props to event type app context

* Type fixes

* Type fix

* Type fixes

* Show team installed apps for members

* Type fix

* Reverting findFirst

* Revert findFirst

* Avoid a possible 500

* Fix missing tanslation

* Avoid possible 500

* Undo default app for teams

* Type fix

* Fix test

* Update package.json

* feat: Fix invite bug - added tests (#9945)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* chore: Button Component Tidy up (#9888)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* feat: Make Team Private

## What does this PR do?

Fixes https://github.com/calcom/cal.com/issues/8974

1) When user is admin

<img width="1440" alt="Screenshot 2023-07-03 at 6 45 50 PM" src="https://github.com/calcom/cal.com/assets/53316345/ce15158f-d278-4f1a-ba2e-8b63e4274793">

2) When user is not admin and team is private

<img width="1440" alt="Screenshot 2023-07-03 at 6 47 15 PM" src="https://github.com/calcom/cal.com/assets/53316345/ce23560e-690a-4c42-a76d-49691260aa4d">

3) 
<img width="1440" alt="Screenshot 2023-07-03 at 6 51 56 PM" src="https://github.com/calcom/cal.com/assets/53316345/13af38f8-5618-4dae-b359-b24dc91e4eb4">


## Type of change

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

- New feature (non-breaking change which adds functionality)

## How should this be tested?

1) go to Team members page and turn on switch Make Team Private.

Now after making the team private only admin would be able to see all the members list in the settings. There will not be a button to Book a team member instead on the team page like before.


## Mandatory Tasks

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-07-06 16:48:39 +00:00
Crowdin Bot 54374cf43e New Crowdin translations by Github Action 2023-07-06 16:37:13 +00:00
Peer RichelsenandLeo Giovanetti 55bca88d9b chore: Create pr-assign-team-label.yml (#9959)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-06 16:36:20 +00:00
Udit Takkar f755312ed7 feat: Make Team Private
## What does this PR do?

Fixes https://github.com/calcom/cal.com/issues/8974

1) When user is admin

<img width="1440" alt="Screenshot 2023-07-03 at 6 45 50 PM" src="https://github.com/calcom/cal.com/assets/53316345/ce15158f-d278-4f1a-ba2e-8b63e4274793">

2) When user is not admin and team is private

<img width="1440" alt="Screenshot 2023-07-03 at 6 47 15 PM" src="https://github.com/calcom/cal.com/assets/53316345/ce23560e-690a-4c42-a76d-49691260aa4d">

3) 
<img width="1440" alt="Screenshot 2023-07-03 at 6 51 56 PM" src="https://github.com/calcom/cal.com/assets/53316345/13af38f8-5618-4dae-b359-b24dc91e4eb4">


## Type of change

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

- New feature (non-breaking change which adds functionality)

## How should this be tested?

1) go to Team members page and turn on switch Make Team Private.

Now after making the team private only admin would be able to see all the members list in the settings. There will not be a button to Book a team member instead on the team page like before.


## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-07-06 11:55:12 +02:00
sean-brydonandPeer Richelsen 207e0aac1f chore: Button Component Tidy up (#9888)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-06 09:52:47 +00:00
sean-brydonandHariom Balhara c8f7b36307 feat: Fix invite bug - added tests (#9945)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-07-06 15:03:31 +05:30
Peer Richelsen b5f0f0c0de Update package.json 2023-07-06 11:14:30 +02:00
alannnc 816bfdd8f7 fix/premium-username-callback (#9938) 2023-07-05 18:19:42 +00:00
Crowdin Bot 499b433d4d New Crowdin translations by Github Action 2023-07-05 16:48:28 +00:00
7b1fbd2853 feat: date range overhaul (#9802)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-05 09:47:41 -07:00
Crowdin Bot 9c2e15f016 New Crowdin translations by Github Action 2023-07-05 13:13:57 +00:00
Bailey Pumfleet 4ac2c52c4e Add missing strings (#9943) 2023-07-05 15:12:45 +02:00
Crowdin Bot 4a4cc7c766 New Crowdin translations by Github Action
release.yml / release (push) Canceled after 0s
2023-07-05 03:26:36 +00:00
Crowdin Bot 648886f630 New Crowdin translations by Github Action 2023-07-05 03:25:40 +00:00
Udit TakkarandLeo Giovanetti 8d7b2cfe51 feat: booking filters [CAL-1801] (#9692)
* feat: booking filters

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

* fix: responsive issues

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

* fix: only show accepted members in filter

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

* chore: translations

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

* fix: remove organizer default

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

* chore

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

* fix: remove locationValues

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

* fix: remove unused and mobile

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-07-05 08:54:42 +05:30
jemiluv8 bfc2b8df34 fix: Exclude the possibility to book events with invalid email addresses (#9898)
* Introduce email refinement based on the updated email regex in newer zod versions at https://github.com/colinhacks/zod/pull/2157/files#diff-c54113cf61ec99691748a3890bfbeb00e10efb3f0a76f03a0fd9ec49072e410a

* fix import bug

* fix lint issue

* update emailSchemaRefinement comment to reflect the state of play for the zod fix
2023-07-05 06:07:52 +05:30
GitStart-Cal.comandgitstart-calcom a36f2aa871 fix: Spacing issues on "settings" Button navbar skeleton (fix-settings-skeleton) (#9663)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-07-04 23:41:29 +00:00
Crowdin Bot 6d8a576f60 New Crowdin translations by Github Action 2023-07-04 22:55:54 +00:00
Peer RichelsenandAlex van Andel b5e54d6cf9 fix: added brand hover for dates (#9936)
* added brand hover for dates

* Update packages/features/bookings/components/AvailableTimes.tsx

just removed trailing space

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-07-04 22:55:12 +00:00
sean-brydonandAlex van Andel ea20fa9f35 Adds LicenseRequired (#9883)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-07-04 22:12:51 +00:00
Crowdin Bot c3b2a52815 New Crowdin translations by Github Action 2023-07-04 22:12:03 +00:00
6bd97065e6 chore: replace slack with discord (#9900)
* chore: replace slack with discord

* fix: add slack again to avoid braking code

* chore: update readme.md

* chore: update 404

* Update packages/lib/constants.ts

* chore: add discord svg and update 404

* chore: delete svg and shift to code

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-07-04 22:11:10 +00:00
Pradumn Kumar 878a7cbfc5 fix: app crash issue on setting a greater recurring count than the maximum (#9899) 2023-07-05 03:21:41 +05:30
Omar López 9c84c3db0a fix: API /teams/:id/publish (#9904)
Fixes API build failure introduced after #9291
2023-07-05 03:19:20 +05:30
Anik Dhabal Babu 1cf8ef8155 fix: Enhance the AnimatedPopover (#9917) 2023-07-04 22:08:39 +02:00
Leo Giovanetti 14aa0a2f2d fix: Few minor UI issues in Orgs (#9919) 2023-07-04 22:07:45 +02:00
Alex van Andel 5d73095060 v3.0.12
release.yml / release (push) Canceled after 0s
2023-07-04 15:23:40 -04:00
Hariom Balhara 08683a4883 fix: Missing Video Apps (#9927) 2023-07-04 19:10:15 +00:00
Pradumn Kumar 2f3cc03255 feat: added missing skeletons for sidebar (#9909) 2023-07-04 19:37:34 +02:00
Crowdin Bot b5fd33d689 New Crowdin translations by Github Action 2023-07-04 16:37:51 +00:00
Alex van Andel 6799e5f214 fix: Vercel autoconverts + to a ' ' character. (#9920) 2023-07-04 12:37:04 -04:00
Peer Richelsen 63e93fb12c Update README.md 2023-07-04 13:06:24 +02:00
Peer Richelsen 94289a84f1 Update README.md 2023-07-04 13:03:50 +02:00
sean-brydon 0f332cc35e fix: Auto scroll on selection of date selection (#9907) 2023-07-04 12:44:13 +02:00
Peer Richelsen 504d900d05 Update package.json
release.yml / release (push) Canceled after 0s
2023-07-04 12:30:43 +02:00
sean-brydon 7ebcc08182 fix: Fix empty screen being left alligned (#9908) 2023-07-04 12:29:15 +02:00
Alex van Andel b137c87634 fix: toLowerCase/slugify of booking page params (#9903)
* fix: toLowerCase/slugify of booking page params

* Ensure support for dynamic events
2023-07-03 23:22:27 -04:00
Peer Richelsen fefb297c47 readme/remove-heroku (#9894) 2023-07-03 18:29:29 +02:00
Afrie Irham c412e7f815 fix: URL case sensitive bug (#9886) 2023-07-03 13:43:22 +05:30
+2 cc99e1697b fix: Creating team and preview URL fixes [CAL-1997] [CAL-1998] (#9648)
* Creating team and preview URL fixes

* Renamed function in tests too

* fix: Signup from org wizard invitation and other tweaks [CAL-1973] [CAL-1993] [CAL-1994] (#9693)

* Fixing signup from org invitation and other tweaks

* feat:  Organizations no middleware rewrite (#9548)

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Revert "fix: redirect to 404 page for invalid email verification token" (#9703)

* New Crowdin translations by Github Action

* refactor: Skip license checks for development (#9375)

* Allow closing the embed on clicking the black transparent area (#9706)

* New Crowdin translations by Github Action

* perf: [CAL-1928] Deduplicate eventTypes.getByViewer query (#9700)

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* perf: Remove useless data on EvenTypes getByViewer endpoint (#9704)

* feat: [CAL-910] Pre-select country for phone number (CALCOM-5759) (#9526)

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>

* Fix settings layout + org headings (#9712)

* Simplifying prop

* Removing restriction to choose username

* Formatting

---------

Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Afzal Sayed <14029371+afzalsayed96@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@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: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Fix conditional before hook

* Fix lints

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Afzal Sayed <14029371+afzalsayed96@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: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-07-03 08:06:12 +00:00
Alex van Andel ad4d71d8c0 v3.0.10
release.yml / release (push) Canceled after 0s
2023-07-03 02:05:11 -04:00
Crowdin Bot b3226dc308 New Crowdin translations by Github Action 2023-07-03 05:44:04 +00:00
Joe Au-Yeung 9ef8903309 Transform URLs to array (#9890) 2023-07-03 01:43:23 -04:00
Crowdin Bot c46c9bfe90 New Crowdin translations by Github Action 2023-07-02 18:41:05 +00:00
583571247c feat: org invite billing (#9291)
* Initial commit

* Adding feature flag

* Desktop first banner, mobile pending

* Removing dead code and img

* AppInstallButtonBase

* WIP

* Adds Email verification template+translations for organizations (#9202)

* feat: Orgs Schema Changing `scopedMembers` to `orgUsers` (#9209)

* Change scopedMembers to orgMembers

* Change to orgUsers

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Session logic to show org label

* Step 2 done, avatar not working

* List orgs and list teams specific if orgs exist

* Conditionally show org - fix settings layout - add labels for all pages

* Profile Page + update

* Org specific team creation

* appearance page

* Ensure members cant of org cant update settings in UI

* Fix update handler imports

* hide billing on sub teams

* Update profile slug page

* Letting duplicate slugs for teams to support orgs

* Add slug coliisions for org

* Covering null on unique clauses

* Covering null on unique clauses

* Extract to utils

* Update settings to use subdomain path in team url , team + org

* Supporting having the orgId in the session cookie

* Onboarding admins step

* Last step to create teams

* Update handler comments

* Upgrade ORG banner - disabled team banner for child teams

* Handle publishing ORGS

* Fix licenese issue

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

* Split into function calls to make this file more explisit

* Update parents stripe sub not teamID

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Create org membership also - billing portal page

* A11ly

* Hide create team if no valid permisisons

* Get Org members router

* Handle updating subscription if orgId

* Fix double upgrade banner

* Update constants

* Feedback

* Copy change

* Making an org avatar (temp)

* Add slug colission detection for user and team name

* Fix Import

* Remove update password func

* Fix module import over relative

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* Fix import again

* Throw no team found before auth error

* Check if invited found user is already in differnt org

* Move to for of loop to throw errors in usenamelist

* Remove app install button sa its in 9337

* Remove i18n key not being used

* feat: Onboarding process to create an organization (#9184)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Making sure we check requestedSlug now

---------

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

* feat: [CAL-1816] Organization subdomain support (#9345)

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

* Adds Email verification template+translations for organizations (#9202)

* First step done

* Merge branch 'feat/organizations-onboarding' of github.com:calcom/cal.com into feat/organizations-onboarding

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* Covering users and subteams, excluding non-org users

* Unpublished teams shows correctly

* Create subdomain in Vercel

* feedback

* Renaming Vercel env vars

* Vercel domain check before creation

* Supporting cal-staging.com

* Change to have vercel detect it

* vercel domain check data message error

* Remove check domain

* Making sure we check requestedSlug now

* Feedback and unneeded code

* Reverting unneeded changes

* Unneeded changes

---------

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

* Vercel subdomain creation in PROD only

* Fix router

* feat: organization settings general and members page (#9266)

* feat: organization settings general page

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

* feat: add members page

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

* chore: remove

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

* fix: use invalidate

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

* fix: delete mutation

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

* fix: remove organization id

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

* chore

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

* fix: use zod schema

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

---------

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

* Type fixes

* Use org Stripe product when upgrading

* Removed unused code

* Reverting changes

* Update UsernameTextfield.tsx

* More reverts

* Update next-auth-options.ts

* Update common.json

* Type fixes

* Include invite token for orgs

* Update org schema

* Make token settings optional as it isnt used in orgs yet

* Reverts

* remove yarn.lock from commit

* Fix types

* feat: orgs unverified (#9415)

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* Fix wrong banner being displayed

* Type fix

* Fix type issues

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

Co-authored-by: alannnc <alannnc@gmail.com>

* fix missing input on trpc query

* Fix for parentId value for createProvisionalMembership

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-07-02 11:40:12 -07:00
Crowdin Bot 05655a92ca New Crowdin translations by Github Action 2023-07-02 10:13:36 +00:00
Ciarán Hanrahan 6fb51db6ac chore: small changes to dropdown & main sidebar (#9719) 2023-07-02 10:12:50 +00:00
Crowdin Bot 407b0518b3 New Crowdin translations by Github Action 2023-07-01 21:48:31 +00:00
Ujwal KumarandPeer Richelsen 8436025ff5 fix: alignment of icon and title in ConfirmationDialogContent (#9868)
* Fix alignment of icon and title in ConfirmationDialogContent

* Update packages/ui/components/dialog/ConfirmationDialogContent.tsx

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
2023-07-01 21:47:57 +00:00
firefox341andPeer Richelsen 4bea5a09c6 feat: verificationTokenComponent (#9476)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-07-01 18:40:19 +00:00
Carina WollendorferandCarinaWolli 9b25f81b61 fix: add missing translation for organizer_name_info (#9867)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-07-01 20:26:19 +02:00
Keith Williams 0c3ca5c267 chore: Make reading env file cache strict (#9863) 2023-07-01 19:42:40 +02:00
2f85b8ad6b fix: phone number disappearing issue (#9811)
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-07-01 19:42:18 +02:00
Crowdin Bot 480bd48680 New Crowdin translations by Github Action 2023-07-01 17:19:16 +00:00
0e022b013c fix: Stripe integration (#9462)
* Add Stripe integration tests

* Add Stripe payment feature

* Update Stripe payment form with valid card info

* Fill postal code if visible in Stripe test

* Implementing new e2e test

* lint fix

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-07-01 19:18:27 +02:00
Ujwal Kumar 70b8a38b95 fix: modal width while removing installed apps (#9865) 2023-06-30 15:56:06 -04:00
Crowdin Bot 94eae71675 New Crowdin translations by Github Action 2023-06-30 19:50:43 +00:00
Crowdin Bot 6f595b16ba New Crowdin translations by Github Action 2023-06-30 19:49:47 +00:00
Carina WollendorferandCarinaWolli 0871e29f69 fix: missing translations for workflow variables (#9862)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-30 21:48:51 +02:00
Crowdin Bot 513a01fc74 New Crowdin translations by Github Action 2023-06-30 18:48:11 +00:00
Crowdin Bot b35532c07f New Crowdin translations by Github Action 2023-06-30 18:47:28 +00:00
Anmol Mahajan 2ae197b66d fix: allow backtracking while adding new time slots in availability (#9789) 2023-06-30 18:46:38 +00:00
Ujwal Kumar 647ba8c19c fix: message for mandatory name field on profile (#9861) 2023-06-30 13:25:48 -04:00
sean-brydonandalannnc e5ca77eb93 Adds auto increment ID (#9848)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-30 11:32:35 -04:00
Crowdin Bot 44191e79a1 New Crowdin translations by Github Action 2023-06-30 14:59:01 +00:00
Hariom Balhara be40e4b4b0 fix: Legacy Routing Forms handling - Allow team Event in existing non-team Routing forms (#9852) 2023-06-30 10:58:14 -04:00
Crowdin Bot 000f39b9c8 New Crowdin translations by Github Action 2023-06-30 04:08:23 +00:00
Crowdin Bot 4be693341b New Crowdin translations by Github Action 2023-06-30 04:07:25 +00:00
Sagar Singh NegiandAlan 6a96531da5 fix: [CAL-1959] Removed duplicate value warnings on starting dev server(#1959) (#9596)
Co-authored-by: Alan <alannnc@gmail.com>
2023-06-30 04:06:01 +00:00
Crowdin Bot 8a6b37104f New Crowdin translations by Github Action 2023-06-29 17:04:52 +00:00
d180e14013 feat: insights orgs [CAL-1888] (#9551)
* initial commit

* fixes on data returned

* mobile style fixes

* Update on styles and dates not being shown on graphics

* feat: update insights filters UI (#9756)

* feat: update insights filters UI

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

* Fixes for filters with new UI

* fix initialConfig lost while clearing filters

* Add single user conditional for first render

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Alan <alannnc@gmail.com>

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-29 19:03:44 +02:00
Crowdin Bot 44e5002369 New Crowdin translations by Github Action 2023-06-29 08:32:43 +00:00
f1aed2d8b7 fix: api date picker to drop down (#9810)
* fix: api date picker to drop down

* fix: api key , date picker to drop down

* Update packages/features/ee/api-keys/components/ApiKeyDialogForm.tsx

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-29 08:31:39 +00:00
Crowdin Bot b0e81e6b7b New Crowdin translations by Github Action 2023-06-29 08:04:40 +00:00
Carles MitjansandCarles Mitjans 8bbc05b1e5 fix: add floating button tests (#9501)
Co-authored-by: Carles Mitjans <cmitjans@DYX3773MX7.local>
2023-06-29 13:33:34 +05:30
Crowdin Bot 19f76e86d9 New Crowdin translations by Github Action 2023-06-29 07:48:06 +00:00
GitStart-Cal.com e731c33235 feat: Add divider and set spacing on modals with the type=creation prop (feat-add-divider)
## 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. -->

- Add the prop customDividerClassName to customize the CSS Divider if needed
- Add a divider on all modals with type="creation" prop, following the [figma](https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/1.-Cal-DS?type=design&node-id=29898%3A100590&t=62LvZCZAEZm5zDdw-1) design
- Some type="creation" modals seem not to be this type, we pushed all changes with screenshots, if you want us to revert the changes on some modals we will revert

![image](https://github.com/calcom/cal.com/assets/121884634/6674016e-819d-49da-9d44-14eefb4160ee)


- Inline Embed: 
![image](https://github.com/calcom/cal.com/assets/121884634/191c94ea-257f-44ba-8c7a-3815550e36f1)

- Enable Two-Factor Authentication:
![image](https://github.com/calcom/cal.com/assets/121884634/3b9246be-52ab-4c9d-af12-eda82541e837)

-Enable Two-Factor Authentication (step 2):
![image](https://github.com/calcom/cal.com/assets/121884634/a3b7934b-fd66-441d-b9d7-c5ba2fc7fa91)

-Enable Two-Factor Authentication (step 3):
![image](https://github.com/calcom/cal.com/assets/121884634/77367ae7-0967-497a-858a-f18a91b62077)

- Disable Two-Factor Authentication:
![image](https://github.com/calcom/cal.com/assets/121884634/8fc31cc2-e3ca-4c74-8ac6-c4832f9116d9)

- Delete Account:
![image](https://github.com/calcom/cal.com/assets/121884634/5146f605-523e-4c49-aeeb-7c5dd533844d)

- Update Timezone:
![image](https://github.com/calcom/cal.com/assets/121884634/f5acb9f0-2550-4d48-8c02-5cf1b94aaf33)

- Duplicate Event Type:
![image](https://github.com/calcom/cal.com/assets/121884634/078d40a5-5e35-4d4a-8d0e-77d7b8c21d6d)

- Add a New Form:
![image](https://github.com/calcom/cal.com/assets/121884634/5b6352e0-a1c1-46e3-af18-dc84e83fdecd)

- Change Team Member Role:
![image](https://github.com/calcom/cal.com/assets/121884634/dc8b8ba1-7986-4980-ab78-6027aca350ca)

- Impersonate:
![image](https://github.com/calcom/cal.com/assets/121884634/ffd28b83-0c9b-47a1-8894-29d35add9c27)

- Bulk Update Event Types:
![image](https://github.com/calcom/cal.com/assets/121884634/ba864dcc-0c73-426e-bc49-3ac81a55d3ea)

- Connecting with MS Teams:
![image](https://github.com/calcom/cal.com/assets/121884634/d6666cbf-cc36-4fe5-90bb-9d69de26248d)

- Set a Default App Link:
![image](https://github.com/calcom/cal.com/assets/121884634/f4f7093c-839c-4ae1-a4f3-ecd266238115)

- OIDC configuration:
![image](https://github.com/calcom/cal.com/assets/121884634/a689acd7-2488-423e-a57d-5610d683d11d)

- SAML Configuration:
![image](https://github.com/calcom/cal.com/assets/121884634/61480037-1733-4ba9-8dfd-429819e7eac9)

- How to use booking questions as variables?
![image](https://github.com/calcom/cal.com/assets/121884634/2982ace4-41b0-4849-878b-b2ceabf59469)
![image](https://github.com/calcom/cal.com/assets/121884634/65888a13-d9a7-4df5-a8b7-56becc61882e)

- Invite Link Settings:
![image](https://github.com/calcom/cal.com/assets/121884634/3f81fcd1-f862-4106-a79c-70808090f055)

- Add Action:
![image](https://github.com/calcom/cal.com/assets/121884634/ec5f9490-dccc-4c8e-bc8e-b48f8a29144b)

- Edit Keys:
![245192220-c669e6fe-01ba-4a66-9b03-96ab4e8f1809](https://github.com/calcom/cal.com/assets/121884634/bfff69b3-831a-4a5c-9835-0569109b8386)

- Create an Api Key:
![image](https://github.com/calcom/cal.com/assets/121884634/fbdf7494-6da4-49da-ba41-5f9a6393bb72)

- API key created successfully:
![image](https://github.com/calcom/cal.com/assets/121884634/81e6a5c2-5d9d-40ad-a78e-ed1ab2128a26)

- Invite Team Member:
![image](https://github.com/calcom/cal.com/assets/121884634/9e4ceea9-f708-4958-a6d7-e00304beacbf)

- Add a Question:
![image](https://github.com/calcom/cal.com/assets/121884634/8329c593-8203-484e-a4dd-c9bd5210d8db)

- Add a new event type:
![image](https://github.com/calcom/cal.com/assets/121884634/19a72734-aa79-4828-81bb-f534d46a5fc5)
2023-06-29 08:47:16 +01:00
5059723ad1 Apply suggestions from code review (#9839)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-06-29 00:09:03 +02:00
Crowdin Bot 611e109c28 New Crowdin translations by Github Action 2023-06-28 19:44:32 +00:00
Nafees NazikandOmar López 3b1cca2505 perf: improve event types single page loading speed (#9792)
Co-authored-by: Omar López <zomars@me.com>
2023-06-28 19:43:44 +00:00
Crowdin Bot 759af31838 New Crowdin translations by Github Action 2023-06-28 17:06:55 +00:00
D. DomigandPeer Richelsen 1e6b4861d9 Fix markdownlint issues (#9497)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-28 19:06:00 +02:00
Crowdin Bot 617cf9c538 New Crowdin translations by Github Action 2023-06-28 16:24:55 +00:00
171827f547 chore: recategorize apps (#9306)
* Remove unused code in InstalledAppsLayout

* Add new app categories "crm", "conferencing" and "messaging"

* Sort getAppCategories entries alphabetically

* Fix 404s on new category pages (and remove hardcoded category lists)

* Fix admin apps list not showing "no available apps" for new categories

* Recategorise apps

* Sync seed-app-store categories with config files

* Replace unnecessary seed-app-store.config.json with appStoreMetadata

* Copy video.svg to conferencing.svg

* Add messaging.svg

* Remove web3 from getAppCategories (used by installed apps, admin apps)

* Fix app-store-cli categories

- Add conferencing
- Add CRM
- Remove video
- Remove web3

* Remove outdated web3 comment in seed-app-store

* Update apps/web/public/static/locales/en/common.json

* Add cron script to keep db apps in sync with app metadata

* Add redirect for app category "video" to "conferencing"

* Fix up "video" category overrides to apply to conferencing

* Fix conferencing apps not showing as a location for non-team users

* Restore "installed_app" string for conferencing apps

* Make linter happier

* Remove my "installed_app_conferencing_description" as this was fixed upstream

* Quick tidy up

* Add dry-run to syncAppMeta via  CRON_ENABLE_APP_SYNC env

* Replace console.log with logger in syncAppMeta

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-06-28 18:22:51 +02:00
Chaithanya ReddyandChay Reddy 8cfb9902ff fixed theme in API key expiration time calender (#9777)
Co-authored-by: Chay Reddy <chay@Chays-MacBook-Pro.local>
2023-06-28 18:22:26 +02:00
79779e5166 fix: Toast is removed using onClick (#9832)
* toaster is removed using onclick

* Apply suggestions from code review

* Update showToast.tsx

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-28 18:21:53 +02:00
Ujwal Kumar 6a975194bb fix: title-for-team-events (#9828) 2023-06-28 10:47:28 -04:00
Peer Richelsen a9f74b68f5 Update README.md 2023-06-28 16:09:04 +02:00
Hariom Balhara 15692374c6 fix: Embed Issues: Logo causing scroll, Logo text color, mobile view with layout configuration (#9826)
* Fixes scroll and logo text color

* Add a month_view scenario

* Fix false alarm in logs when namespace is empty string

* In mobile view, we only support mobile layout

* Check for embed mode
2023-06-28 15:45:39 +02:00
Crowdin Bot 723b754703 New Crowdin translations by Github Action 2023-06-28 13:43:54 +00:00
ParthibandPeer Richelsen bddec9d1de Update CONTRIBUTING.md (#9814)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-28 15:43:18 +02:00
Efraín Rochín 42494c7c2b fix: [CAL-2005] Issue with loading skeleton on new side menu (#9829)
* remove mx-auto from the Icon in bottom navigation link

* add loading skeleton for the profile links
2023-06-28 15:12:33 +02:00
Keith Williams 852d5b3954 chore: increase operations-per-run for stale issues job (#9833) 2023-06-28 15:10:03 +02:00
Crowdin Bot 5f718bd9f7 New Crowdin translations by Github Action 2023-06-28 11:55:44 +00:00
Keith Williams b9ced0ecf8 v3.0.9
release.yml / release (push) Canceled after 0s
2023-06-28 13:54:45 +02:00
Efraín Rochín 04ed36cfcf fix: Slot reservation in new booker (#9769)
## What does this PR do?

Bring back the slot reservation sistem to the new booker

## Type of change

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

- 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 -->

### Seated event
- [x] Open the new booker in an event with seats 
- [x] Select an available slot
- [x] Open the same event and date in other browser, the seat should be already reserved.
- [x] In the first browser come back to the slots views or close the booking form
- [x] In the second browser reload the booking page, the reserved slot should be realeased. 

### No seated event
- [x] Open the new booker
- [x] Select an available slot
- [x] Open the same event and date in other browser, the seat should be disabled


## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2023-06-27 16:34:15 -07:00
Leo Giovanettiandzomars 467cacb8da fix: Make sure we get slug to decide things (#9727)
Co-authored-by: zomars <zomars@me.com>
2023-06-27 20:45:18 +00:00
b799fdcb7e chore: Subdomain blacklist when creating org (#9728)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
Co-authored-by: Omar López <zomars@me.com>
2023-06-27 20:32:19 +00:00
Crowdin Bot d8017e4d2d New Crowdin translations by Github Action 2023-06-27 19:02:20 +00:00
Hariom Balhara 8384c3f9ff test: Hotfix/Orgs rewrite causing 404 with 3 or more dots domains (#9803)
* Fix RegExp

* Used ORGANIZATIONS_ENABLED to opt-out of orgs behaviour

* Add comments

* Add a new testcase

* Add response headers for easier debugging and Checkly tests in production
2023-06-27 12:01:30 -07:00
Keith Williams 66d47974cb v3.0.8
release.yml / release (push) Canceled after 0s
2023-06-27 14:13:40 +02:00
Hariom Balhara 3556dda252 fix: Forever increasing scroll height in embed in month_view on certain screen size. (#9820) 2023-06-27 09:56:40 +00:00
zomars c2e91b8378 feat: custom auth preparations 2023-06-26 20:45:50 -07:00
Keith WilliamsandEfraín Rochín 4fe69b5864 chore: Removing the calendar-cache because of perf issues (#9808)
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-27 00:32:05 +02:00
zomars 95ce3b215d Update turbo.json 2023-06-26 15:28:41 -07:00
sean-brydonandOmar López 007f3319a0 feat: ratelimit - ratelimit refactor + tests (#9744)
Co-authored-by: Omar López <zomars@me.com>
2023-06-26 19:44:58 +00:00
9441da9d4e fix: custom meeting link overflowing (#9804)
release.yml / release (push) Canceled after 0s
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-26 19:35:55 +00:00
Pradumn Kumar 1510740cb2 fix: adds triggerEvent attribute to meeting ended webhook (#9806) 2023-06-26 14:35:32 -04:00
Keith Williams 8da68cc1fe chore: Added test SendGrid env variables (#9807) 2023-06-26 18:59:39 +02:00
Crowdin Bot 6dbc8dd820 New Crowdin translations by Github Action 2023-06-26 15:25:09 +00:00
Hariom Balhara 4ada428bcb fix: Embed - Better support for layout config(without flash of default toggle) (#9714) 2023-06-26 17:24:19 +02:00
Crowdin Bot 6adaf0d3e8 New Crowdin translations by Github Action 2023-06-26 14:11:17 +00:00
Peer Richelsen c9e6acfa69 Update common.json (#9788) 2023-06-26 19:40:13 +05:30
Hariom Balhara 2272da5ee7 test: New Booker Tests for Embed (#9666) 2023-06-26 15:27:24 +02:00
Carina WollendorferandCarinaWolli 79b3875af3 fix: add UAE to list of non alphanumeric sender id support (#9752)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-26 15:25:27 +02:00
Peer Richelsen d16b6fce8f Update package.json 2023-06-26 15:13:31 +02:00
Gaurav Goswami 40b35bdbe4 bug/changed icon (#9801) 2023-06-26 13:00:19 +00:00
Keith Williams 0af76ec5ab chore: Updated staleness check to start with old content (#9800) 2023-06-26 14:49:41 +02:00
Ayush Mainali f7b20da640 fix: fixed layout shift issue of top banner (#9791) 2023-06-26 14:12:57 +02:00
Ritik KumarandCarina Wollendorfer eedf6913b8 feat: allow duplicate availability (#9615)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-06-26 07:35:20 -04:00
78393a2b8b feat: disable workflows and webhooks for orgs + UI changes [CAL-1873] (#9504)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-26 06:37:59 -04:00
Kinjalk Bajpai b3d64104b0 fix: Removing 2fa text that appears twice (#9762) 2023-06-25 15:21:39 +00:00
9266ba1e48 fix: don't show tool-tip when negative or zero recurring events (#9612)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-25 15:09:32 +00:00
Ujwal Kumar ec212e4317 fix-borders-on-weekly-view (#9770) 2023-06-25 17:04:25 +02:00
Kinjalk Bajpai 02d4f0542a fix: Broken Heroku Image in README.md (#9772) 2023-06-25 16:59:36 +02:00
Crowdin Bot 0aae07ae2f New Crowdin translations by Github Action 2023-06-23 20:50:05 +00:00
Daniel Roe 80ab6c9c98 fix: add space to make link clickable (#9763) 2023-06-23 22:49:20 +02:00
Afzal Sayed 86ba0766fc Prevent invalid api call (#9757) 2023-06-23 22:48:38 +02:00
Crowdin Bot 0adde17f38 New Crowdin translations by Github Action 2023-06-23 20:03:43 +00:00
Crowdin Bot 9e4700f9df New Crowdin translations by Github Action 2023-06-23 20:02:48 +00:00
Leo Giovanetti f4a87c66b3 Adding team and org registration suggestion (#9764) 2023-06-23 22:01:54 +02:00
0c06f1f6cb feat: Add google spam warning on booking confirmation page for gmail users ( CALCOM-9247) (#9531)
* feat: Add google spam warning on booking confirmation page for gmail users

* Update apps/web/pages/booking/[uid].tsx

Co-authored-by: alannnc <alannnc@gmail.com>

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-23 17:13:16 +00:00
Hariom Balhara b731dfd421 fix: Error during booking when default app is a Static Link Video App and Event has Organizer default location selected. (#9734) 2023-06-23 19:04:34 +02:00
sean-brydon 0b704b9ed4 test: Remove mockdate and replate with vitest system time (#9750) 2023-06-23 16:10:43 +00:00
Soham DattaandBailey Pumfleet a0ece0d77e fix: added loading skeleton for teams profile view (#9746)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-06-23 13:14:18 +00:00
Crowdin Bot 7839381911 New Crowdin translations by Github Action 2023-06-23 12:52:50 +00:00
Hariom Balhara 6a976f5b42 feat: New Booker: Disable dialog form in mobile in embed (#9748) 2023-06-23 18:22:03 +05:30
Peer Richelsenandalannnc 8718905474 docs: added "commerical" to API readme (#9636)
* Update README.md

* Update apps/api/README.md

* Update apps/api/README.md

---------

Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-23 11:06:01 +02:00
Jaideep Guntupalli ff968d3867 fix: Added relevant colors to preset and replaced with tailwind colors (#9733) 2023-06-23 09:05:37 +00:00
Jaideep Guntupalliandsean-brydon 8f5e6370b0 feat: Added Storybook Controls (#9676)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-23 09:04:26 +00:00
Peer Richelsen 0a0c400333 Update package.json
release.yml / release (push) Canceled after 0s
2023-06-23 10:58:34 +02:00
Anit Jha 0eb05aa540 fix: Public Link of pending members is now disabled (#9730) 2023-06-23 10:53:00 +02:00
Crowdin Bot efe18ebdcd New Crowdin translations by Github Action 2023-06-23 08:51:18 +00:00
Crowdin Bot ce097b89ba New Crowdin translations by Github Action 2023-06-23 08:50:29 +00:00
Deepak Rudra PaulandDeepak Paul bb0e6389fa fix: Fixed delete event-type button (#9739)
Co-authored-by: Deepak Paul <rudra.ad7@gmail.com>
2023-06-23 10:49:53 +02:00
Nafees Nazik 19a7cd1a50 fix: invite modal (#9713)
* fix: invite modal

* fix: truncate text
2023-06-23 10:48:20 +02:00
Hariom Balhara 49bcd5fec7 Event Analytics not working for new booker (#9740) 2023-06-23 08:42:02 +00:00
Udit Takkar 000a6e0d35 fix: location details on new booker (#9710)
* fix: location details on new booker

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

* chore

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-23 11:53:00 +05:30
Alex van Andel 52936f60b4 fix: Various errors & 500 error on duplicate url when editing (#9731) 2023-06-23 07:03:45 +02:00
Omar López 2d6eebcd8a chore: Sync packages mismatches (#9725) 2023-06-22 15:25:37 -07:00
Keith Williamsandalannnc cfa6a5b996 chore: Added unit tests for converting availability to string (#9617)
* Added unit tests for availability to string

* Testing to see if extra toString() removes unicode

* Replacing unicode space

* Updated to regex

* Reverted files that were not supposed to change

* Reverted schema changes

* Added comment

* Type check

* Change function call structure

---------

Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-22 22:19:50 +00:00
sean-brydon 7413ba6aa0 Fixes overflow + zindex of cal icon (#9707) 2023-06-22 19:46:42 +00:00
Deepak Rudra PaulandDeepak Paul e9cbe5d353 fix:copy-invite-link button (#9721)
Co-authored-by: Deepak Paul <rudra.ad7@gmail.com>
2023-06-22 21:35:09 +02:00
Alex van Andel d8bc9acbbc feat: remove member from hosts on removal (#9722)
* feat: When a member is removed we should remove it from the hosts

* Add migration for fixing old records affected
2023-06-22 21:33:03 +02:00
Hariom Balhara 4f0e843ac6 test: Make embed tests stable. (#9718)
* Wait for /embed/embed.js to be servable

* Change something in embeds/ to make embed tests run
2023-06-22 17:38:51 +02:00
sean-brydon 02f1c50bb2 Fix settings layout + org headings (#9712) 2023-06-22 11:57:00 +01:00
GitStart-Cal.comandgitstart-calcom 5952a38ab9 feat: [CAL-910] Pre-select country for phone number (CALCOM-5759) (#9526)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-06-22 10:18:05 +02:00
Efraín Rochín e5fb19163e perf: Remove useless data on EvenTypes getByViewer endpoint (#9704) 2023-06-22 10:16:53 +02:00
79f34a4fc7 perf: [CAL-1928] Deduplicate eventTypes.getByViewer query (#9700)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-06-22 10:16:34 +02:00
Crowdin Bot 3e90c08d55 New Crowdin translations by Github Action 2023-06-22 07:34:42 +00:00
Hariom Balhara 697d8eca43 Allow closing the embed on clicking the black transparent area (#9706) 2023-06-22 07:33:50 +00:00
Omar López 5cecf3104f refactor: Skip license checks for development (#9375) 2023-06-21 23:13:26 -03:00
Crowdin Bot a3a6caa6dd New Crowdin translations by Github Action 2023-06-21 23:30:55 +00:00
Omar López d3586c9039 Revert "fix: redirect to 404 page for invalid email verification token" (#9703) 2023-06-21 23:29:53 +00:00
71eafa287a feat: Organizations no middleware rewrite (#9548)
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-21 21:12:12 +00:00
Keith Williams 828ef8580e v3.0.5
release.yml / release (push) Canceled after 0s
2023-06-21 18:39:37 +02:00
Jeroen Reumkens a348c3a20a CAL-1892: Show booker layout dark mode preview based on theme the user selects (#9664) 2023-06-21 17:14:23 +02:00
Omar López 3c68c7f7cb chore: Upgrades prisma to 4.16 (#9670)
* Upgrades prisma to 4.16

* typefix
2023-06-21 17:07:54 +02:00
Hariom Balhara 820b2da16d test: Add RegExp tests for Next.config.js rewrites (#9681)
* Add RegExp tests

* Update apps/web/test/lib/next-config.test.ts

* Dont avoid rewrite for /booked event slug

* Add /embed/embed.js test

* Add dynamic group booking link test
2023-06-21 17:06:44 +02:00
sean-brydon 6bdb2b94eb feat: Adds sheet component (#9680)
* Adds sheet component

* Fix overlay

* Calc math
2023-06-21 15:29:23 +01:00
Hariom Balhara 3c3db0b3a6 fix: Hotfix/ Dynamic Group Booking Links not working in new booker (#9685)
* Debuglogs

* Derive group usernames correctly

* Revert "Debuglogs"

This reverts commit d93f031589b34bb7fee32a34f96eb89805c20275.
2023-06-21 15:22:13 +01:00
Hariom Balhara 4748641c22 fix: Hotfix/main - 404 workflows (#9678) 2023-06-21 15:39:03 +05:30
Jeroen Reumkens 804787f2a3 Get brand colors from DB for owner, so it's reflected in booker (#9679) 2023-06-21 09:04:15 +00:00
Alex van Andel b7c3b31e0c fix: eventType-getByViewer subselect on children->users[] (#9668) 2023-06-21 08:56:42 +00:00
Jeroen ReumkensandHariom Balhara ab6781cd72 fix: Booker fix for team + user with same event + event slug (#9654)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-06-21 13:01:55 +05:30
Crowdin Bot b070cfb227 New Crowdin translations by Github Action 2023-06-21 07:29:17 +00:00
Hariom Balhara 5acb3a09e5 fix: Hide EventTypeDetails when configured (#9633) 2023-06-21 12:58:21 +05:30
Crowdin Bot 69c9f690cb New Crowdin translations by Github Action 2023-06-20 17:55:04 +00:00
Ayush Mainali d2a12ea834 refactor: Convert card to cva (#9651) 2023-06-20 19:54:08 +02:00
16f24b4831 feat: [CAL-1435] Emptyscreen for App Store if no external app has been configured (CALCOM-8154) (#9516)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-20 19:53:33 +02:00
sean-brydon a3eeeab259 feat: Handle deleting vercel domain when org is deleted (#9661) 2023-06-20 17:57:21 +02:00
Alex van Andel f69c254fbc v3.0.4
release.yml / release (push) Canceled after 0s
2023-06-20 17:37:53 +02:00
Hariom Balhara f0f6bd4846 fix: Team links and username starting with team rewrites (#9665)
* Fix

* Fix private link /book page as well
2023-06-20 16:48:15 +02:00
Udit TakkarandPeer Richelsen 8816a18854 feat: team filters (#9543)
* feat: team filers

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

* fix: type error

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

* fix: remove filter from mobile

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

* fix: show heading when one item selected

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-20 16:39:18 +02:00
sean-brydon 4e529e5c6c test: Adds permission tests to impersonation (#9660)
* Adds permission tests to impersonation

* NIT + new test for none numbers
2023-06-20 13:42:37 +00:00
Alex van Andel e521bf6f0b v3.0.3
release.yml / release (push) Canceled after 0s
2023-06-20 12:26:14 +02:00
Jeroen Reumkensandalannnc a91a0f86e0 CAL-1904: Fixed bg color for all booked days as per design (#9630)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-20 12:20:40 +02:00
GitStart-Cal.comandgitstart-calcom f654003f95 fix: settings: skeleton issues (CALCOM-9627) (#9641)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-06-20 12:20:13 +02:00
Hariom Balhara 4cc8a6fbf4 fix: embed booking 404 (#9652) 2023-06-20 15:42:39 +05:30
Peer Richelsenandalannnc 5167608404 chore: added commercial API to LICENSE (#9635)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-20 09:33:43 +00:00
Crowdin Bot 9c93685431 New Crowdin translations by Github Action 2023-06-20 08:40:54 +00:00
Hariom Balhara a380087c9b reschedule_reason is used by Booking Form (#9656) 2023-06-20 10:39:46 +02:00
alannnc 3559e0d082 Improved with some try catch and removed old behaviour (#9650) 2023-06-20 00:10:47 +00:00
4323b0c2d1 chore: Upgrades Next.js to 13.4.6 (#9578)
* Upgrades Next.js to 13.4.6

* API handlers no longer receive a payload with req.method=DELETE

* Use DELETE with query params in Calendar Switch component

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-19 14:22:30 -07:00
Crowdin Bot 3a800f7511 New Crowdin translations by Github Action 2023-06-19 21:16:33 +00:00
Ujwal Kumarandalannnc c203e5a38b Fix margins in booking page (#9639)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-19 21:15:44 +00:00
Crowdin Bot 3f5b3f06a2 New Crowdin translations by Github Action 2023-06-19 20:33:22 +00:00
Leo Giovanetti d52635efbb Org labels and typos (#9646) 2023-06-19 13:32:21 -07:00
Omar López 4add4df08e fix: regression of username case sensitivity (#9642) 2023-06-19 17:34:31 +00:00
Crowdin Bot df6d7e8df7 New Crowdin translations by Github Action 2023-06-19 17:16:51 +00:00
Samuel StroscheinandPeer Richelsen dc61640118 chore: add inlang ide extension for i18n (#9599)
* add: inlang for i18n

* chore: format file

* chore: re-add comments

VSCode removed them for unknown reasons.

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-19 10:15:45 -07:00
Alex van Andel 361108246e Created secondary function to read the owner from users array (#9638) 2023-06-19 08:53:31 -07:00
Abdurrahman RajabandKeith Williams a5e25b9b28 docs: improving developer experience (#9432)
* feat: add feedback to semantic commits

* feat: welcome new contributors

* feat: add triggers welcome message

* chore: fix lint issue

* chore: typos

* Fixed spacing

* Temporarily making this runnable via Actions page

* Removed workflow_call

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-19 16:45:41 +02:00
Leo Giovanetti ccff528765 fix: Creating teams for organizations in wizard (#9632)
* Expanding org slug check and adding proper error msg

* Duh &&
2023-06-19 14:37:23 +00:00
Peer Richelsen 24a1ef6721 Create LICENSE 2023-06-19 16:18:02 +02:00
Ninad Trivediandsean-brydon b89919129e Copy link button repositioned (#9613)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-19 16:15:48 +02:00
Keith Williams cbf3edefc7 chore: Added unit tests for CalendarManager->getCalendarCredentials (#9618)
* Added unit tests for CalendarManager->getCalendarCredentials

* Fixed spacing

* Made test data more accurate
2023-06-19 16:10:06 +02:00
Crowdin Bot 909ae5f2fb New Crowdin translations by Github Action 2023-06-19 14:01:31 +00:00
Syed Ali Shahbaz 0bbe5173dc docs: Update API documentation for Bookings endpoint (_post & _patch) (#9511)
* Fix swagger in _post bookings

* Update swagger doc for _patch

* Resolves two NITs

* Resolves metadata mistake
2023-06-19 14:00:36 +00:00
Jeroen Reumkens 84ad133077 Removed full height which incorrectly limited height in booker sidebar. Tested in Chrome, FF and Safari. (#9628) 2023-06-19 13:14:51 +00:00
Udit Takkar 43647a797f fix: remove enable_new_call_ui prop (#9625)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-19 15:03:40 +02:00
Ritik KumarandUdit Takkar f469a9c127 fix: redirect to 404 page for invalid email verification token (#9499)
* fix:redirect to 404 page for invalid email token

* update 404 page to include invalid token error

* fix: use i18n

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-19 13:24:10 +01:00
Carina WollendorferandCarinaWolli 212f7a374f fix name dropdown (#9621)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-19 11:44:47 +00:00
819 changed files with 21712 additions and 12180 deletions
+11 -7
View File
@@ -59,6 +59,11 @@ SAML_CLIENT_SECRET_VERIFIER=
# PGSSLMODE='no-verify'
PGSSLMODE=
# Define which hostnames are expected for the app to work on
ALLOWED_HOSTNAMES='"cal.com","cal.dev","cal-staging.com","cal.community","cal.local:3000","localhost:3000"'
# Reserved orgs subdomains for our own usage
RESERVED_SUBDOMAINS='"app","auth","docs","design","console","go","status","api","saml","www","matrix","developer","cal","my","team","support","security","blog","learn","admin"'
# - NEXTAUTH
# @see: https://github.com/calendso/calendso/issues/263
# @see: https://next-auth.js.org/configuration/options#nextauth_url
@@ -76,6 +81,10 @@ CALCOM_TELEMETRY_DISABLED=
# ApiKey for cronjobs
CRON_API_KEY='0cc0e6c35519bba620c9360cfe3e68d0'
# Whether to automatically keep app metadata in the database in sync with the metadata/config files. When disabled, the
# sync runs in a reporting-only dry-run mode.
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
@@ -112,6 +121,7 @@ TWILIO_SID=
TWILIO_TOKEN=
TWILIO_MESSAGING_SID=
TWILIO_PHONE_NUMBER=
TWILIO_WHATSAPP_PHONE_NUMBER=
# For NEXT_PUBLIC_SENDER_ID only letters, numbers and spaces are allowed (max. 11 characters)
NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID=
@@ -126,6 +136,7 @@ NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE=
NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_ORG_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
@@ -172,7 +183,6 @@ CLOSECOM_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Change your Brand
NEXT_PUBLIC_APP_NAME="Cal.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
@@ -205,9 +215,3 @@ PROJECT_ID_VERCEL=
TEAM_ID_VERCEL=
# Get it from: https://vercel.com/account/tokens
AUTH_BEARER_TOKEN_VERCEL=
#Enables New booker for Embed only
NEW_BOOKER_ENABLED_FOR_EMBED=0
#Enables New booker for All but Embed requests
NEW_BOOKER_ENABLED_FOR_NON_EMBED=0
+12
View File
@@ -20,8 +20,20 @@ A summary of the issue. This needs to be a clear detailed-rich summary.
Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
### Actual Results
- What's happening right now that is different from what is expected
### Expected Results
- This is an ideal result that the system should get after the tests are performed
### Technical details
- Browser version, screen recording, console logs, network requests: You can make a recording with [Bird Eats Bug](https://birdeatsbug.com/).
- Node.js version
- Anything else that you think could be an issue.
### Evidence
- How was this tested? This is quite mandatory in terms of bugs. Providing evidence of your testing with screenshots or/and videos is an amazing way to prove the bug and a troubleshooting chance to find the solution.
+2 -2
View File
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions
url: https://cal.com/slack
about: Ask a general question about the project on our Slack workspace
url: https://go.cal.com/discord
about: Ask a general question about the project on our discord community
@@ -39,3 +39,11 @@ assignees: ""
-->
(Write your answer here.)
### Requirement/Document
<!--
Is there any type of document that could support that feature?
-->
(Share it here.)
+19 -8
View File
@@ -8,29 +8,40 @@ Fixes # (issue)
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
- [ ] 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 -->
<!-- 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 -->
- [ ] Test A
- [ ] Test B
- 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
+5 -4
View File
@@ -2,15 +2,16 @@ name: "Set environment variables"
description: "Configures environment variables for a workflow"
runs:
using: "composite"
steps:
- name: Create env file
- name: Read env file
uses: buildjet/cache@v3
id: env-cache
with:
path: gh.env
key: env-cache-${{ hashFiles('gh.env') }}
path: gh.env-${{ github.sha }}
key: env-cache
restore-keys: env-cache-
- name: Set Environment Variables
uses: tw3lveparsecs/github-actions-setvars@latest
with:
envFilePath: gh.env
envFilePath: gh.env-${{ github.sha }}
@@ -0,0 +1,23 @@
name: Cron - scheduleWhatsappReminders
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
jobs:
cron-scheduleWhatsappReminders:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/workflows/scheduleWhatsappReminders \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
+2
View File
@@ -10,6 +10,7 @@ on:
schedule:
# Runs every day (see https://crontab.guru)
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
@@ -22,3 +23,4 @@ jobs:
days-before-pr-close: 7
stale-pr-message: "This PR is being marked as stale due to inactivity."
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
operations-per-run: 100
+24
View File
@@ -0,0 +1,24 @@
name: Cron - syncAppMeta
on:
workflow_dispatch:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “Every month at 1st (see https://crontab.guru)
- cron: "0 0 1 * *"
jobs:
cron-syncAppMeta:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/syncAppMeta \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
+1
View File
@@ -22,6 +22,7 @@ jobs:
uses: crowdin/github-action@1.5.1
with:
upload_sources: true
upload_translations: true
download_translations: true
push_translations: true
commit_message: "New Crowdin translations by Github Action"
+8 -3
View File
@@ -4,6 +4,7 @@ on:
workflow_call:
env:
ENV_FILE_NAME: gh.env-${{ github.sha }}
INPUT_ENV_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
INPUT_ENV_NEXT_PUBLIC_WEBAPP_URL: http://localhost:3000
INPUT_ENV_NEXT_PUBLIC_WEBSITE_URL: http://localhost:3000
@@ -11,6 +12,7 @@ env:
INPUT_ENV_GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
INPUT_ENV_GOOGLE_LOGIN_ENABLED: true
# INPUT_ENV_CRON_API_KEY: xxx
# INPUT_ENV_CRON_ENABLE_APP_SYNC: true|false
INPUT_ENV_CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
INPUT_ENV_NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
INPUT_ENV_STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
@@ -31,6 +33,8 @@ env:
# INPUT_ENV_MS_GRAPH_CLIENT_SECRET: xxx
# INPUT_ENV_ZOOM_CLIENT_ID: xxx
# INPUT_ENV_ZOOM_CLIENT_SECRET: xxx
INPUT_ENV_SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
INPUT_ENV_SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
INPUT_ENV_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
INPUT_ENV_TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
@@ -41,10 +45,11 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ozaytsev86/create-env-file@v1
with:
file-name: ${{ github.workspace }}/gh.env
file-name: ${{ github.workspace }}/${{ env.ENV_FILE_NAME }}
- uses: buildjet/cache@v3
id: env-cache
with:
path: gh.env
key: env-cache-${{ hashFiles('gh.env') }}
path: ${{ env.ENV_FILE_NAME }}
key: env-cache-${{ hashFiles(env.ENV_FILE_NAME) }}
@@ -7,8 +7,13 @@ on:
- main
jobs:
env:
name: Create env file
uses: ./.github/workflows/env-create-file.yml
secrets: inherit
build:
name: Production build
needs: env
if: ${{ github.event_name == 'push' }}
uses: ./.github/workflows/production-build.yml
secrets: inherit
@@ -0,0 +1,16 @@
name: Assign PR team labels
on:
pull_request:
branches:
- main
jobs:
team-labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: equitybee/team-label-action@main
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"
+3
View File
@@ -34,12 +34,15 @@ jobs:
app-store:
- 'apps/web/**'
- 'packages/app-store/**'
- 'playwright.config.ts'
embed:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
embed-react:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
env:
name: Create env file
+28 -2
View File
@@ -9,7 +9,7 @@ on:
- synchronize
permissions:
pull-requests: read
pull-requests: write
jobs:
validate-pr:
@@ -17,5 +17,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
message: |
Thank you for following the naming conventions! 🙏
@@ -0,0 +1,28 @@
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
needs: semantics
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 the conversation at [discord](https://go.cal.com/discord)
issue-message: |
Thank you for opening your first issue, one of our team members will review it as soon as it possible. ❤️🎉
+2 -1
View File
@@ -8,6 +8,7 @@
"ban.spellright", // Spell check for docs
"stripe.vscode-stripe", // stripe VSCode extension
"Prisma.prisma", // syntax|format|completion for prisma
"rebornix.project-snippets" // Share useful snippets between collaborators
"rebornix.project-snippets", // Share useful snippets between collaborators
"inlang.vs-code-extension" // improved i18n DX
]
}
+1
View File
@@ -88,6 +88,7 @@ To develop locally:
5. Set up your `.env` file:
- Duplicate `.env.example` to `.env`.
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
+1 -1
View File
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
Portions of this software are licensed as follows:
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee and https://github.com/calcom/cal.com/tree/main/apps/api/ directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
+26 -22
View File
File diff suppressed because one or more lines are too long
+34 -16
View File
@@ -1,39 +1,57 @@
# Security
Contact: security@cal.com
Contact: [security@cal.com](mailto:security@cal.com)
Based on [https://supabase.com/.well-known/security.txt](https://supabase.com/.well-known/security.txt)
At Cal.com, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present.
At Cal.com, we consider the security of our systems a top priority. But no
matter how much effort we put into system security, there can still be
vulnerabilities present.
If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible. We would like to ask you to help us better protect our clients and our systems.
If you discover a vulnerability, we would like to know about it so we can take
steps to address it as quickly as possible. We would like to ask you to help us
better protect our clients and our systems.
## Out of scope vulnerabilities:
## Out of scope vulnerabilities
- Clickjacking on pages with no sensitive actions.
- Unauthenticated/logout/login CSRF.
- Attacks requiring MITM or physical access to a user's device.
- Any activity that could lead to the disruption of our service (DoS).
- Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS.
- Content spoofing and text injection issues without showing an attack
vector/without being able to modify HTML/CSS.
- Email spoofing
- Missing DNSSEC, CAA, CSP headers
- Lack of Secure or HTTP only flag on non-sensitive cookies
- Deadlinks
## Please do the following:
## Please do the following
- E-mail your findings to [security@cal.com](mailto:security@cal.com).
- Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you.
- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
- Do not run automated scanners on our infrastructure or dashboard. If you wish
to do this, contact us and we will set up a sandbox for you.
- Do not take advantage of the vulnerability or problem you have discovered,
for example by downloading more data than necessary to demonstrate the
vulnerability or deleting or modifying other people's data,
- Do not reveal the problem to others until it has been resolved,
- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties,
- Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation.
- Do not use attacks on physical security, social engineering, distributed
denial of service, spam or applications of third parties,
- Do provide sufficient information to reproduce the problem, so we will be
able to resolve it as quickly as possible. Usually, the IP address or the URL
of the affected system and a description of the vulnerability will be
sufficient, but complex vulnerabilities may require further explanation.
## What we promise:
## What we promise
- We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date,
- If you have followed the instructions above, we will not take any legal action against you in regard to the report,
- We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission,
- We will respond to your report within 3 business days with our evaluation of
the report and an expected resolution date,
- If you have followed the instructions above, we will not take any legal
action against you in regard to the report,
- We will handle your report with strict confidentiality, and not pass on your
personal details to third parties without your permission,
- We will keep you informed of the progress towards resolving the problem,
- In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and
- We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved.
- In the public information concerning the problem reported, we will give your
name as the discoverer of the problem (unless you desire otherwise), and
- We strive to resolve all problems as quickly as possible, and we would like
to play an active role in the ultimate publication on the problem after it
is resolved.
+4
View File
@@ -37,6 +37,10 @@
"description": "ApiKey for cronjobs",
"value": ""
},
"CRON_ENABLE_APP_SYNC": {
"description": "Whether to automatically keep app metadata in the database in sync with the metadata/config files. When disabled, the sync runs in a reporting-only dry-run mode.",
"value": "false"
},
"SEND_FEEDBACK_EMAIL": {
"description": "Send feedback email",
"value": ""
+42
View File
@@ -0,0 +1,42 @@
The Cal.com Commercial License (the “Commercial License”)
Copyright (c) 2020-present Cal.com, Inc
With regard to the Cal.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cal.com Subscription Terms available
at https://cal.com/terms, or other agreements governing
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
and otherwise have a valid Cal.com Enterprise Edition subscription ("Commercial Subscription")
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This Commercial License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Cal.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.
+1 -1
View File
@@ -7,7 +7,7 @@
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
</div>
# Cal.com Public API
# Commercial Cal.com Public API
Welcome to the Public API ("/apps/api") of the Cal.com.
+1 -1
View File
@@ -17,7 +17,7 @@ export const dateNotInPast = function (date: Date) {
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
const { prisma, isCustomPrisma, isAdmin } = req;
const hasValidLicense = await checkLicense(prisma);
if (!hasValidLicense)
if (!hasValidLicense && process.env.NODE_ENV !== "development")
return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" });
// If the user is an admin and using a license key (from customPrisma), skip the apiKey check.
if (isCustomPrisma && isAdmin) {
+6 -2
View File
@@ -1,5 +1,6 @@
import { z } from "zod";
import slugify from "@calcom/lib/slugify";
import { _EventTypeModel as EventType, _HostModel } from "@calcom/prisma/zod";
import { customInputSchema, eventTypeBookingFields } from "@calcom/prisma/zod-utils";
@@ -58,7 +59,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
const schemaEventTypeCreateParams = z
.object({
title: z.string(),
slug: z.string(),
slug: z.string().transform((s) => slugify(s)),
description: z.string().optional().nullable(),
length: z.number().int(),
metadata: z.any().optional(),
@@ -76,7 +77,10 @@ export const schemaEventTypeCreateBodyParams = schemaEventTypeBaseBodyParams
const schemaEventTypeEditParams = z
.object({
title: z.string().optional(),
slug: z.string().optional(),
slug: z
.string()
.transform((s) => slugify(s))
.optional(),
length: z.number().int().optional(),
seatsPerTimeSlot: z.number().optional(),
seatsShowAttendees: z.boolean().optional(),
+2 -2
View File
@@ -32,9 +32,9 @@
"@sentry/nextjs": "^7.20.0",
"bcryptjs": "^2.4.3",
"memory-cache": "^0.2.0",
"next": "~13.2.1",
"next": "^13.4.6",
"next-api-middleware": "^1.0.1",
"next-axiom": "^0.16.0",
"next-axiom": "^0.17.0",
"next-swagger-doc": "^0.3.6",
"next-validations": "^0.2.0",
"typescript": "^4.9.4",
+12 -1
View File
@@ -35,6 +35,9 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* status:
* type: string
* description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]'
* description:
* type: string
* description: 'Description of the meeting'
* examples:
* editBooking:
* value:
@@ -79,7 +82,15 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* "startTime": "2023-05-24T13:00:00.000Z",
* "endTime": "2023-05-24T13:30:00.000Z",
* "metadata": {},
* "status": "CANCELLED"
* "status": "CANCELLED",
* "responses": {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "location": {
* "optionValue": "",
* "value": "inPerson"
* }
* }
* }
* }
* 400:
+69 -70
View File
@@ -26,13 +26,10 @@ import { defaultResponder } from "@calcom/lib/server";
* required:
* - eventTypeId
* - start
* - name
* - email
* - responses
* - timeZone
* - language
* - metadata
* - customInputs
* - location
* properties:
* eventTypeId:
* type: integer
@@ -45,29 +42,33 @@ import { defaultResponder } from "@calcom/lib/server";
* type: string
* format: date-time
* description: 'End time of the Event'
* name:
* type: string
* description: 'Name of the Attendee'
* email:
* type: string
* format: email
* description: 'Email ID of the Attendee'
* responses:
* type: object
* required:
* - name
* - email
* - location
* properties:
* name:
* type: string
* description: 'Attendee full name'
* email:
* type: string
* format: email
* description: 'Attendee email address'
* location:
* type: string
* description: 'Meeting location'
* metadata:
* type: object
* properties: {}
* description: 'Any metadata associated with the booking'
* timeZone:
* type: string
* description: 'TimeZone of the Attendee'
* language:
* type: string
* description: 'Language of the Attendee'
* metadata:
* type: object
* properties: {}
* description: 'Any metadata associated with the booking'
* customInputs:
* type: array
* items: {}
* location:
* type: string
* description: 'Meeting location'
* title:
* type: string
* description: 'Booking event title'
@@ -96,13 +97,14 @@ import { defaultResponder } from "@calcom/lib/server";
* "eventTypeId": 2323232,
* "start": "2023-05-24T13:00:00.000Z",
* "end": "2023-05-24T13:30:00.000Z",
* "name": "Hello Hello",
* "email": "hello@gmail.com",
* "responses":{
* "name": "Hello Hello",
* "email": "hello@gmail.com",
* "metadata": {},
* "location": "Calcom HQ",
* },
* "timeZone": "Europe/London",
* "language": "en",
* "metadata": {},
* "customInputs": [],
* "location": "Calcom HQ",
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
* "description": null,
* "status": "PENDING",
@@ -117,53 +119,50 @@ import { defaultResponder } from "@calcom/lib/server";
* content:
* application/json:
* examples:
* bookings:
* booking created successfully example:
* value:
* {
* "id": 11223344,
* "uid": "5yUjmAYTDF6MXo98re8SkX",
* "userId": 123,
* "eventTypeId": 2323232,
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
* "description": null,
* "customInputs": {},
* "responses": null,
* "startTime": "2023-05-24T13:00:00.000Z",
* "endTime": "2023-05-24T13:30:00.000Z",
* "location": "Calcom HQ",
* "createdAt": "2023-04-19T10:17:58.580Z",
* "updatedAt": null,
* "status": "PENDING",
* "paid": false,
* "destinationCalendarId": 2180,
* "cancellationReason": null,
* "rejectionReason": null,
* "dynamicEventSlugRef": null,
* "dynamicGroupSlugRef": null,
* "rescheduled": null,
* "fromReschedule": null,
* "recurringEventId": null,
* "smsReminderNumber": null,
* "scheduledJobs": [],
* "metadata": {},
* "isRecorded": false,
* "user": {
* "email": "test@cal.com",
* "name": "Syed Ali Shahbaz",
* "timeZone": "Asia/Calcutta"
* },
* "attendees": [
* {
* "id": 12345,
* "email": "hello@gmail.com",
* "name": "Hello Hello",
* "timeZone": "Europe/London",
* "locale": "en",
* "bookingId": 11223344
* "booking": {
* "id": 91,
* "userId": 5,
* "description": "",
* "eventTypeId": 7,
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
* "title": "60min between Pro Example and John Doe",
* "startTime": "2023-05-25T09:30:00.000Z",
* "endTime": "2023-05-25T10:30:00.000Z",
* "attendees": [
* {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "timeZone": "Asia/Kolkata",
* "locale": "en"
* }
* ],
* "user": {
* "email": "pro@example.com",
* "name": "Pro Example",
* "timeZone": "Asia/Kolkata",
* "locale": "en"
* },
* "payment": [
* {
* "id": 1,
* "success": true,
* "paymentOption": "ON_BOOKING"
* }
* ],
* "metadata": {},
* "status": "ACCEPTED",
* "responses": {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "location": {
* "optionValue": "",
* "value": "inPerson"
* }
* }
* ],
* "payment": [],
* "references": []
* }
* }
* 400:
* description: |
+38 -3
View File
@@ -47,7 +47,6 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* slug:
* type: string
* description: Unique slug for the event type
* example: my-event
* hosts:
* type: array
* items:
@@ -168,9 +167,9 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* type: string
* displayLocationPublicly:
* type: boolean
* example:
* examples:
* event-type:
* summary: An example of event type POST request
* summary: An example of an individual event type POST request
* value:
* title: Hello World
* slug: hello-world
@@ -205,6 +204,42 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* }
* }
* }
* team-event-type:
* summary: An example of a team event type POST request
* value:
* title: "Tennis class"
* slug: "tennis-class-{{$guid}}"
* length: 60
* hidden: false
* position: 0
* teamId: 3
* eventName: null
* timeZone: null
* periodType: "UNLIMITED"
* periodStartDate: null
* periodEndDate: null
* periodDays: null
* periodCountCalendarDays: null
* requiresConfirmation: true
* recurringEvent:
* interval: 2
* count: 10
* freq: 2
* disableGuests: false
* hideCalendarNotes: false
* minimumBookingNotice: 120
* beforeEventBuffer: 0
* afterEventBuffer: 0
* schedulingType: "COLLECTIVE"
* price: 0
* currency: "usd"
* slotInterval: null
* successRedirectUrl: null
* description: null
* locations:
* - address: "London"
* type: "inPerson"
* metadata: {}
* tags:
* - event-types
* externalDocs:
@@ -2,7 +2,7 @@ import { CAL_URL } from "@calcom/lib/constants";
export default function getCalLink(eventType: {
team?: { slug: string | null } | null;
owner: { username: string | null } | null;
owner?: { username: string | null } | null;
users?: { username: string | null }[];
slug: string;
}) {
+17 -10
View File
@@ -2,29 +2,36 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { MembershipRole } from "@calcom/prisma/enums";
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
import { createContext } from "@calcom/trpc/server/createContext";
import { publishHandler } from "@calcom/trpc/server/routers/viewer/teams/publish.handler";
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId";
import authMiddleware, { checkPermissions } from "./_auth-middleware";
const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
await checkPermissions(req, { in: [MembershipRole.OWNER, MembershipRole.ADMIN] });
/** @see https://trpc.io/docs/server-side-calls */
const ctx = await createContext({ req, res });
const user = ctx.user;
if (!user) {
throw new Error("Internal Error.");
async function sessionGetter() {
return {
user: {
id: req.userId,
username: "" /* Not used in this context */,
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
},
hasValidLicense: true /* To comply with TS signature */,
expires: "" /* Not used in this context */,
};
}
/** @see https://trpc.io/docs/server-side-calls */
const ctx = await createContext({ req, res }, sessionGetter);
try {
const { teamId } = schemaQueryTeamId.parse(req.query);
return await publishHandler({ input: { teamId }, ctx: { ...ctx, user } });
const caller = viewerTeamsRouter.createCaller(ctx);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return await caller.publish(req.query as any /* Let tRPC handle this */);
} catch (cause) {
if (cause instanceof TRPCError) {
const statusCode = getHTTPStatusCodeFromError(cause);
+5 -5
View File
@@ -26,15 +26,15 @@ export function VariantsTable({
<table>
<RowTitles titles={titles} />
{columns.map((column) => (
<tr className="p-2 pr-6 pb-6" key={column.props.variant}>
<tr className="p-2 pb-6 pr-6" key={column.props.variant}>
<th
className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]"
className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]"
key={column.props.variant}>
{column.props.variant}
</th>
{React.Children.count(column.props.children) &&
React.Children.map(column.props.children, (cell) => (
<td className="p-2 pr-6 pb-6" style={{ minWidth: `${columnMinWidth}px` }}>
<td className="p-2 pb-6 pr-6" style={{ minWidth: `${columnMinWidth}px` }}>
{cell}
</td>
))}
@@ -70,9 +70,9 @@ export function VariantRow({ children }: RowProps) {
export function RowTitles({ titles }: { titles: string[] }) {
return (
<tr>
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" />
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" />
{titles.map((title) => (
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
{title}
</th>
))}
+6 -6
View File
@@ -12,15 +12,15 @@
"@calcom/ui": "*",
"@radix-ui/react-avatar": "^1.0.0",
"@radix-ui/react-collapsible": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-id": "^1.0.0",
"@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-radio-group": "^1.0.0",
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^13.2.1",
"next": "^13.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook-addon-rtl-direction": "^0.0.19"
@@ -37,8 +37,8 @@
"@storybook/react": "^6.5.13",
"@storybook/testing-library": "^0.0.13",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@vitejs/plugin-react": "^2.1.0",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.12",
"babel-loader": "^8.2.5",
"fs": "^0.0.1-security",
@@ -48,7 +48,7 @@
"storybook-addon-designs": "^6.3.1",
"storybook-addon-next": "^1.6.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"tailwindcss": "^3.3.1",
"typescript": "^4.9.4",
"vite": "^4.1.2"
}
@@ -209,6 +209,7 @@
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-dark-error: #752522;
/* Borders */
@@ -216,6 +217,7 @@
--cal-border: #D1D5DB;
--cal-border-subtle:#E5E7EB;
--cal-border-muted:#F3F4F6;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #111827;
@@ -251,6 +253,7 @@
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-dark-error: #752522;
/* Borders */
@@ -258,6 +261,7 @@
--cal-border: #444444;
--cal-border-subtle: #2B2B2B;
--cal-border-muted: #1C1C1C;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #F3F4F6;
+2 -2
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^13.2.1",
"next": "^13.4.6",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -23,7 +23,7 @@
"devDependencies": {
"@types/node": "16.9.1",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@types/react-dom": "^18.0.9",
"typescript": "^4.9.4"
}
}
+20 -2
View File
@@ -3,9 +3,10 @@ import type { ReactNode } from "react";
import { useEffect, useRef, useState } from "react";
import { z } from "zod";
import type { CredentialOwner } from "@calcom/app-store/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { Badge, ListItemText } from "@calcom/ui";
import { Badge, ListItemText, Avatar } from "@calcom/ui";
import { AlertCircle } from "@calcom/ui/components/icon";
type ShouldHighlight = { slug: string; shouldHighlight: true } | { shouldHighlight?: never; slug?: never };
@@ -19,6 +20,7 @@ type AppListCardProps = {
isTemplate?: boolean;
invalidCredential?: boolean;
children?: ReactNode;
credentialOwner?: CredentialOwner;
} & ShouldHighlight;
const schema = z.object({ hl: z.string().optional() });
@@ -36,6 +38,7 @@ export default function AppListCard(props: AppListCardProps) {
isTemplate,
invalidCredential,
children,
credentialOwner,
} = props;
const {
data: { hl },
@@ -65,7 +68,7 @@ export default function AppListCard(props: AppListCardProps) {
return (
<div className={`${highlight ? "dark:bg-muted bg-yellow-100" : ""}`}>
<div className="flex gap-x-3 px-5 py-4">
<div className="flex items-center gap-x-3 px-5 py-4">
{logo ? <img className="h-10 w-10" src={logo} alt={`${title} logo`} /> : null}
<div className="flex grow flex-col gap-y-1 truncate">
<div className="flex items-center gap-x-2">
@@ -85,6 +88,21 @@ export default function AppListCard(props: AppListCardProps) {
</div>
)}
</div>
{credentialOwner && (
<div>
<Badge variant="gray">
<div className="flex items-center">
<Avatar
className="mr-2"
alt={credentialOwner.name || "Nameless"}
size="xs"
imageSrc={credentialOwner.avatar}
/>
{credentialOwner.name}
</div>
</Badge>
</div>
)}
{actions}
</div>
+90 -24
View File
@@ -10,13 +10,14 @@ import { components } from "react-select";
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { APP_NAME, EMBED_LIB_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import {
Button,
Dialog,
DialogClose,
DialogFooter,
DialogContent,
HorizontalTabs,
Label,
@@ -38,13 +39,21 @@ const enum Theme {
dark = "dark",
}
const EMBED_CAL_ORIGIN = WEBAPP_URL;
const EMBED_CAL_JS_URL = `${WEBAPP_URL}/embed/embed.js`;
type PreviewState = {
inline: {
width: string;
height: string;
};
theme: Theme;
floatingPopup: Record<string, string | boolean | undefined>;
floatingPopup: {
config?: {
layout: BookerLayouts;
};
[key: string]: string | boolean | undefined | Record<string, string>;
};
elementClick: Record<string, string>;
palette: {
brandColor: string;
@@ -175,14 +184,24 @@ const Codes: Record<string, Record<string, (...args: any[]) => string>> = {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function MyApp() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
${uiInstructionCode}
})();
}, [])
return <Cal calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} />;
return <Cal
calLink="${calLink}"
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
${previewState.layout ? "config={{layout: '" + previewState.layout + "'}}" : ""}${
IS_SELF_HOSTED
? `
calOrigin="${EMBED_CAL_ORIGIN}"
calJsUrl="${EMBED_CAL_JS_URL}"`
: ""
}
/>;
};`;
},
"floating-popup": ({
@@ -193,38 +212,60 @@ function MyComponent() {
uiInstructionCode: string;
}) => {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function App() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
cal("floatingButton", ${floatingButtonArg});
${uiInstructionCode}
})();
}, [])
};`;
},
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
"element-click": ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function App() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
${uiInstructionCode}
})();
}, [])
return <button data-cal-link="${calLink}" />;
return <button
data-cal-link="${calLink}"${IS_SELF_HOSTED ? `\ndata-cal-origin="${EMBED_CAL_ORIGIN}"` : ""}
${`data-cal-config='${JSON.stringify({
layout: previewState.layout,
})}'`}
>Click me</button>;
};`;
},
},
HTML: {
inline: ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
inline: ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`Cal("inline", {
elementOrSelector:"#my-cal-inline",
calLink: "${calLink}"
calLink: "${calLink}",
layout: "${previewState.layout}"
});
${uiInstructionCode}`;
@@ -240,8 +281,22 @@ ${uiInstructionCode}`;
return code`Cal("floatingButton", ${floatingButtonArg});
${uiInstructionCode}`;
},
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
return code`// Important: Make sure to add \`data-cal-link="${calLink}"\` attribute to the element you want to open Cal on click
"element-click": ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`
// Important: Please add following attributes to the element you want to open Cal on click
// \`data-cal-link="${calLink}"\`
// \`data-cal-config='${JSON.stringify({
layout: previewState.layout,
})}'\`
${uiInstructionCode}`;
},
},
@@ -298,6 +353,7 @@ const getEmbedTypeSpecificString = ({
} else if (embedType === "floating-popup") {
const floatingButtonArg = {
calLink,
...(IS_SELF_HOSTED ? { calOrigin: EMBED_CAL_ORIGIN } : null),
...previewState.floatingPopup,
};
return frameworkCodes[embedType]({
@@ -710,13 +766,13 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
height: "100%",
},
theme: Theme.auto,
layout: BookerLayouts.MONTH_VIEW,
floatingPopup: {},
elementClick: {},
hideEventTypeDetails: false,
palette: {
brandColor: "#000000",
},
layout: BookerLayouts.MONTH_VIEW,
});
const close = () => {
@@ -778,8 +834,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
name: "ui",
arg: {
theme: previewState.theme,
hideEventTypeDetails: previewState.hideEventTypeDetails,
layout: previewState.layout,
hideEventTypeDetails: previewState.hideEventTypeDetails,
styles: {
branding: {
...previewState.palette,
@@ -1000,7 +1056,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div className="whitespace-nowrap">Text color</div>
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
<div className="mb-6 mt-2 w-40 xl:mt-0 xl:w-full">
<ColorPicker
className="w-[130px]"
popoverAlign="start"
@@ -1102,8 +1158,15 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
return;
}
setPreviewState((previewState) => {
const config = {
...(previewState.floatingPopup.config ?? {}),
layout: option.value,
};
return {
...previewState,
floatingPopup: {
config,
},
layout: option.value,
};
});
@@ -1119,7 +1182,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
</div>
<div className="flex w-2/3 flex-col p-8">
<HorizontalTabs data-testid="embed-tabs" tabs={parsedTabs} linkProps={{ shallow: true }} />
<HorizontalTabs data-testid="embed-tabs" tabs={parsedTabs} linkShallow />
{tabs.map((tab) => {
return (
<div
@@ -1144,8 +1207,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
/>
)}
</div>
<div className={router.query.embedTabName == "embed-preview" ? "block" : "hidden"} />
<div className="mt-8 flex flex-row-reverse gap-x-2">
<div className={router.query.embedTabName == "embed-preview" ? "mt-2 block" : "hidden"} />
<DialogFooter
className="mt-10 flex flex-row-reverse gap-x-2"
showDivider
customDividerClassNames="w-2/3">
<DialogClose />
{tab.type === "code" ? (
<Button
type="submit"
@@ -1160,8 +1227,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{t("copy_code")}
</Button>
) : null}
<DialogClose />
</div>
</DialogFooter>
</div>
);
})}
+2 -4
View File
@@ -1,4 +1,3 @@
import { isEmpty } from "lodash";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
@@ -20,13 +19,13 @@ export function useViewerI18n() {
/**
* Auto-switches locale client-side to the logged in user's preference
*/
const I18nLanguageHandler = (): null => {
const I18nLanguageHandler = () => {
const { i18n } = useTranslation("common");
const locale = useViewerI18n().data?.locale || i18n.language;
useEffect(() => {
// bail early when i18n = {}
if (isEmpty(i18n)) return;
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);
@@ -35,7 +34,6 @@ const I18nLanguageHandler = (): null => {
document.dir = i18n.dir();
document.documentElement.setAttribute("lang", locale);
}, [locale, i18n]);
return null;
};
+1 -1
View File
@@ -71,7 +71,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
isCurrent
? "text-emphasis border-gray-900"
: "hover:border-default hover:text-default text-subtle border-transparent",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
"group inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
className
)}
aria-current={isCurrent ? "page" : undefined}>
+2 -6
View File
@@ -6,7 +6,7 @@ import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { WEBAPP_URL, IS_CALCOM } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import type { AppProps } from "@lib/app-providers";
@@ -53,10 +53,6 @@ function PageWrapper(props: AppProps) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
// Canonical: Check if the URL is from cal.com so that we set the canonical conditionally
const isCalcom =
WEBAPP_URL &&
(new URL(WEBAPP_URL).hostname.endsWith("cal.com") || new URL(WEBAPP_URL).hostname.endsWith("cal.dev"));
const path = router.asPath;
return (
@@ -70,7 +66,7 @@ function PageWrapper(props: AppProps) {
<DefaultSeo
// Set canonical to https://cal.com or self-hosted URL
canonical={
isCalcom
IS_CALCOM
? buildCanonical({ path, origin: "https://cal.com" }) // cal.com & .dev
: buildCanonical({ path, origin: WEBAPP_URL }) // self-hosted
}
+140 -33
View File
@@ -1,19 +1,36 @@
import Link from "next/link";
import type { IframeHTMLAttributes } from "react";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { CAL_URL } from "@calcom/lib/constants";
import type { RouterOutputs } from "@calcom/trpc/react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import Shell from "@calcom/features/shell/Shell";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App as AppType } from "@calcom/types/App";
import type { ButtonProps } from "@calcom/ui";
import type { AppFrontendPayload as App } from "@calcom/types/App";
import { Button, showToast, SkeletonButton, SkeletonText, HeadSeo, Badge } from "@calcom/ui";
import {
Dropdown,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuLabel,
DropdownItem,
Avatar,
} from "@calcom/ui";
import { BookOpen, Check, ExternalLink, File, Flag, Mail, Plus, Shield } from "@calcom/ui/components/icon";
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
/* These app slugs all require Google Cal to be installed */
@@ -60,11 +77,14 @@ const Component = ({
}).format(price);
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
const appCredentials = trpc.viewer.appCredentialsByType.useQuery(
const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false);
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery(
{ appType: type },
{
onSuccess(data) {
setExistingCredentials(data);
onSettled(data) {
const credentialsCount = data?.credentials.length || 0
setShowDisconnectIntegration(data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0);
setExistingCredentials(data?.credentials.map(credential => credential.id) || []);
},
}
);
@@ -85,7 +105,7 @@ const Component = ({
return (
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
{hasDescriptionItems && (
<div className="align-center bg-subtle mb-4 -ml-4 -mr-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:mb-8 md:-ml-8 md:-mr-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
<div className="align-center bg-subtle -ml-4 -mr-4 mb-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:-ml-8 md:-mr-8 md:mb-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
{descriptionItems ? (
descriptionItems.map((descriptionItem, index) =>
typeof descriptionItem === "object" ? (
@@ -141,7 +161,7 @@ const Component = ({
)}
</header>
</div>
{!appCredentials.isLoading ? (
{!appDbQuery.isLoading ? (
isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3">
@@ -166,28 +186,19 @@ const Component = ({
};
}
return (
<Button
StartIcon={Plus}
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
color="primary"
size="base"
data-testid="install-app-button">
{t("install_another")}
</Button>
<InstallAppButtonChild appCategories={categories} userAdminTeams={appDbQuery.data?.userAdminTeams} addAppMutationInput={{ type, variant, slug }} multiInstall {...props} />
);
}}
/>
)}
</div>
) : existingCredentials.length > 0 ? (
) : showDisconnectIntegration ? (
<DisconnectIntegration
buttonProps={{ color: "secondary" }}
label={t("disconnect")}
credentialId={existingCredentials[0]}
onSuccess={() => {
appCredentials.refetch();
appDbQuery.refetch();
}}
/>
) : (
@@ -206,15 +217,7 @@ const Component = ({
};
}
return (
<Button
data-testid="install-app-button"
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
color="primary"
size="base">
{t("install_app")}
</Button>
<InstallAppButtonChild appCategories={categories} userAdminTeams={appDbQuery.data?.userAdminTeams} addAppMutationInput={{type, variant, slug}} credentials={appDbQuery.data?.credentials} {...props} />
);
}}
/>
@@ -260,7 +263,7 @@ const Component = ({
)}
</span>
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("contact")}</h4>
<h4 className="text-emphasis mb-2 mt-8 font-semibold ">{t("contact")}</h4>
<ul className="prose-sm -ml-1 -mr-1 leading-5">
{docs && (
<li>
@@ -269,7 +272,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis text-sm font-normal no-underline hover:underline"
href={docs}>
<BookOpen className="text-subtle mr-1 -mt-1 inline h-4 w-4" />
<BookOpen className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
@@ -281,7 +284,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={website}>
<ExternalLink className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<ExternalLink className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
@@ -293,7 +296,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={"mailto:" + email}>
<Mail className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<Mail className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{email}
</a>
@@ -306,7 +309,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={tos}>
<File className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<File className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
@@ -318,7 +321,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={privacy}>
<Shield className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<Shield className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
@@ -385,3 +388,107 @@ export default function App(props: {
</Shell>
);
}
const InstallAppButtonChild = ({
userAdminTeams,
addAppMutationInput,
appCategories,
multiInstall,
credentials,
...props
}: {
userAdminTeams?: UserAdminTeams;
addAppMutationInput: { type: App["type"]; variant: string; slug: string };
appCategories: string[];
multiInstall?: boolean;
credentials?: RouterOutputs["viewer"]["appCredentialsByType"]["credentials"];
} & ButtonProps) => {
const { t } = useLocale();
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
if (data?.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error");
},
});
if (!userAdminTeams?.length || appCategories.some((category) => category === "calendar")) {
return <Button
data-testid="install-app-button"
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
color="primary"
size="base">
{multiInstall ? t("install_another") : t("install_app")}
</Button>
}
return (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
data-testid="install-app-button"
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
color="primary"
size="base">
{multiInstall ? t("install_another") : t("install_app")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
onInteractOutside={(event) => {
if (mutation.isLoading) event.preventDefault();
}}
>
{mutation.isLoading && (
<div className="z-1 fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<Spinner />
</div>
)}
<DropdownMenuLabel>{t("install_app_on")}</DropdownMenuLabel>
{userAdminTeams.map((team) => {
const isInstalled = credentials &&
credentials.some((credential) =>
credential?.teamId ? credential?.teamId === team.id : credential.userId === team.id
)
return (
<DropdownItem
type="button"
data-testid={team.isUser ? "install-app-button-personal" : "anything else"}
key={team.id}
disabled={
isInstalled
}
StartIcon={(props) => (
<Avatar
alt={team.logo || ""}
imageSrc={team.logo || `${CAL_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
size="sm"
{...props}
/>
)}
onClick={() => {
mutation.mutate(
team.isUser ? addAppMutationInput : { ...addAppMutationInput, teamId: team.id }
);
}}>
<p>{team.name}{" "}
{isInstalled &&
`(${t("installed")})`}</p>
</DropdownItem>
)})}
</DropdownMenuContent>
</DropdownMenuPortal>
</Dropdown>
);
};
@@ -28,8 +28,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
{emptyStore ? (
<EmptyScreen
Icon={AlertCircle}
headline={t("no_apps")}
description={isAdmin ? "You can enable apps in the settings" : ""}
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
buttonText={isAdmin ? t("apps_settings") : ""}
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
/>
@@ -1,66 +1,117 @@
import { z } from "zod";
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import type {
DefaultEventLocationType,
EventLocationTypeFromApp,
LocationObject,
} from "@calcom/app-store/locations";
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "@calcom/ui";
import { Link } from "@calcom/ui/components/icon";
import type { Props } from "./pages/AvailabilityPage";
const excludeNullValues = (value: unknown) => !!value;
export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) {
function RenderIcon({
eventLocationType,
isTooltip,
}: {
eventLocationType: DefaultEventLocationType | EventLocationTypeFromApp;
isTooltip: boolean;
}) {
return (
<img
src={eventLocationType.iconUrl}
className={classNames(
"me-[10px] h-4 w-4 opacity-70 dark:opacity-100",
!eventLocationType.iconUrl?.startsWith("/app-store") ? "dark:invert-[.65]" : "",
!eventLocationType.iconUrl?.startsWith("/app-store") && isTooltip && "invert"
)}
alt={`${eventLocationType.label} icon`}
/>
);
}
function RenderLocationTooltip({ locations }: { locations: LocationObject[] }) {
const { t } = useLocale();
const renderLocations = locations.map((location, index) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
// It's possible that the location app got uninstalled
return null;
return (
<div className="my-2 me-2 flex w-full flex-col space-y-3 break-words">
<p>{t("select_on_next_step")}</p>
{locations.map(
(
location: Pick<Partial<LocationObject>, "link" | "address"> &
Omit<LocationObject, "link" | "address">,
index: number
) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
return null;
}
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
return (
<div key={`${location.type}-${index}`} className="font-sm flex flex-row items-center">
<RenderIcon eventLocationType={eventLocationType} isTooltip />
<p className="line-clamp-1">{translatedLocation}</p>
</div>
);
}
)}
</div>
);
}
export function AvailableEventLocations({ locations }: { locations: LocationObject[] }) {
const { t } = useLocale();
const renderLocations = locations.map(
(
location: Pick<Partial<LocationObject>, "link" | "address"> & Omit<LocationObject, "link" | "address">,
index: number
) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
// It's possible that the location app got uninstalled
return null;
}
if (eventLocationType.variable === "hostDefault") {
return null;
}
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
return (
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
{eventLocationType.iconUrl === "/link.svg" ? (
<Link className="text-default ml-[2px] h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
) : (
<RenderIcon eventLocationType={eventLocationType} isTooltip={false} />
)}
<Tooltip content={translatedLocation}>
<p className="line-clamp-1">{translatedLocation}</p>
</Tooltip>
</div>
);
}
if (eventLocationType.variable === "hostDefault") {
return null;
}
const translateAbleKeys = [
"in_person_attendee_address",
"in_person",
"attendee_phone_number",
"link_meeting",
"organizer_phone_number",
];
const locationKey = z.string().default("").parse(locationKeyToString(location));
const translatedLocation = location.type.startsWith("integrations:")
? eventLocationType.label
: translateAbleKeys.includes(locationKey)
? t(locationKey)
: locationKey;
return (
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
{eventLocationType.iconUrl === "/link.svg" ? (
<Link className="text-default ml-[2px] h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
) : (
<img
src={eventLocationType.iconUrl}
className={classNames(
"ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
!eventLocationType.iconUrl?.startsWith("/app-store") ? "dark:invert-[.65]" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
)}
<Tooltip content={translatedLocation}>
<p className="line-clamp-1">{translatedLocation}</p>
</Tooltip>
</div>
);
});
);
const filteredLocations = renderLocations.filter(excludeNullValues) as JSX.Element[];
return filteredLocations.length ? (
return filteredLocations.length > 1 ? (
<div className="flex flex-row items-center text-sm font-medium">
<img
src="/map-pin.svg"
className={classNames("me-[10px] h-4 w-4 opacity-70 dark:invert")}
alt="map-pin"
/>
<Tooltip content={<RenderLocationTooltip locations={locations} />}>
<p className="line-clamp-1">
{t("location_options", {
locationCount: filteredLocations.length,
})}
</p>
</Tooltip>
</div>
) : filteredLocations.length === 1 ? (
<div className="text-default mr-6 flex w-full flex-col space-y-4 break-words text-sm">
{filteredLocations}
</div>
+21 -10
View File
@@ -66,6 +66,10 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
);
const reserveSlot = (slot: Slot) => {
// Prevent double clicking
if (reserveSlotMutation.isLoading || reserveSlotMutation.isSuccess) {
return;
}
reserveSlotMutation.mutate({
slotUtcStartDate: slot.time,
eventTypeId,
@@ -132,8 +136,20 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
let slotFull, notEnoughSeats;
if (slot.attendees && seatsPerTimeSlot) slotFull = slot.attendees >= seatsPerTimeSlot;
if (slot.attendees && bookingAttendees && seatsPerTimeSlot)
if (slot.attendees && bookingAttendees && seatsPerTimeSlot) {
notEnoughSeats = slot.attendees + bookingAttendees > seatsPerTimeSlot;
}
const isHalfFull =
slot.attendees && seatsPerTimeSlot && slot.attendees / seatsPerTimeSlot >= 0.5;
const isNearlyFull =
slot.attendees && seatsPerTimeSlot && slot.attendees / seatsPerTimeSlot >= 0.83;
const colorClass = isNearlyFull
? "text-rose-600"
: isHalfFull
? "text-yellow-500"
: "text-emerald-400";
return (
<div data-slot-owner={(slot.userIds || []).join(",")} key={`${dayjs(slot.time).format()}`}>
@@ -144,7 +160,9 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
className={classNames(
"text-default bg-default border-subtle mb-2 block rounded-sm border py-2 font-medium opacity-25",
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}>
)}
data-testid="time"
data-disabled="true">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{notEnoughSeats ? (
<p className="text-sm">{t("not_enough_seats")}</p>
@@ -165,14 +183,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
data-disabled="false">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
<p className={`${colorClass} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot}{" "}
{t("seats_available", {
@@ -1,162 +0,0 @@
import type { FC, ReactNode } from "react";
import { useEffect } from "react";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums";
import { Badge } from "@calcom/ui";
import { CheckSquare, Clock } from "@calcom/ui/components/icon";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { UserAvatars } from "@components/booking/UserAvatars";
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
import type { AvailabilityPageProps } from "../../pages/[user]/[type]";
import type { BookPageProps } from "../../pages/[user]/book";
import type { DynamicAvailabilityPageProps } from "../../pages/d/[link]/[slug]";
import type { HashLinkPageProps } from "../../pages/d/[link]/book";
import type { AvailabilityTeamPageProps } from "../../pages/team/[slug]/[type]";
import type { TeamBookingPageProps } from "../../pages/team/[slug]/book";
import { AvailableEventLocations } from "./AvailableEventLocations";
interface Props {
profile:
| AvailabilityPageProps["profile"]
| HashLinkPageProps["profile"]
| TeamBookingPageProps["profile"]
| BookPageProps["profile"]
| AvailabilityTeamPageProps["profile"]
| DynamicAvailabilityPageProps["profile"];
eventType:
| AvailabilityPageProps["eventType"]
| HashLinkPageProps["eventType"]
| TeamBookingPageProps["eventType"]
| BookPageProps["eventType"]
| AvailabilityTeamPageProps["eventType"]
| DynamicAvailabilityPageProps["eventType"];
isBookingPage?: boolean;
children: ReactNode;
isMobile?: boolean;
rescheduleUid?: string;
}
const BookingDescription: FC<Props> = (props) => {
const { profile, eventType, isBookingPage = false, children } = props;
const { date: bookingDate } = useRouterQuery("date");
const { t } = useLocale();
const { duration, setQuery: setDuration } = useRouterQuery("duration");
useEffect(() => {
if (
!duration ||
isNaN(Number(duration)) ||
(eventType.metadata?.multipleDuration &&
!eventType.metadata?.multipleDuration.includes(Number(duration)))
) {
setDuration(eventType.length);
}
}, [duration, setDuration, eventType.length, eventType.metadata?.multipleDuration]);
let requiresConfirmation = eventType?.requiresConfirmation;
let requiresConfirmationText = t("requires_confirmation");
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold;
if (rcThreshold) {
if (isBookingPage) {
if (dayjs(bookingDate).diff(dayjs(), rcThreshold.unit) > rcThreshold.time) {
requiresConfirmation = false;
}
} else {
requiresConfirmationText = t("requires_confirmation_threshold", {
...rcThreshold,
unit: rcThreshold.unit.slice(0, -1),
});
}
}
return (
<>
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size="sm"
truncateAfter={3}
/>
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">
{eventType.team?.parent?.name} {profile.name}
</h2>
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
{eventType.title}
</h1>
<div className=" text-default flex flex-col space-y-4 text-sm font-medium">
{eventType?.description && (
<div
className={classNames(
"scroll-bar scrollbar-track-w-20 -mx-5 flex max-h-[180px] overflow-y-scroll px-5 ",
isBookingPage && "text-default text-sm font-medium"
)}>
{/* TODO: Fix colors when token is introdcued to DS */}
<div className="max-w-full flex-shrink break-words [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600">
<EventTypeDescriptionSafeHTML eventType={eventType} />
</div>
</div>
)}
{requiresConfirmation && (
<div className={classNames("items-top flex", isBookingPage && "text-default text-sm font-medium")}>
<div>
<CheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
</div>
{requiresConfirmationText}
</div>
)}
<AvailableEventLocations
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
/>
<div
className={classNames(
"flex flex-nowrap text-sm font-medium",
isBookingPage && "text-default",
!eventType.metadata?.multipleDuration && "items-center"
)}>
<Clock
className={classNames(
"ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "mt-[2px]"
)}
/>
{eventType.metadata?.multipleDuration !== undefined ? (
!isBookingPage ? (
<ul className="-mt-1 flex flex-wrap gap-1">
{eventType.metadata.multipleDuration.map((dur, idx) => (
<li key={idx}>
<Badge
variant="gray"
className={classNames(
duration === dur.toString()
? "bg-emphasis border-emphasis text-emphasis "
: "bg-subtle text-default border-transparent ",
"cursor-pointer border"
)}
onClick={() => {
setDuration(dur);
}}>
{dur} {t("minute_timeUnit")}
</Badge>
</li>
))}
</ul>
) : (
`${duration} ${t("minutes")}`
)
) : (
`${eventType.length} ${t("minutes")}`
)}
</div>
{children}
</div>
</>
);
};
export default BookingDescription;
@@ -19,7 +19,7 @@ const BookingDescriptionPayment = (props: {
return (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{paymentAppData.paymentOption === "HOLD" ? (
<>{props.t("no_show_fee_amount", params)}</>
) : (
@@ -10,6 +10,7 @@ import ViewRecordingsDialog from "@calcom/features/ee/video/ViewRecordingsDialog
import classNames from "@calcom/lib/classNames";
import { formatTime } from "@calcom/lib/date-fns";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { BookingStatus } from "@calcom/prisma/enums";
@@ -49,6 +50,8 @@ type BookingItemProps = BookingItem & {
function BookingListItem(booking: BookingItemProps) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const bookerUrl = useBookerUrl();
const user = query.data;
const {
t,
@@ -170,7 +173,7 @@ function BookingListItem(booking: BookingItemProps) {
id: "reschedule",
icon: Clock,
label: t("reschedule_booking"),
href: `/reschedule/${booking.uid}${
href: `${bookerUrl}/reschedule/${booking.uid}${
booking.seatsReferences.length ? `?seatReferenceUid=${getSeatReferenceUid()}` : ""
}`,
},
@@ -374,7 +377,7 @@ function BookingListItem(booking: BookingItemProps) {
</td>
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
{/* Time and Badges for mobile */}
<div className="w-full pt-4 pb-2 sm:hidden">
<div className="w-full pb-2 pt-4 sm:hidden">
<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">
@@ -423,7 +426,7 @@ function BookingListItem(booking: BookingItemProps) {
<span> </span>
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
<Badge className="ms-2 me-2 hidden sm:inline-flex" variant="orange">
<Badge className="me-2 ms-2 hidden sm:inline-flex" variant="orange">
{t("pending_payment")}
</Badge>
)}
@@ -64,7 +64,7 @@ export default function CancelBooking(props: Props) {
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-4 w-full "
className="mb-4 mt-2 w-full "
rows={3}
/>
<div className="flex flex-col-reverse rtl:space-x-reverse ">
@@ -93,7 +93,7 @@ export default function CancelBooking(props: Props) {
headers: {
"Content-Type": "application/json",
},
method: "DELETE",
method: "POST",
});
if (res.status >= 200 && res.status < 300) {
@@ -25,7 +25,7 @@ function SkeletonItem() {
</div>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText className="h-6 w-16" />
<SkeletonText className="h-6 w-32" />
@@ -1,292 +0,0 @@
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedStyles,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import classNames from "@calcom/lib/classNames";
import useGetBrandingColours from "@calcom/lib/getBrandColours";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { trpc } from "@calcom/trpc";
import { HeadSeo, NumberInput, useCalcomTheme } from "@calcom/ui";
import { CreditCard, User, RefreshCcw } from "@calcom/ui/components/icon";
import { timeZone as localStorageTimeZone } from "@lib/clock";
import BookingDescription from "@components/booking/BookingDescription";
import { SlotPicker } from "@components/booking/SlotPicker";
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy"));
const Toaster = dynamic(() => import("react-hot-toast").then((mod) => mod.Toaster), { ssr: false });
/*const SlotPicker = dynamic(() => import("../SlotPicker").then((mod) => mod.SlotPicker), {
ssr: false,
loading: () => <div className="mt-8 px-4 pb-4 sm:mt-0 sm:p-4 md:min-w-[300px] md:px-5 lg:min-w-[455px]" />,
});*/
const TimezoneDropdown = dynamic(() => import("../TimezoneDropdown").then((mod) => mod.TimezoneDropdown), {
ssr: false,
});
const dateQuerySchema = z.object({
rescheduleUid: z.string().optional().default(""),
date: z.string().optional().default(""),
timeZone: z.string().optional().default(""),
seatReferenceUid: z.string().optional(),
});
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
const useBrandColors = ({ brandColor, darkBrandColor }: { brandColor: string; darkBrandColor: string }) => {
const brandTheme = useGetBrandingColours({
lightVal: brandColor,
darkVal: darkBrandColor,
});
useCalcomTheme(brandTheme);
};
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
const router = useRouter();
const isEmbed = useIsEmbed(restProps.isEmbed);
const query = dateQuerySchema.parse(router.query);
const { rescheduleUid } = query;
useTheme(profile.theme);
useBrandColors({
brandColor: profile.brandColor,
darkBrandColor: profile.darkBrandColor,
});
const { t, i18n } = useLocale();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const isBackgroundTransparent = useIsBackgroundTransparent();
const [timeZone, setTimeZone] = useState<string>();
const [timeFormat, setTimeFormat] = useState<TimeFormat>(detectBrowserTimeFormat);
const onTimeFormatChange = (is24Hours: boolean) => {
setTimeFormat(is24Hours ? TimeFormat.TWENTY_FOUR_HOUR : TimeFormat.TWELVE_HOUR);
setIs24hClockInLocalStorage(is24Hours);
};
useEffect(() => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
/*
const telemetry = useTelemetry();
useEffect(() => {
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(
telemetryEventTypes.embedView,
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
);
}
}, [telemetry]); */
const embedUiConfig = useEmbedUiConfig();
// get dynamic user list here
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
const timezoneDropdown = useMemo(
() => <TimezoneDropdown timeZone={timeZone} onChangeTimeZone={setTimeZone} />,
[timeZone]
);
const paymentAppData = getPaymentAppData(eventType);
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
const { data: bookingAttendees } = trpc.viewer.bookings.getBookingAttendees.useQuery(
{
seatReferenceUid: rescheduleUid,
},
{
enabled: !!(rescheduleUid && eventType.seatsPerTimeSlot),
}
);
return (
<>
<HeadSeo
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
meeting={{
title: eventType.title,
profile: { name: `${profile.name}`, image: profile.image },
users: [
...(eventType.users || []).map((user) => ({
name: `${user.name}`,
username: `${user.username}`,
})),
],
}}
nextSeoProps={{
nofollow: eventType.hidden,
noindex: eventType.hidden,
}}
isBrandingHidden={restProps.isBrandingHidden}
/>
<BookingPageTagManager eventType={eventType} />
<div>
<main
className={classNames(
"flex flex-col md:mx-4",
shouldAlignCentrally ? "items-center" : "items-start",
!isEmbed && classNames("bg-subtle dark:bg-default mx-auto my-0 ease-in-out md:my-24")
)}>
<div>
<div
style={availabilityDatePickerEmbedStyles}
className={classNames(
isBackgroundTransparent ? "" : "bg-default dark:bg-muted pb-4 md:pb-0",
"border-booker md:border-booker-width md:rounded-md",
isEmbed && "mx-auto"
)}>
<div className="md:flex">
{showEventTypeDetails && (
<div
className={classNames(
" border-subtle flex flex-col p-5 sm:border-r",
"min-w-full md:w-[230px] md:min-w-[230px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{rescheduleUid && eventType.seatsPerTimeSlot && bookingAttendees && (
<div
className={classNames(
"flex flex-nowrap items-center text-sm font-medium",
" text-default",
"ltr:mr-[10px] rtl:ml-[10px]"
)}>
<User
className={classNames(
"min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]",
"mt-[2px]"
)}
/>{" "}
{t("event_type_seats", { numberOfSeats: bookingAttendees })}
</div>
)}
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<RefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<NumberInput
defaultValue={eventType.recurringEvent.count}
min="1"
max={eventType.recurringEvent.count}
isFullWidth={false}
className="me-2 inline w-16"
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
)}
{paymentAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{paymentAppData.paymentOption === "HOLD" ? (
<>
{t("no_show_fee_amount", {
amount: paymentAppData.price / 100.0,
formatParams: { amount: { currency: paymentAppData.currency } },
})}
</>
) : (
<>
{new Intl.NumberFormat(i18n.language, {
style: "currency",
currency: paymentAppData.currency,
}).format(paymentAppData.price / 100)}
</>
)}
</p>
)}
{timezoneDropdown}
</BookingDescription>
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-4 mb-3 text-default"
data-testid="former_time_p_desktop">
{t("former_time")}
</p>
<p className="text-subtle line-through ">
<CalendarIcon className="ltr:mr-[10px] rtl:ml-[10px] -mt-1 inline-block h-4 w-4 text-subtle" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)*/}
</div>
)}
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? ([
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
].indexOf(profile.weekStart) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
onTimeFormatChange={onTimeFormatChange}
timeZone={timeZone}
users={userList}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
bookingAttendees={bookingAttendees || undefined}
recurringEventCount={recurringEventCount}
/>
</div>
</div>
{/* FIXME: We don't show branding in Embed yet because we need to place branding on top of the main content. Keeping it outside the main content would have visibility issues because outside main content background is transparent */}
{!restProps.isBrandingHidden && !isEmbed && <PoweredBy />}
</div>
</main>
</div>
<Toaster position="bottom-right" />
</>
);
};
export default AvailabilityPage;
@@ -1,704 +0,0 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useSession } from "next-auth/react";
import dynamic from "next/dynamic";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { useForm, useFormContext } from "react-hook-form";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import type { EventLocationType } from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import type { LocationObject } from "@calcom/core/location";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { createBooking, createRecurringBooking } from "@calcom/features/bookings/lib";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import {
getBookingFieldsWithSystemFields,
SystemField,
} from "@calcom/features/bookings/lib/getBookingFields";
import getBookingResponsesSchema, {
getBookingResponsesPartialSchema,
} from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
import { FormBuilderField } from "@calcom/features/form-builder/FormBuilder";
import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, MINUTES_TO_BOOK } from "@calcom/lib/constants";
import useGetBrandingColours from "@calcom/lib/getBrandColours";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { parseDate, parseDateTimeWithTimeZone, parseRecurringDates } from "@calcom/lib/parse-dates";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { trpc } from "@calcom/trpc";
import { Button, Form, Tooltip, useCalcomTheme } from "@calcom/ui";
import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/icon";
import { timeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import BookingDescription from "@components/booking/BookingDescription";
import type { BookPageProps } from "../../../pages/[user]/book";
import type { HashLinkPageProps } from "../../../pages/d/[link]/book";
import type { TeamBookingPageProps } from "../../../pages/team/[slug]/book";
const Toaster = dynamic(() => import("react-hot-toast").then((mod) => mod.Toaster), { ssr: false });
/** These are like 40kb that not every user needs */
const BookingDescriptionPayment = dynamic(
() => import("@components/booking/BookingDescriptionPayment")
) as unknown as typeof import("@components/booking/BookingDescriptionPayment").default;
const useBrandColors = ({ brandColor, darkBrandColor }: { brandColor?: string; darkBrandColor?: string }) => {
const brandTheme = useGetBrandingColours({
lightVal: brandColor,
darkVal: darkBrandColor,
});
useCalcomTheme(brandTheme);
};
type BookingPageProps = BookPageProps | TeamBookingPageProps | HashLinkPageProps;
const BookingFields = ({
fields,
locations,
rescheduleUid,
isDynamicGroupBooking,
}: {
fields: BookingPageProps["eventType"]["bookingFields"];
locations: LocationObject[];
rescheduleUid?: string;
isDynamicGroupBooking: boolean;
}) => {
const { t } = useLocale();
const { watch, setValue } = useFormContext();
const locationResponse = watch("responses.location");
const currentView = rescheduleUid ? "reschedule" : "";
return (
// TODO: It might make sense to extract this logic into BookingFields config, that would allow to quickly configure system fields and their editability in fresh booking and reschedule booking view
<div>
{fields.map((field, index) => {
// During reschedule by default all system fields are readOnly. Make them editable on case by case basis.
// Allowing a system field to be edited might require sending emails to attendees, so we need to be careful
let readOnly =
(field.editable === "system" || field.editable === "system-but-optional") && !!rescheduleUid;
let noLabel = false;
let hidden = !!field.hidden;
const fieldViews = field.views;
if (fieldViews && !fieldViews.find((view) => view.id === currentView)) {
return null;
}
if (field.name === SystemField.Enum.rescheduleReason) {
// rescheduleReason is a reschedule specific field and thus should be editable during reschedule
readOnly = false;
}
if (field.name === SystemField.Enum.smsReminderNumber) {
// `smsReminderNumber` and location.optionValue when location.value===phone are the same data point. We should solve it in a better way in the Form Builder itself.
// I think we should have a way to connect 2 fields together and have them share the same value in Form Builder
if (locationResponse?.value === "phone") {
setValue(`responses.${SystemField.Enum.smsReminderNumber}`, locationResponse?.optionValue);
// Just don't render the field now, as the value is already connected to attendee phone location
return null;
}
// `smsReminderNumber` can be edited during reschedule even though it's a system field
readOnly = false;
}
if (field.name === SystemField.Enum.guests) {
// No matter what user configured for Guests field, we don't show it for dynamic group booking as that doesn't support guests
hidden = isDynamicGroupBooking ? true : !!field.hidden;
}
// We don't show `notes` field during reschedule
if (
(field.name === SystemField.Enum.notes || field.name === SystemField.Enum.guests) &&
!!rescheduleUid
) {
return null;
}
// Dynamically populate location field options
if (field.name === SystemField.Enum.location && field.type === "radioInput") {
if (!field.optionsInputs) {
throw new Error("radioInput must have optionsInputs");
}
const optionsInputs = field.optionsInputs;
// TODO: Instead of `getLocationOptionsForSelect` options should be retrieved from dataStore[field.getOptionsAt]. It would make it agnostic of the `name` of the field.
const options = getLocationOptionsForSelect(locations, t);
options.forEach((option) => {
const optionInput = optionsInputs[option.value as keyof typeof optionsInputs];
if (optionInput) {
optionInput.placeholder = option.inputPlaceholder;
}
});
field.options = options.filter(
(location): location is NonNullable<(typeof options)[number]> => !!location
);
// If we have only one option and it has an input, we don't show the field label because Option name acts as label.
// e.g. If it's just Attendee Phone Number option then we don't show `Location` label
if (field.options.length === 1) {
if (field.optionsInputs[field.options[0].value]) {
noLabel = true;
} else {
// If there's only one option and it doesn't have an input, we don't show the field at all because it's visible in the left side bar
hidden = true;
}
}
}
const label = noLabel ? "" : field.label || t(field.defaultLabel || "");
const placeholder = field.placeholder || t(field.defaultPlaceholder || "");
return (
<FormBuilderField
className="mb-4"
field={{ ...field, label, placeholder, hidden }}
readOnly={readOnly}
key={index}
/>
);
})}
</div>
);
};
const routerQuerySchema = z
.object({
timeFormat: z.nativeEnum(TimeFormat),
rescheduleUid: z.string().optional(),
date: z
.string()
.optional()
.transform((date) => {
if (date === undefined) {
return null;
}
return date;
}),
})
.passthrough();
const BookingPage = ({
eventType,
booking,
currentSlotBooking,
profile,
isDynamicGroupBooking,
recurringEventCount,
hasHashedBookingLink,
hashedLink,
...restProps
}: BookingPageProps) => {
const removeSelectedSlotMarkMutation = trpc.viewer.public.slots.removeSelectedSlotMark.useMutation();
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
const { t, i18n } = useLocale();
const { duration: queryDuration } = useRouterQuery("duration");
const { date: queryDate } = useRouterQuery("date");
const isEmbed = useIsEmbed(restProps.isEmbed);
const embedUiConfig = useEmbedUiConfig();
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();
const { data: session } = useSession();
const isBackgroundTransparent = useIsBackgroundTransparent();
const telemetry = useTelemetry();
const { timezone } = useTimePreferences();
const reserveSlot = () => {
if (queryDuration) {
reserveSlotMutation.mutate({
eventTypeId: eventType.id,
slotUtcStartDate: dayjs(queryDate).utc().format(),
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
bookingUid: currentSlotBooking?.uid,
});
}
};
// Define duration now that we support multiple duration eventTypes
let duration = eventType.length;
if (
queryDuration &&
!isNaN(Number(queryDuration)) &&
eventType.metadata?.multipleDuration &&
eventType.metadata?.multipleDuration.includes(Number(queryDuration))
) {
duration = Number(queryDuration);
}
useEffect(() => {
/* if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(
telemetryEventTypes.embedView,
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
);
} */
reserveSlot();
const interval = setInterval(reserveSlot, parseInt(MINUTES_TO_BOOK) * 60 * 1000 - 2000);
return () => {
clearInterval(interval);
removeSelectedSlotMarkMutation.mutate();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
const { uid } = responseData;
if ("paymentUid" in responseData && !!responseData.paymentUid) {
return await router.push(
createPaymentLink({
paymentUid: responseData.paymentUid,
date,
name: bookingForm.getValues("responses.name"),
email: bookingForm.getValues("responses.email"),
absolute: false,
})
);
}
const query = {
isSuccessBookingPage: true,
email: bookingForm.getValues("responses.email"),
eventTypeSlug: eventType.slug,
seatReferenceUid: "seatReferenceUid" in responseData ? responseData.seatReferenceUid : null,
...(rescheduleUid && booking?.startTime && { formerTime: booking.startTime.toString() }),
};
return bookingSuccessRedirect({
router,
successRedirectUrl: eventType.successRedirectUrl,
query,
bookingUid: uid,
});
},
});
const recurringMutation = useMutation(createRecurringBooking, {
onSuccess: async (responseData = []) => {
const { uid } = responseData[0] || {};
const query = {
isSuccessBookingPage: true,
allRemainingBookings: true,
email: bookingForm.getValues("responses.email"),
eventTypeSlug: eventType.slug,
formerTime: booking?.startTime.toString(),
};
return bookingSuccessRedirect({
router,
successRedirectUrl: eventType.successRedirectUrl,
query,
bookingUid: uid,
});
},
});
const {
data: { timeFormat, rescheduleUid, date },
} = useTypedQuery(routerQuerySchema);
useTheme(profile.theme);
useBrandColors({
brandColor: profile.brandColor,
darkBrandColor: profile.darkBrandColor,
});
const querySchema = getBookingResponsesPartialSchema({
eventType: {
bookingFields: getBookingFieldsWithSystemFields(eventType),
},
view: rescheduleUid ? "reschedule" : "booking",
});
const parsedQuery = querySchema.parse({
...router.query,
// `guest` because we need to support legacy URL with `guest` query param support
// `guests` because the `name` of the corresponding bookingField is `guests`
guests: router.query.guests || router.query.guest,
});
// it would be nice if Prisma at some point in the future allowed for Json<Location>; as of now this is not the case.
const locations: LocationObject[] = useMemo(
() => (eventType.locations as LocationObject[]) || [],
[eventType.locations]
);
const [isClientTimezoneAvailable, setIsClientTimezoneAvailable] = useState(false);
useEffect(() => {
// THis is to fix hydration error that comes because of different timezone on server and client
setIsClientTimezoneAvailable(true);
}, []);
const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id;
// There should only exists one default userData variable for primaryAttendee.
const defaultUserValues = {
email: rescheduleUid ? booking?.attendees[0].email : parsedQuery["email"],
name: rescheduleUid ? booking?.attendees[0].name : parsedQuery["name"],
};
const defaultValues = () => {
if (!rescheduleUid) {
const defaults = {
responses: {} as Partial<z.infer<typeof bookingFormSchema>["responses"]>,
};
const responses = eventType.bookingFields.reduce((responses, field) => {
return {
...responses,
[field.name]: parsedQuery[field.name],
};
}, {});
defaults.responses = {
...responses,
name: defaultUserValues.name || (!loggedInIsOwner && session?.user?.name) || "",
email: defaultUserValues.email || (!loggedInIsOwner && session?.user?.email) || "",
};
return defaults;
}
if (!booking || !booking.attendees.length) {
return {};
}
const primaryAttendee = booking.attendees[0];
if (!primaryAttendee) {
return {};
}
const defaults = {
responses: {} as Partial<z.infer<typeof bookingFormSchema>["responses"]>,
};
const responses = eventType.bookingFields.reduce((responses, field) => {
return {
...responses,
[field.name]: booking.responses[field.name],
};
}, {});
defaults.responses = {
...responses,
name: defaultUserValues.name || (!loggedInIsOwner && session?.user?.name) || "",
email: defaultUserValues.email || (!loggedInIsOwner && session?.user?.email) || "",
};
return defaults;
};
const bookingFormSchema = z
.object({
responses: getBookingResponsesSchema({
eventType: { bookingFields: getBookingFieldsWithSystemFields(eventType) },
view: rescheduleUid ? "reschedule" : "booking",
}),
})
.passthrough();
type BookingFormValues = {
locationType?: EventLocationType["type"];
responses: z.infer<typeof bookingFormSchema>["responses"];
};
const bookingForm = useForm<BookingFormValues>({
defaultValues: defaultValues(),
resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
});
// Calculate the booking date(s)
let recurringStrings: string[] = [],
recurringDates: Date[] = [];
if (eventType.recurringEvent?.freq && recurringEventCount !== null) {
[recurringStrings, recurringDates] = parseRecurringDates(
{
startDate: date,
timeZone: timeZone(),
recurringEvent: eventType.recurringEvent,
recurringCount: parseInt(recurringEventCount.toString()),
selectedTimeFormat: timeFormat,
},
i18n.language
);
}
const bookEvent = (bookingValues: BookingFormValues) => {
telemetry.event(
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
{ isTeamBooking: document.URL.includes("team/") }
);
// "metadata" is a reserved key to allow for connecting external users without relying on the email address.
// <...url>&metadata[user_id]=123 will be send as a custom input field as the hidden type.
// @TODO: move to metadata
const metadata = Object.keys(router.query)
.filter((key) => key.startsWith("metadata"))
.reduce(
(metadata, key) => ({
...metadata,
[key.substring("metadata[".length, key.length - 1)]: router.query[key],
}),
{}
);
if (recurringDates.length) {
// Identify set of bookings to one intance of recurring event to support batch changes
const recurringEventId = uuidv4();
const recurringBookings = recurringDates.map((recurringDate) => ({
...bookingValues,
start: dayjs(recurringDate).utc().format(),
end: dayjs(recurringDate).utc().add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
recurringEventId,
// Added to track down the number of actual occurrences selected by the user
recurringCount: recurringDates.length,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
user: router.query.user,
metadata,
hasHashedBookingLink,
hashedLink,
}));
recurringMutation.mutate(recurringBookings);
} else {
mutation.mutate({
...bookingValues,
start: dayjs(date).utc().format(),
end: dayjs(date).utc().add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
bookingUid: (router.query.bookingUid as string) || booking?.uid,
user: router.query.user,
metadata,
hasHashedBookingLink,
hashedLink,
seatReferenceUid: router.query.seatReferenceUid as string,
});
}
};
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
return (
<>
<Head>
<title>
{rescheduleUid
? t("booking_reschedule_confirmation", {
eventTypeTitle: eventType.title,
profileName: profile.name,
})
: t("booking_confirmation", {
eventTypeTitle: eventType.title,
profileName: profile.name,
})}{" "}
| {APP_NAME}
</title>
<link rel="icon" href="/favico.ico" />
</Head>
<BookingPageTagManager eventType={eventType} />
<main
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "" : "sm:my-24",
"my-0 max-w-3xl"
)}>
<div
className={classNames(
"main",
isBackgroundTransparent ? "" : "bg-default dark:bg-muted",
"border-booker sm:border-booker-width rounded-md"
)}>
<div className="sm:flex">
{showEventTypeDetails && (
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="text-default items-start text-sm font-medium">
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
</p>
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Calendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<div className="text-sm font-medium">
{isClientTimezoneAvailable &&
(rescheduleUid || !eventType.recurringEvent?.freq) &&
`${parseDate(date, i18n.language, { selectedTimeFormat: timeFormat })}`}
{isClientTimezoneAvailable &&
!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className=" text-sm">
+ {t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Calendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{isClientTimezoneAvailable &&
typeof booking.startTime === "string" &&
parseDateTimeWithTimeZone(booking.startTime, i18n.language, timezone, {
selectedTimeFormat: timeFormat,
})}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm">
<User
className={`ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] ${
currentSlotBooking &&
currentSlotBooking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: currentSlotBooking &&
currentSlotBooking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
currentSlotBooking &&
currentSlotBooking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: currentSlotBooking &&
currentSlotBooking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
} mb-2 font-medium`}>
{currentSlotBooking
? eventType.seatsPerTimeSlot - currentSlotBooking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot}{" "}
{t("seats_available", {
count: currentSlotBooking
? eventType.seatsPerTimeSlot - currentSlotBooking.attendees.length
: eventType.seatsPerTimeSlot,
})}
</p>
</div>
)}
</BookingDescription>
</div>
)}
<div className={classNames("p-6", showEventTypeDetails ? "sm:w-1/2" : "w-full")}>
<Form form={bookingForm} noValidate handleSubmit={bookEvent}>
<BookingFields
isDynamicGroupBooking={isDynamicGroupBooking}
fields={eventType.bookingFields}
locations={locations}
rescheduleUid={rescheduleUid}
/>
<div
className={classNames(
"flex justify-end space-x-2 rtl:space-x-reverse",
// HACK: If the last field is guests, we need to move Cancel, Submit buttons up because "Add Guests" being present on the left and the buttons on the right, spacing is not required
eventType.bookingFields[eventType.bookingFields.length - 1].name ===
SystemField.Enum.guests
? "-mt-4"
: ""
)}>
<Button color="minimal" type="button" onClick={() => router.back()}>
{t("cancel")}
</Button>
<Button
type="submit"
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
loading={mutation.isLoading || recurringMutation.isLoading}>
{rescheduleUid ? t("reschedule") : t("confirm")}
</Button>
</div>
</Form>
{mutation.isError || recurringMutation.isError ? (
<ErrorMessage error={mutation.error || recurringMutation.error} />
) : null}
</div>
</div>
</div>
</main>
<Toaster position="bottom-right" />
</>
);
};
export default BookingPage;
function ErrorMessage({ error }: { error: unknown }) {
const { t } = useLocale();
const { query: { rescheduleUid } = {} } = useRouter();
const router = useRouter();
return (
<div data-testid="booking-fail" className="mt-2 border-l-4 border-blue-400 bg-blue-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<AlertTriangle className="h-5 w-5 text-blue-400" aria-hidden="true" />
</div>
<div className="ms-3">
<p className="text-sm text-blue-700">
{rescheduleUid ? t("reschedule_fail") : t("booking_fail")}{" "}
{error instanceof HttpError || error instanceof Error ? (
<>
{t("can_you_try_again")}{" "}
<span className="cursor-pointer underline" onClick={() => router.back()}>
{t("go_back")}
</span>
.
</> /* t(error.message) */
) : (
"Unknown error"
)}
</p>
</div>
</div>
</div>
);
}
@@ -13,6 +13,7 @@ import {
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationType,
OrganizerDefaultConferencingAppType,
} from "@calcom/app-store/locations";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
@@ -33,12 +34,12 @@ interface ISetLocationDialog {
saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void;
selection?: LocationOption;
booking?: BookingItem;
isTeamEvent?: boolean;
defaultValues?: LocationObject[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: LocationOption | undefined) => void;
setEditingLocationType?: (param: string) => void;
teamId?: number;
}
const LocationInput = (props: {
@@ -78,15 +79,15 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
saveLocation,
selection,
booking,
isTeamEvent,
setShowLocationModal,
isOpenDialog,
defaultValues,
setSelectedLocation,
setEditingLocationType,
teamId,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.viewer.locationOptions.useQuery();
const locationsQuery = trpc.viewer.locationOptions.useQuery({ teamId });
useEffect(() => {
if (selection) {
@@ -102,6 +103,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
locationType: z.string(),
phone: z.string().optional().nullable(),
locationAddress: z.string().optional(),
credentialId: z.number().optional(),
locationLink: z
.string()
.optional()
@@ -172,8 +174,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
}
);
console.log(defaultLocation);
const LocationOptions = (() => {
if (eventLocationType && eventLocationType.organizerInputType && LocationInput) {
if (!eventLocationType.variable) {
@@ -259,7 +259,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
{booking && (
<>
<p className="text-emphasis mt-6 mb-2 ml-1 text-sm font-bold">{t("current_location")}:</p>
<p className="text-emphasis mb-2 ml-1 mt-6 text-sm font-bold">{t("current_location")}:</p>
<p className="text-emphasis mb-2 ml-1 text-sm">
{getHumanReadableLocationValue(booking.location, t)}
</p>
@@ -297,6 +297,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
};
}
if (values.credentialId) {
details = { ...details, credentialId: values.credentialId };
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
@@ -311,8 +314,15 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
query={locationsQuery}
success={({ data }) => {
if (!data.length) return null;
const locationOptions = [...data].filter((option) => {
return !isTeamEvent ? option.label !== "Conferencing" : true;
const locationOptions = [...data].map((option) => {
if (teamId) {
// Let host's Default conferencing App option show for Team Event
return option;
}
return {
...option,
options: option.options.filter((o) => o.value !== OrganizerDefaultConferencingAppType),
};
});
if (booking) {
locationOptions.map((location) =>
@@ -49,7 +49,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="text-subtle text-sm">{t("reschedule_modal_description")}</p>
<p className="text-emphasis mt-6 mb-2 text-sm font-bold">
<p className="text-emphasis mb-2 mt-6 text-sm font-bold">
{t("reason_for_reschedule_request")}
<span className="text-subtle font-normal"> (Optional)</span>
</p>
+1 -1
View File
@@ -35,7 +35,7 @@ const ErrorDebugPanel: React.FC<{ error: Props["error"]; children?: never }> = (
{debugMap.map(([key, value]) => {
if (value !== undefined) {
return (
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<dt className="text-emphasis text-sm font-bold">{key}</dt>
<dd className="text-emphasis mt-1 text-sm sm:col-span-2 sm:mt-0">{value}</dd>
</div>
@@ -84,7 +84,7 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
backgroundImage: "url(/calendar-preview.svg)",
}}>
<div className="m-auto flex items-center justify-center self-stretch">
<div className="bg-subtle mt-3 ml-11 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
<div className="bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
</div>
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import { z } from "zod";
import type { EventNameObjectType } from "@calcom/core/event";
import { getEventName } from "@calcom/core/event";
@@ -16,6 +17,7 @@ import {
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import { EditableSchema } from "@calcom/features/form-builder/FormBuilderFieldsSchema";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
@@ -38,6 +40,7 @@ const generateHashedLink = (id: number) => {
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
const { data: user } = trpc.viewer.me.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
@@ -49,6 +52,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const bookingFields: Prisma.JsonObject = {};
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
const selectedThemeIsDark =
user?.theme === "dark" ||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
eventType.bookingFields.forEach(({ name }) => {
bookingFields[name] = name + " input";
@@ -81,6 +87,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
return {
...field,
hidden: !enabled,
editable: (!enabled ? "system-but-hidden" : "system-but-optional") as z.infer<
typeof EditableSchema
>,
};
}
return field;
@@ -153,7 +162,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
color="minimal"
size="sm"
aria-label="edit custom name"
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
onClick={() => setShowEventNameTip((old) => !old)}>
<Edit className="h-4 w-4" />
</Button>
@@ -162,7 +171,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</div>
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
<div>
<BookerLayoutSelector fallbackToUserSettings />
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
</div>
<hr className="border-subtle" />
<FormBuilder
@@ -216,7 +225,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
onChange(e ? value : "");
}}>
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
<div className="lg:-ml-2 lg:-mb-2">
<div className="lg:-mb-2 lg:-ml-2">
<TextField
className="w-full"
label={t("redirect_success_booking")}
@@ -268,7 +277,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
color="minimal"
size="sm"
type="button"
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
aria-label="copy link"
onClick={() => {
navigator.clipboard.writeText(placeholderHashedLink);
+65 -13
View File
@@ -16,12 +16,16 @@ export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const { t } = useLocale();
const { data: eventTypeApps, isLoading } = trpc.viewer.apps.useQuery({
const { data: eventTypeApps, isLoading } = trpc.viewer.integrations.useQuery({
extendsFeature: "EventType",
teamId: eventType.team?.id || eventType.parent?.teamId,
});
const methods = useFormContext<FormValues>();
const installedApps = eventTypeApps?.filter((app) => app.credentials.length);
const notInstalledApps = eventTypeApps?.filter((app) => !app.credentials.length);
const installedApps =
eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || [];
const notInstalledApps =
eventTypeApps?.items.filter((app) => !app.userCredentialIds.length && !app.teams.length) || [];
const allAppsData = methods.watch("metadata")?.apps || {};
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
@@ -62,11 +66,54 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
t("locked_fields_member_description")
);
const appsWithTeamCredentials = eventTypeApps?.items.filter((app) => app.teams.length) || [];
const cardsForAppsWithTeams = appsWithTeamCredentials.map((app) => {
const appCards = [];
if (app.userCredentialIds.length) {
appCards.push(
<EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug}
app={app}
eventType={eventType}
{...shouldLockDisableProps("apps")}
/>
);
}
for (const team of app.teams) {
if (team) {
appCards.push(
<EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug + team?.credentialId}
app={{
...app,
// credentialIds: team?.credentialId ? [team.credentialId] : [],
credentialOwner: {
name: team.name,
avatar: team.logo,
teamId: team.teamId,
credentialId: team.credentialId,
},
}}
eventType={eventType}
{...shouldLockDisableProps("apps")}
/>
);
}
}
return appCards;
});
return (
<>
<div>
<div className="before:border-0">
{!installedApps?.length && isManagedEventType && (
{isManagedEventType && (
<Alert
severity="neutral"
className="mb-2"
@@ -92,15 +139,20 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
}
/>
) : null}
{installedApps?.map((app) => (
<EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug}
app={app}
eventType={eventType}
/>
))}
{cardsForAppsWithTeams.map((apps) => apps.map((cards) => cards))}
{installedApps.map((app) => {
if (!app.teams.length)
return (
<EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug}
app={app}
eventType={eventType}
{...shouldLockDisableProps("apps")}
/>
);
})}
</div>
</div>
{!shouldLockDisableProps("apps").disabled && (
@@ -1,5 +1,5 @@
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { useState, memo } from "react";
import { Controller, useFormContext } from "react-hook-form";
import type { OptionProps, SingleValueProps } from "react-select";
import { components } from "react-select";
@@ -71,86 +71,91 @@ const format = (date: Date, hour12: boolean) =>
hourCycle: hour12 ? "h12" : "h24",
}).format(new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss")));
const EventTypeScheduleDetails = ({
isManagedEventType,
selectedScheduleValue,
}: {
isManagedEventType: boolean;
selectedScheduleValue: AvailabilityOption | undefined;
}) => {
const { data: loggedInUser } = useMeQuery();
const timeFormat = loggedInUser?.timeFormat;
const { t, i18n } = useLocale();
const { watch } = useFormContext<FormValues>();
const EventTypeScheduleDetails = memo(
({
isManagedEventType,
selectedScheduleValue,
}: {
isManagedEventType: boolean;
selectedScheduleValue: AvailabilityOption | undefined;
}) => {
const { data: loggedInUser } = useMeQuery();
const timeFormat = loggedInUser?.timeFormat;
const { t, i18n } = useLocale();
const { watch } = useFormContext<FormValues>();
const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
{
scheduleId: scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
isManagedEventType,
},
{ enabled: !!scheduleId || !!loggedInUser?.defaultScheduleId || !!selectedScheduleValue }
);
const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
{
scheduleId:
scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
isManagedEventType,
},
{ enabled: !!scheduleId || !!loggedInUser?.defaultScheduleId || !!selectedScheduleValue }
);
const filterDays = (dayNum: number) =>
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
const filterDays = (dayNum: number) =>
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
return (
<div className="border-default space-y-4 rounded border px-6 pb-4">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
const isAvailable = !!filterDays(index).length;
return (
<li key={day} className="my-6 flex border-transparent last:mb-2">
<span
className={classNames(
"w-20 font-medium sm:w-32 ",
!isAvailable ? "text-subtle line-through" : "text-default"
)}>
{day}
</span>
{isLoading ? (
<SkeletonText className="block h-5 w-60" />
) : isAvailable ? (
<div className="space-y-3 text-right">
{filterDays(index).map((dayRange, i) => (
<div key={i} className="text-default flex items-center leading-4">
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ms-4">-</span>
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
</div>
) : (
<span className="text-subtle ml-6 sm:ml-0">{t("unavailable")}</span>
)}
</li>
);
})}
</ol>
<hr className="border-subtle" />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
{!!schedule?.id && !schedule.isManaged && (
<Button
href={`/availability/${schedule.id}`}
disabled={isLoading}
color="minimal"
EndIcon={ExternalLink}
target="_blank"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
)}
return (
<div className="border-default space-y-4 rounded border px-6 pb-4">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
const isAvailable = !!filterDays(index).length;
return (
<li key={day} className="my-6 flex border-transparent last:mb-2">
<span
className={classNames(
"w-20 font-medium sm:w-32 ",
!isAvailable ? "text-subtle line-through" : "text-default"
)}>
{day}
</span>
{isLoading ? (
<SkeletonText className="block h-5 w-60" />
) : isAvailable ? (
<div className="space-y-3 text-right">
{filterDays(index).map((dayRange, i) => (
<div key={i} className="text-default flex items-center leading-4">
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ms-4">-</span>
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
</div>
) : (
<span className="text-subtle ml-6 sm:ml-0">{t("unavailable")}</span>
)}
</li>
);
})}
</ol>
<hr className="border-subtle" />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
{!!schedule?.id && !schedule.isManaged && (
<Button
href={`/availability/${schedule.id}`}
disabled={isLoading}
color="minimal"
EndIcon={ExternalLink}
target="_blank"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
)}
</div>
</div>
</div>
);
};
);
}
);
EventTypeScheduleDetails.displayName = "EventTypeScheduleDetails";
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
const { t } = useLocale();
@@ -171,7 +171,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
return (
<div className="space-y-8">
<div className="space-y-4 lg:space-y-8">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="beforeBufferTime">
{t("before_event")}
@@ -245,7 +245,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
</div>
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="minimumBookingNotice">
{t("minimum_booking_notice")}
@@ -416,7 +416,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</div>
)}
{period.type === "RANGE" && (
<div className="ms-2 me-2 inline-flex space-x-2 rtl:space-x-reverse">
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
<Controller
name="periodDates"
control={formMethods.control}
@@ -437,7 +437,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
)}
{period.suffix ? <span className="ms-2 me-2">&nbsp;{period.suffix}</span> : null}
{period.suffix ? <span className="me-2 ms-2">&nbsp;{period.suffix}</span> : null}
</div>
);
})}
@@ -117,8 +117,16 @@ export const EventSetupTab = (
const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
const locationOptions = props.locationOptions.filter((option) => {
return !team ? option.label !== "Conferencing" : true;
const locationOptions = props.locationOptions.map((locationOption) => {
const options = locationOption.options.filter((option) => {
// Skip "Organizer's Default App" for non-team members
return !team ? option.label !== t("organizer_default_conferencing_app") : true;
});
return {
...locationOption,
options,
};
});
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
@@ -284,7 +292,7 @@ export const EventSetupTab = (
return (
<li
key={`${location.type}${index}`}
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
className="border-default text-default mb-2 h-9 rounded-md border px-2 py-1.5 hover:cursor-pointer">
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
@@ -298,7 +306,7 @@ export const EventSetupTab = (
)}
alt={`${eventLocationType.label} logo`}
/>
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
<span className="ms-1 line-clamp-1 text-sm">{eventLabel}</span>
</div>
<div className="flex">
<button
@@ -332,7 +340,7 @@ export const EventSetupTab = (
location.type === MeetLocationType && destinationCalendar?.integration !== "google_calendar"
) && (
<div className="text-default flex text-sm">
<Check className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Check className="mr-1.5 mt-0.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal">
<p>
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
@@ -522,7 +530,6 @@ export const EventSetupTab = (
{/* We portal this modal so we can submit the form inside. Otherwise we get issues submitting two forms at once */}
<EditLocationDialog
isTeamEvent={!!team}
isOpenDialog={showLocationModal}
setShowLocationModal={setShowLocationModal}
saveLocation={saveLocation}
@@ -545,6 +552,7 @@ export const EventSetupTab = (
}
setSelectedLocation={setSelectedLocation}
setEditingLocationType={setEditingLocationType}
teamId={eventType.team?.id}
/>
</div>
);
@@ -7,6 +7,7 @@ import { useMemo, useState, Suspense } from "react";
import type { UseFormReturn } from "react-hook-form";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
import Shell from "@calcom/features/shell/Shell";
import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
@@ -210,7 +211,7 @@ function EventTypeSingleLayout({
}
if (isManagedEventType || isChildrenManagedEventType) {
// Removing apps and workflows for manageg event types by admins v1
navigation.splice(-2, 1);
navigation.splice(0, 1);
} else {
navigation.push({
name: "webhooks",
@@ -233,9 +234,11 @@ function EventTypeSingleLayout({
formMethods,
]);
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
const orgBranding = useOrgBrandingValues();
const isOrgEvent = orgBranding?.fullDomain;
const permalink = `${orgBranding?.fullDomain ?? CAL_URL}/${
team ? `${!isOrgEvent ? "team/" : ""}${team.slug}` : eventType.users[0].username
}/${eventType.slug}`;
const embedLink = `${team ? `team/${team.slug}` : eventType.users[0].username}/${eventType.slug}`;
const isManagedEvent = eventType.schedulingType === SchedulingType.MANAGED ? "_managed" : "";
@@ -369,7 +372,7 @@ function EventTypeSingleLayout({
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuSeparator />
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
@@ -404,13 +407,13 @@ function EventTypeSingleLayout({
className="primary-navigation"
tabs={EventTypeTabs}
sticky
linkProps={{ shallow: true }}
linkShallow
itemClassname="items-start"
iconClassName="md:mt-px"
/>
</div>
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
<HorizontalTabs tabs={EventTypeTabs} linkProps={{ shallow: true }} />
<HorizontalTabs tabs={EventTypeTabs} linkShallow />
</div>
<div className="w-full ltr:mr-2 rtl:ml-2">
<div
@@ -0,0 +1,52 @@
import { Trans } from "next-i18next";
import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { ConfirmationDialogContent, Dialog } from "@calcom/ui";
interface ManagedEventDialogProps {
slugExistsChildrenDialogOpen: ChildrenEventType[];
slug: string;
onOpenChange: () => void;
isLoading: boolean;
onConfirm: (e: { preventDefault: () => void }) => void;
}
export default function ManagedEventDialog(props: ManagedEventDialogProps) {
const { t } = useLocale();
const { slugExistsChildrenDialogOpen, slug, onOpenChange, isLoading, onConfirm } = props;
return (
<Dialog open={slugExistsChildrenDialogOpen.length > 0} onOpenChange={onOpenChange}>
<ConfirmationDialogContent
isLoading={isLoading}
variety="warning"
title={t("managed_event_dialog_title", {
slug,
count: slugExistsChildrenDialogOpen.length,
})}
confirmBtnText={t("managed_event_dialog_confirm_button", {
count: slugExistsChildrenDialogOpen.length,
})}
cancelBtnText={t("go_back")}
onConfirm={onConfirm}>
<p className="mt-5">
<Trans
i18nKey="managed_event_dialog_information"
values={{
names: `${slugExistsChildrenDialogOpen
.map((ch) => ch.owner.name)
.slice(0, -1)
.join(", ")} ${
slugExistsChildrenDialogOpen.length > 1 ? t("and") : ""
} ${slugExistsChildrenDialogOpen.map((ch) => ch.owner.name).slice(-1)}`,
slug,
}}
count={slugExistsChildrenDialogOpen.length}
/>
</p>{" "}
<p className="mt-5">{t("managed_event_dialog_clarification")}</p>
</ConfirmationDialogContent>
</Dialog>
);
}
@@ -32,11 +32,11 @@ function SkeletonItem() {
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<Clock className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
<Clock className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
<li className="flex items-center whitespace-nowrap">
<User className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
<User className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
@@ -28,6 +28,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
color="secondary"
disabled={installed}
type="button"
loading={buttonProps?.isLoading}
onClick={(event) => {
// Save cookie key to return url step
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
@@ -17,7 +17,11 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
const { nextStep } = props;
const queryConnectedCalendars = trpc.viewer.connectedCalendars.useQuery({ onboarding: true });
const { t } = useLocale();
const queryIntegrations = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
const queryIntegrations = trpc.viewer.integrations.useQuery({
variant: "calendar",
onlyInstalled: false,
sortByMostPopular: true,
});
const firstCalendar = queryConnectedCalendars.data?.connectedCalendars.find(
(item) => item.calendars && item.calendars?.length > 0
@@ -16,11 +16,12 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
const { data: queryConnectedVideoApps, isLoading } = trpc.viewer.integrations.useQuery({
variant: "conferencing",
onlyInstalled: false,
sortByMostPopular: true,
});
const { t } = useLocale();
const hasAnyInstalledVideoApps = queryConnectedVideoApps?.items.some(
(item) => item.credentialIds.length > 0
(item) => item.userCredentialIds.length > 0
);
return (
@@ -38,7 +39,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
title={item.name}
description={item.description}
logo={item.logo}
installed={item.credentialIds.length > 0}
installed={item.userCredentialIds.length > 0}
/>
)}
</li>
@@ -22,15 +22,13 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
const { nextStep } = props;
const router = useRouter();
let queryAvailability;
if (defaultScheduleId) {
queryAvailability = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId: defaultScheduleId },
{
enabled: router.isReady,
}
);
}
const queryAvailability = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId: defaultScheduleId! },
{
enabled: router.isReady && !!defaultScheduleId,
}
);
const availabilityForm = useForm({
defaultValues: {
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -75,14 +75,12 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
{...form.register("password")}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
/>
<Label className="mt-4"> {t("2fa_code")}</Label>
<TwoFactor center={false} />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ms-2 me-2" disabled={isDisabling}>
<Button type="submit" className="me-2 ms-2" disabled={isDisabling}>
{t("disable")}
</Button>
<Button color="secondary" onClick={onCancel}>
@@ -19,7 +19,7 @@ const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonati
return (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
@@ -1,8 +1,9 @@
import type { BaseSyntheticEvent } from "react";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useCallbackRef } from "@calcom/lib/hooks/useCallbackRef";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form } from "@calcom/ui";
@@ -127,6 +128,17 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
}
}
const handleEnableRef = useCallbackRef(handleEnable);
const totpCode = form.watch("totpCode");
// auto submit 2FA if all inputs have a value
useEffect(() => {
if (totpCode?.trim().length === 6) {
form.handleSubmit(handleEnableRef.current)();
}
}, [form, handleEnableRef, totpCode]);
return (
<Dialog open={true}>
<DialogContent>
@@ -178,19 +190,19 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<Button type="submit" className="ms-2 me-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
<Button type="submit" className="me-2 ms-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button type="submit" className="ms-2 me-2" disabled={isSubmitting}>
<Button type="submit" className="me-2 ms-2" disabled={isSubmitting}>
{t("enable")}
</Button>
</WithStep>
@@ -14,7 +14,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
return (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">{t("2fa")}</h2>
@@ -6,7 +6,7 @@ const TwoFactorModalHeader = ({ title, description }: { title: string; descripti
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<Shield className="text-inverted h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
{title}
</h3>
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -74,7 +74,7 @@ const DisableTwoFactorAuthModal = ({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent title={t("disable_2fa")} description={t("disable_2fa_recommendation")} type="creation">
<Form form={form} handleSubmit={handleDisable}>
<div className="mb-4">
<div className="mb-8">
<PasswordField
labelProps={{
className: "block text-sm font-medium text-default",
@@ -82,21 +82,19 @@ const DisableTwoFactorAuthModal = ({
{...form.register("password")}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
/>
<Label className="mt-4"> {t("2fa_code")}</Label>
<TwoFactor center={false} />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ms-2 me-2" data-testid="disable-2fa" disabled={isDisabling}>
{t("disable")}
</Button>
<DialogFooter showDivider className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
<Button type="submit" className="me-2 ms-2" data-testid="disable-2fa" disabled={isDisabling}>
{t("disable")}
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
@@ -1,10 +1,11 @@
import type { BaseSyntheticEvent } from "react";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useCallbackRef } from "@calcom/lib/hooks/useCallbackRef";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, TextField } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, TextField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -129,6 +130,17 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
}
}
const handleEnableRef = useCallbackRef(handleEnable);
const totpCode = form.watch("totpCode");
// auto submit 2FA if all inputs have a value
useEffect(() => {
if (totpCode?.trim().length === 6) {
form.handleSubmit(handleEnableRef.current)();
}
}, [form, handleEnableRef, totpCode]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent title={t("enable_2fa")} description={setupDescriptions[step]} type="creation">
@@ -150,20 +162,20 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<>
<div className="flex justify-center">
<div className="-mt-3 flex justify-center">
{
// eslint-disable-next-line @next/next/no-img-element
<img src={dataUri} alt="" />
}
</div>
<p data-testid="two-factor-secret" className="text-center font-mono text-xs">
<p data-testid="two-factor-secret" className="mb-4 text-center font-mono text-xs">
{secret}
</p>
</>
</WithStep>
<Form handleSubmit={handleEnable} form={form}>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<div className="mb-4">
<div className="-mt-4 pb-2">
<TwoFactor center />
{errorMessage && (
@@ -173,11 +185,14 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
)}
</div>
</WithStep>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogFooter className="mt-8 sm:flex sm:flex-row-reverse" showDivider>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
@@ -187,20 +202,17 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
<Button
type="submit"
data-testid="goto-otp-screen"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button type="submit" className="ms-2 me-2" data-testid="enable-2fa" disabled={isSubmitting}>
<Button type="submit" className="me-2 ms-2" data-testid="enable-2fa" disabled={isSubmitting}>
{t("enable")}
</Button>
</WithStep>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
+1 -1
View File
@@ -111,7 +111,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
<Label htmlFor="username" className={classNames(longWebsiteUrl && "mb-0")}>
<span className="block">{t("username")}</span>
{longWebsiteUrl && (
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 py-1 px-3">
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 px-3 py-1">
{process.env.NEXT_PUBLIC_WEBSITE_URL}
</small>
)}
+14 -19
View File
@@ -1,11 +1,10 @@
import * as RadioGroup from "@radix-ui/react-radio-group";
import classNames from "classnames";
import Link from "next/link";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
const ENTERPRISE_BOOKING_FEE = "$99"; // TODO: get this from a new API endpoint
const ChooseLicense = (
props: {
value: string;
@@ -50,23 +49,19 @@ const ChooseLicense = (
</ul>
</div>
</RadioGroup.Item>
<RadioGroup.Item value="EE">
<div
className={classNames(
"bg-default cursor-pointer space-y-2 rounded-md border p-4 hover:border-black",
value === "EE" && "ring-2 ring-black"
)}>
<h2 className="font-cal text-emphasis text-xl">{t("ee_enterprise_license")}</h2>
<p className="font-medium text-green-800">
{t("enterprise_booking_fee", { enterprise_booking_fee: ENTERPRISE_BOOKING_FEE })}
</p>
<p className="text-subtle">{t("enterprise_license_includes")}</p>
<ul className="text-subtle ml-4 list-disc text-left text-xs">
<li>{t("no_need_to_keep_your_code_open_source")}</li>
<li>{t("repackage_rebrand_resell")}</li>
<li>{t("a_vast_suite_of_enterprise_features")}</li>
</ul>
</div>
<RadioGroup.Item value="EE" disabled>
<Link href="https://cal.com/sales" target="_blank">
<div className={classNames("bg-default h-full cursor-pointer space-y-2 rounded-md border p-4")}>
<h2 className="font-cal text-emphasis text-xl">{t("custom_plan")}</h2>
<p className="font-medium text-green-800">{t("contact_sales")}</p>
<p className="text-subtle">Build on top of Cal.com</p>
<ul className="text-subtle ml-4 list-disc text-left text-xs">
<li>{t("no_need_to_keep_your_code_open_source")}</li>
<li>{t("repackage_rebrand_resell")}</li>
<li>{t("a_vast_suite_of_enterprise_features")}</li>
</ul>
</div>
</Link>
</RadioGroup.Item>
</RadioGroup.Root>
</form>
+1 -1
View File
@@ -27,7 +27,7 @@ const StepDone = (props: {
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
<h2 className="mb-1 mt-6 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
</form>
+6 -11
View File
@@ -1,7 +1,6 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
@@ -12,27 +11,23 @@ type TeamType = NonNullable<TeamWithMembers>;
type MembersType = TeamType["members"];
type MemberType = MembersType[number] & { safeBio: string | null };
type TeamTypeWithSafeHtml = Omit<TeamType, "members"> & { members: MemberType[] };
type TeamTypeWithSafeHtml = Omit<TeamType, "members" | "inviteToken"> & { members: MemberType[] };
const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => {
const { t } = useLocale();
const router = useRouter();
const isBioEmpty = !member.bio || !member.bio.replace("<p><br></p>", "").length;
// slug is a route parameter, we don't want to forward it to the next route
const { slug: _slug, ...queryParamsToForward } = router.query;
// We don't want to forward orgSlug and user which are route params to the next route
const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = router.query;
return (
<Link key={member.id} href={{ pathname: `/${member.username}`, query: queryParamsToForward }}>
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
<Avatar
size="md"
alt={member.name || ""}
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
/>
<section className="line-clamp-4 mt-2 w-full space-y-1">
<Avatar size="md" alt={member.name || ""} imageSrc={"/" + member.username + "/avatar.png"} />
<section className="mt-2 line-clamp-4 w-full space-y-1">
<p className="text-default font-medium">{member.name}</p>
<div className="line-clamp-3 text-subtle overflow-ellipsis text-sm font-normal">
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
{!isBioEmpty ? (
<>
<div
+2 -2
View File
@@ -23,7 +23,7 @@ const EditableHeading = function EditableHeading({
<label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && (
<Edit2 className=" text-subtle group-hover:text-subtle ml-1 -mt-px inline h-3 w-3" />
<Edit2 className=" text-subtle group-hover:text-subtle -mt-px ml-1 inline h-3 w-3" />
)}
<input
{...passThroughProps}
@@ -31,7 +31,7 @@ const EditableHeading = function EditableHeading({
value={value}
required
className={classNames(
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
"text-emphasis hover:text-default focus:text-emphasis absolute left-0 top-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
)}
onFocus={(e) => {
setIsEditing(true);
+1 -1
View File
@@ -6,7 +6,7 @@ export default function InfoBadge({ content }: { content: string }) {
<>
<Tooltip side="top" content={content}>
<span title={content}>
<Info className="text-subtle relative top-px left-1 right-1 mt-px h-4 w-4" />
<Info className="text-subtle relative left-1 right-1 top-px mt-px h-4 w-4" />
</span>
</Tooltip>
</>
+1 -1
View File
@@ -14,7 +14,7 @@ export default function ModalContainer(
}>
) {
return (
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
<DialogContent>
<div
@@ -1,10 +1,12 @@
import classNames from "classnames";
import { debounce, noop } from "lodash";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import type { RefCallback } from "react";
import { useEffect, useMemo, useState } from "react";
import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { fetchUsername } from "@calcom/lib/fetchUsername";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -45,6 +47,7 @@ const obtainNewUsernameChangeCondition = ({
const PremiumTextfield = (props: ICustomUsernameProps) => {
const { t } = useLocale();
const { data: session, update } = useSession();
const {
currentUsername,
setCurrentUsername = noop,
@@ -93,6 +96,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
const updateUsername = trpc.viewer.updateProfile.useMutation({
onSuccess: async () => {
onSuccessMutation && (await onSuccessMutation());
await update();
setOpenDialogSaveUsername(false);
},
onError: (error) => {
@@ -116,7 +120,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
const paymentLink = `/api/integrations/stripepayment/subscription?intentUsername=${
inputUsernameValue || usernameFromStripe
}&action=${usernameChangeCondition}&callbackUrl=${router.asPath}`;
}&action=${usernameChangeCondition}&callbackUrl=${WEBAPP_URL}${router.asPath}`;
const ActionButtons = () => {
if (paymentRequired) {
@@ -224,7 +228,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
}}
data-testid="username-input"
/>
<div className="absolute top-0 right-2 flex flex-row">
<div className="absolute right-2 top-0 flex flex-row">
<span
className={classNames(
"mx-2 py-2",
@@ -1,5 +1,6 @@
import classNames from "classnames";
import { debounce, noop } from "lodash";
import { useSession } from "next-auth/react";
import type { RefCallback } from "react";
import { useEffect, useMemo, useState } from "react";
@@ -23,6 +24,7 @@ interface ICustomUsernameProps {
const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.ComponentProps<typeof TextField>>) => {
const { t } = useLocale();
const { data: session, update } = useSession();
const {
currentUsername,
setCurrentUsername = noop,
@@ -69,6 +71,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
onSuccessMutation && (await onSuccessMutation());
setOpenDialogSaveUsername(false);
setCurrentUsername(inputUsernameValue);
await update();
},
onError: (error) => {
onErrorMutation && onErrorMutation(error);
@@ -80,7 +83,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
const ActionButtons = () => {
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
<div className="ms-2 me-2 flex flex-row space-x-2">
<div className="me-2 ms-2 flex flex-row space-x-2">
<Button
type="button"
onClick={() => setOpenDialogSaveUsername(true)}
@@ -135,7 +138,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
/>
{currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex flex-row">
<span className={classNames("mx-2 py-2.5")}>
<span className={classNames("mx-2 py-3.5")}>
{usernameIsAvailable ? <Check className="h-4 w-4" /> : <></>}
</span>
</div>
@@ -156,23 +159,23 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
<DialogContent type="confirmation" Icon={Edit2} title={t("confirm_username_change_dialog_title")}>
<div className="flex flex-row">
<div className="mb-4 w-full pt-1">
<div className="bg-subtle flex w-full flex-wrap rounded-sm py-3 text-sm">
<div className="flex-1 px-2">
<div className="bg-subtle flex w-full flex-wrap gap-6 rounded-sm px-2 py-3 text-sm">
<div>
<p className="text-subtle">{t("current_username")}</p>
<p className="text-emphasis mt-1" data-testid="current-username">
{currentUsername}
</p>
</div>
<div className="ml-6 flex-1">
<div>
<p className="text-subtle" data-testid="new-username">
{t("new_username")}
</p>
<p className="text-emphasis">{inputUsernameValue}</p>
<p className="text-emphasis mt-1">{inputUsernameValue}</p>
</div>
</div>
</div>
</div>
<div className="mt-4 flex flex-row-reverse gap-x-2">
<div className="ml-4 mt-4 flex flex-row-reverse gap-x-2">
<Button
type="button"
loading={updateUsernameMutation.isLoading}
@@ -2,7 +2,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { RouterOutputs } from "@calcom/trpc/react";
@@ -24,9 +24,9 @@ interface UsernameAvailabilityFieldProps {
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
return organization
? organization.slug
? `${organization.slug}.${subdomainSuffix()}`
? getOrgFullDomain(organization.slug, { protocol: false })
: organization.metadata && organization.metadata.requestedSlug
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}`
? getOrgFullDomain(organization.metadata.requestedSlug, { protocol: false })
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
}
@@ -40,11 +40,13 @@ export default function LocationSelect(props: Props<LocationOption, false, Group
name="location"
id="location-select"
components={{
Option: (props) => (
<components.Option {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} />
</components.Option>
),
Option: (props) => {
return (
<components.Option {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} />
</components.Option>
);
},
SingleValue: (props) => (
<components.SingleValue {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} />
+2 -2
View File
@@ -3,7 +3,7 @@ import type { GroupBase, Props, InputProps, SingleValue, MultiValue } from "reac
import ReactSelect, { components } from "react-select";
import classNames from "@calcom/lib/classNames";
import useTheme from "@calcom/lib/hooks/useTheme";
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
export type SelectProps<
Option,
@@ -30,7 +30,7 @@ function Select<
Group extends GroupBase<Option> = GroupBase<Option>
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
const [mounted, setMounted] = useState<boolean>(false);
const { resolvedTheme, forcedTheme } = useTheme();
const { resolvedTheme, forcedTheme } = useGetTheme();
const hasDarkTheme = !forcedTheme && resolvedTheme === "dark";
const darkThemeColors = {
/** Dark Theme starts */
+15
View File
@@ -0,0 +1,15 @@
const getDefaultSubdomain = (url) => {
if (!url.startsWith("http:") && !url.startsWith("https:")) {
// Make it a valid URL. Mabe we can simply return null and opt-out from orgs support till the use a URL scheme.
url = `https://${url}`;
}
const _url = new URL(url);
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-.]+)\.[\w-]+\.\w+)$/);
//console.log(_url.hostname, _url.hostname.match(regex));
return _url.hostname.match(regex)?.groups?.subdomain || null;
};
exports.getSubdomainRegExp = (url) => {
const defaultSubdomain = getDefaultSubdomain(url);
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";
return subdomain;
};
+25 -6
View File
@@ -48,7 +48,18 @@ type AppPropsWithChildren = AppProps & {
children: ReactNode;
};
const CustomI18nextProvider = (props: AppPropsWithChildren) => {
const getEmbedNamespace = (query: ReturnType<typeof useRouter>["query"]) => {
// Mostly embed query param should be available on server. Use that there.
// Use the most reliable detection on client
return typeof window !== "undefined" ? window.getEmbedNamespace() : (query.embed as string) || null;
};
// We dont need to pass nonce to the i18n provider - this was causing x2-x3 re-renders on a hard refresh
type AppPropsWithoutNonce = Omit<AppPropsWithChildren, "pageProps"> & {
pageProps: Omit<AppPropsWithChildren["pageProps"], "nonce">;
};
const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
* We intend to not cache i18n query
@@ -87,7 +98,7 @@ const CalcomThemeProvider = (props: CalcomThemeProps) => {
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
// One such example is our Embeds Demo and Testing page at http://localhost:3100
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
const embedNamespace = typeof window !== "undefined" ? window.getEmbedNamespace() : null;
const embedNamespace = getEmbedNamespace(router.query);
const isEmbedMode = typeof embedNamespace === "string";
return (
@@ -158,10 +169,10 @@ function getThemeProviderProps({
? ThemeSupport.None
: ThemeSupport.App;
const isBookingPageThemSupportRequired = themeSupport === ThemeSupport.Booking;
const isBookingPageThemeSupportRequired = themeSupport === ThemeSupport.Booking;
const themeBasis = props.themeBasis;
if ((isBookingPageThemSupportRequired || isEmbedMode) && !themeBasis) {
if ((isBookingPageThemeSupportRequired || isEmbedMode) && !themeBasis) {
console.warn(
"`themeBasis` is required for booking page theme support. Not providing it will cause theme flicker."
);
@@ -184,7 +195,7 @@ function getThemeProviderProps({
`embed-theme-${embedNamespace}${appearanceIdSuffix}${embedExplicitlySetThemeSuffix}`
: themeSupport === ThemeSupport.App
? "app-theme"
: isBookingPageThemSupportRequired
: isBookingPageThemeSupportRequired
? `booking-theme${appearanceIdSuffix}`
: undefined;
@@ -216,11 +227,19 @@ const AppProviders = (props: AppPropsWithChildren) => {
const session = trpc.viewer.public.session.useQuery().data;
// No need to have intercom on public pages - Good for Page Performance
const isPublicPage = usePublicPage();
const { pageProps, ...rest } = props;
const { _nonce, ...restPageProps } = pageProps;
const propsWithoutNonce = {
pageProps: {
...restPageProps,
},
...rest,
};
const RemainingProviders = (
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<SessionProvider session={session || undefined}>
<CustomI18nextProvider {...props}>
<CustomI18nextProvider {...propsWithoutNonce}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
<CalcomThemeProvider
+2 -1
View File
@@ -5,7 +5,8 @@ import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import type { Maybe } from "@calcom/trpc/server";
import { i18n } from "../../../next-i18next.config";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { i18n } = require("@calcom/config/next-i18next.config");
export function getLocaleFromHeaders(req: IncomingMessage): string {
let preferredLocale: string | null | undefined;
-34
View File
@@ -3,7 +3,6 @@ import { collectEvents } from "next-collect/server";
import type { NextMiddleware } from "next/server";
import { NextResponse, userAgent } from "next/server";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { isIpInBanlist } from "@calcom/lib/getIP";
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
@@ -11,19 +10,6 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
const middleware: NextMiddleware = async (req) => {
const url = req.nextUrl;
const requestHeaders = new Headers(req.headers);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? "");
/**
* We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages
* Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests)
*/
// Make sure we are in the presence of an organization
if (isValidOrgDomain && url.pathname === "/") {
// In the presence of an organization, cover its profile page at "/"
// rewrites for org profile page using team profile page
url.pathname = `/org/${currentOrgDomain}`;
return NextResponse.rewrite(url);
}
if (isIpInBanlist(req) && url.pathname !== "/api/nope") {
// DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback
@@ -83,26 +69,6 @@ const middleware: NextMiddleware = async (req) => {
requestHeaders.set("x-csp-enforce", "true");
}
if (isValidOrgDomain) {
// Match /:slug to determine if it corresponds to org subteam slug or org user slug
const slugs = /^\/([^/]+)(\/[^/]+)?$/.exec(url.pathname);
// In the presence of an organization, if not team profile, a user or team is being accessed
if (slugs) {
const [_, teamName, eventType] = slugs;
// Fetch the corresponding subteams for the entered organization
const getSubteams = await fetch(`${WEBAPP_URL}/api/organizations/${currentOrgDomain}/subteams`);
if (getSubteams.ok) {
const data = await getSubteams.json();
// Treat entered slug as a team if found in the subteams fetched
if (data.slugs.includes(teamName)) {
// Rewriting towards /team/:slug to bring up the team profile within the org
url.pathname = `/team/${teamName}${eventType ?? ""}`;
return NextResponse.rewrite(url);
}
}
}
}
return NextResponse.next({
request: {
headers: requestHeaders,
+165 -109
View File
@@ -1,10 +1,15 @@
require("dotenv").config({ path: "../../.env" });
const CopyWebpackPlugin = require("copy-webpack-plugin");
const os = require("os");
const glob = require("glob");
const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
const { i18n } = require("./next-i18next.config");
const {
orgHostPath,
orgUserRoutePath,
orgUserTypeRoutePath,
orgUserTypeEmbedRoutePath,
} = require("./pagesAndRewritePaths");
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
@@ -70,7 +75,6 @@ const informAboutDuplicateTranslations = () => {
};
informAboutDuplicateTranslations();
const plugins = [];
if (process.env.ANALYZE === "true") {
// only load dependency if env `ANALYZE` was set
@@ -81,17 +85,45 @@ if (process.env.ANALYZE === "true") {
}
plugins.push(withAxiom);
const matcherConfigRootPath = {
has: [
{
type: "host",
value: orgHostPath,
},
],
source: "/",
};
/** Needed to rewrite public booking page, gets all static pages but [user] */
const pages = glob
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
.map((filename) =>
filename
.substr(6)
.replace(/(\.tsx|\.js|\.ts)/, "")
.replace(/\/.*/, "")
)
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
const matcherConfigUserRoute = {
has: [
{
type: "host",
value: orgHostPath,
},
],
source: orgUserRoutePath,
};
const matcherConfigUserTypeRoute = {
has: [
{
type: "host",
value: orgHostPath,
},
],
source: orgUserTypeRoutePath,
};
const matcherConfigUserTypeEmbedRoute = {
has: [
{
type: "host",
value: orgHostPath,
},
],
source: orgUserTypeEmbedRoutePath,
};
/** @type {import("next").NextConfig} */
const nextConfig = {
@@ -183,126 +215,81 @@ const nextConfig = {
return config;
},
async rewrites() {
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
// It would also not match /free/30min/embed because we are ensuring just two slashes
const userTypeRouteRegExp = `/:user((?!${pages.join("|")})[^/]*)/:type`;
const beforeFiles = [
// These rewrites are other than booking pages rewrites and so that they aren't redirected to org pages ensure that they happen in beforeFiles
...(process.env.ORGANIZATIONS_ENABLED
? [
{
...matcherConfigRootPath,
destination: "/team/:orgSlug",
},
{
...matcherConfigUserRoute,
destination: "/org/:orgSlug/:user",
},
{
...matcherConfigUserTypeRoute,
destination: "/org/:orgSlug/:user/:type",
},
{
...matcherConfigUserTypeEmbedRoute,
destination: "/org/:orgSlug/:user/:type/embed",
},
]
: []),
];
let rewrites = [
let afterFiles = [
{
source: "/org/:slug",
destination: "/team/:slug",
},
{
source: "/:user/avatar.png",
destination: "/api/user/avatar?username=:user",
},
{
source: "/team/:teamname/avatar.png",
destination: "/api/user/avatar?teamname=:teamname",
},
{
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
},
{
source: "/router",
destination: "/apps/routing-forms/router",
},
{
source: "/success/:path*",
has: [
{
type: "query",
key: "uid",
value: "(?<uid>.*)",
},
],
destination: "/booking/:uid/:path*",
},
{
source: "/cancel/:path*",
destination: "/booking/:path*",
},
// Keep cookie based booker enabled just in case we disable new-booker globally
// When updating this also update pagesAndRewritePaths.js
...[
{
source: userTypeRouteRegExp,
destination: "/new-booker/:user/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
source: "/:user/avatar.png",
destination: "/api/user/avatar?username=:user",
},
{
source: "/team/:slug/:type",
destination: "/new-booker/team/:slug/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
},
{
source: "/d/:link/:slug",
destination: "/new-booker/d/:link/:slug",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
],
// Keep cookie based booker enabled to test new-booker embed in production
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
destination: "/new-booker/:user/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type/embed",
destination: "/new-booker/team/:slug/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
source: "/router",
destination: "/apps/routing-forms/router",
},
{
source: "/success/:path*",
has: [
{
type: "query",
key: "uid",
value: "(?<uid>.*)",
},
],
destination: "/booking/:uid/:path*",
},
{
source: "/cancel/:path*",
destination: "/booking/:path*",
},
],
/* TODO: have these files being served from another deployment or CDN {
source: "/embed/embed.js",
destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?,
}, */
/**
* Enables new booker using cookie. It works even if NEW_BOOKER_ENABLED_FOR_NON_EMBED, NEW_BOOKER_ENABLED_FOR_EMBED are disabled
*/
];
// Enable New Booker for all Embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
console.log("Enabling New Booker for Embed");
rewrites.push(
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
destination: "/new-booker/:user/:type/embed",
},
{
source: "/team/:slug/:type/embed",
destination: "/new-booker/team/:slug/:type/embed",
},
]
);
}
// Enable New Booker for All but embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
console.log("Enabling New Booker for Non-Embed");
rewrites.push(
...[
{
source: userTypeRouteRegExp,
destination: "/new-booker/:user/:type",
},
{
source: "/team/:slug/:type",
destination: "/new-booker/team/:slug/:type",
},
{
source: "/d/:link/:slug",
destination: "/new-booker/d/:link/:slug",
},
]
);
}
return rewrites;
return {
beforeFiles,
afterFiles,
};
},
async headers() {
return [
@@ -337,6 +324,44 @@ const nextConfig = {
},
],
},
...[
{
...matcherConfigRootPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/team/:orgSlug",
},
],
},
{
...matcherConfigUserRoute,
headers: [
{
key: "X-Cal-Org-path",
value: "/org/:orgSlug/:user",
},
],
},
{
...matcherConfigUserTypeRoute,
headers: [
{
key: "X-Cal-Org-path",
value: "/org/:orgSlug/:user/:type",
},
],
},
{
...matcherConfigUserTypeEmbedRoute,
headers: [
{
key: "X-Cal-Org-path",
value: "/org/:orgSlug/:user/:type/embed",
},
],
},
],
];
},
async redirects() {
@@ -411,6 +436,37 @@ const nextConfig = {
destination: "/event-types?openIntercom=true",
permanent: true,
},
{
source: "/apps/categories/video",
destination: "/apps/categories/conferencing",
permanent: true,
},
{
source: "/apps/installed/video",
destination: "/apps/installed/conferencing",
permanent: true,
},
// OAuth callbacks when sent to localhost:3000(w would be expected) should be redirected to corresponding to WEBAPP_URL
...(process.env.NODE_ENV === "development" &&
// Safer to enable the redirect only when the user is opting to test out organizations
process.env.ORGANIZATIONS_ENABLED &&
// Prevent infinite redirect by checking that we aren't already on localhost
process.env.NEXT_PUBLIC_WEBAPP_URL !== "http://localhost:3000"
? [
{
has: [
{
type: "header",
key: "host",
value: "localhost:3000",
},
],
source: "/api/integrations/:args*",
destination: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/integrations/:args*`,
permanent: false,
},
]
: []),
];
if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") {
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.0.2",
"version": "3.1.3",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -47,8 +47,8 @@
"@next/bundle-analyzer": "^13.1.6",
"@radix-ui/react-avatar": "^1.0.0",
"@radix-ui/react-collapsible": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-id": "^1.0.0",
"@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-radio-group": "^1.0.0",
@@ -58,6 +58,7 @@
"@radix-ui/react-tooltip": "^1.0.0",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tanem/react-nprogress": "^5.0.44",
"@tanstack/react-query": "^4.3.9",
"@tremor/react": "^2.0.0",
"@types/turndown": "^5.0.1",
@@ -88,12 +89,12 @@
"memory-cache": "^0.2.0",
"micro": "^10.0.1",
"mime-types": "^2.1.35",
"next": "^13.2.1",
"next-auth": "^4.20.1",
"next-axiom": "^0.16.0",
"next": "^13.4.6",
"next-auth": "^4.22.1",
"next-axiom": "^0.17.0",
"next-collect": "^0.2.1",
"next-i18next": "^11.3.0",
"next-seo": "^4.26.0",
"next-i18next": "^13.2.2",
"next-seo": "^6.0.0",
"next-themes": "^0.2.0",
"nodemailer": "^6.7.8",
"otplib": "^12.0.1",
@@ -109,7 +110,7 @@
"react-hook-form": "^7.43.3",
"react-hot-toast": "^2.3.0",
"react-intl": "^5.25.1",
"react-live-chat-loader": "^2.7.3",
"react-live-chat-loader": "^2.8.1",
"react-multi-email": "^0.5.3",
"react-phone-input-2": "^2.15.1",
"react-phone-number-input": "^3.2.7",
@@ -162,7 +163,6 @@
"copy-webpack-plugin": "^11.0.0",
"detect-port": "^1.3.0",
"env-cmd": "^10.1.0",
"mockdate": "^3.0.5",
"module-alias": "^2.2.2",
"msw": "^0.42.3",
"postcss": "^8.4.18",
+106 -53
View File
@@ -1,24 +1,40 @@
import type { GetStaticPropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
import { orgDomainConfig, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { DOCS_URL, JOIN_DISCORD, WEBSITE_URL, IS_CALCOM } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HeadSeo } from "@calcom/ui";
import { BookOpen, Check, ChevronRight, FileText, Slack } from "@calcom/ui/components/icon";
import { BookOpen, Check, ChevronRight, FileText, Shield } from "@calcom/ui/components/icon";
import { Discord } from "@calcom/ui/components/icon/Discord";
import PageWrapper from "@components/PageWrapper";
import { ssgInit } from "@server/lib/ssg";
enum pageType {
ORG = "org",
TEAM = "team",
USER = "user",
OTHER = "other",
}
export default function Custom404() {
const { t } = useLocale();
const router = useRouter();
const [username] = router.asPath.replace("%20", "-").split(/[?#]/);
const [username, setUsername] = useState<string>("");
const [currentPageType, setCurrentPageType] = useState<pageType>(pageType.USER);
const links = [
{
title: "Enterprise",
description: "Learn more about organizations and subdomains in our enterprise plan.",
icon: Shield,
href: `${WEBSITE_URL}/enterprise`,
},
{
title: t("documentation"),
description: t("documentation_description"),
@@ -33,15 +49,38 @@ export default function Custom404() {
},
];
const [url, setUrl] = useState(`${WEBSITE_URL}/signup?username=`);
const [url, setUrl] = useState(`${WEBSITE_URL}/signup`);
useEffect(() => {
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
}, [username]);
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host);
const [routerUsername] = router.asPath.replace("%20", "-").split(/[?#]/);
if (!isValidOrgDomain || !currentOrgDomain) {
const splitPath = routerUsername.split("/");
if (splitPath[1] === "team" && splitPath.length === 3) {
// Accessing a non-existent team
setUsername(splitPath[2]);
setCurrentPageType(pageType.TEAM);
setUrl(
`${WEBSITE_URL}/signup?callbackUrl=settings/teams/new%3Fslug%3D${splitPath[2].replace("/", "")}`
);
} else {
setUsername(routerUsername);
setUrl(`${WEBSITE_URL}/signup?username=${routerUsername.replace("/", "")}`);
}
} else {
setUsername(currentOrgDomain);
setCurrentPageType(pageType.ORG);
setUrl(
`${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${currentOrgDomain.replace(
"/",
""
)}`
);
}
}, []);
const isSuccessPage = router.asPath.startsWith("/booking");
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
const isSignup = router.asPath.startsWith("/signup");
const isCalcom = process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com";
/**
* If we're on 404 and the route is insights it means it is disabled
* TODO: Abstract this for all disabled features
@@ -59,7 +98,7 @@ export default function Custom404() {
}}
/>
<div className="min-h-screen bg-white px-4" data-testid="404-page">
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
<div className="text-center">
<p className="text-sm font-semibold uppercase tracking-wide text-black">{t("error_404")}</p>
<h1 className="font-cal mt-2 text-4xl font-extrabold text-gray-900 sm:text-5xl">
@@ -80,6 +119,8 @@ export default function Custom404() {
);
}
if (!username) return null;
return (
<>
<HeadSeo
@@ -91,7 +132,7 @@ export default function Custom404() {
}}
/>
<div className="bg-default min-h-screen px-4" data-testid="404-page">
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
{isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? (
<div>
<div>
@@ -161,18 +202,18 @@ export default function Custom404() {
</li>
<li className="px-4 py-2">
<a
href={JOIN_SLACK}
href={JOIN_DISCORD}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<Slack strokeWidth="1.5" fill="currentColor" className="h-6 w-6" />
<Discord className="text-default h-6 w-6" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
Slack
Discord
</span>
</h3>
<p className="text-subtle text-base">{t("join_our_community")}</p>
@@ -202,13 +243,13 @@ export default function Custom404() {
<h1 className="font-cal text-emphasis mt-2 text-4xl font-extrabold sm:text-5xl">
{isSuccessPage ? "Booking not found" : t("page_doesnt_exist")}
</h1>
{isSubpage ? (
{isSubpage && currentPageType !== pageType.TEAM ? (
<span className="mt-2 inline-block text-lg ">
{t("check_spelling_mistakes_or_go_back")}
</span>
) : isCalcom ? (
) : IS_CALCOM ? (
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
{t("the_username")}{" "}
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
<strong className="text-blue-500">
{new URL(WEBSITE_URL).hostname}
{username}
@@ -217,18 +258,17 @@ export default function Custom404() {
</a>
) : (
<span className="mt-2 inline-block text-lg">
{t("the_username")}{" "}
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
{t("is_still_available")}
</span>
)}
</div>
<div className="mt-12">
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
{t("popular_pages")}
</h2>
{!isSubpage && isCalcom && (
<ul role="list" className="mt-4">
{((!isSubpage && IS_CALCOM) ||
currentPageType === pageType.ORG ||
currentPageType === pageType.TEAM) && (
<ul role="list" className="my-4">
<li className="border-2 border-green-500 px-4 py-2">
<a
href={url}
@@ -245,11 +285,20 @@ export default function Custom404() {
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
{t("register")} <strong className="text-green-500">{username}</strong>
{t("register")}{" "}
<strong className="text-green-500">{`${
currentPageType === pageType.TEAM
? `${new URL(WEBSITE_URL).host}/team/`
: ""
}${username}${
currentPageType === pageType.ORG ? `.${subdomainSuffix()}` : ""
}`}</strong>
</span>
</span>
</h3>
<p className="text-subtle text-base">{t("claim_username_and_schedule_events")}</p>
<p className="text-subtle text-base">
{t(`404_claim_entity_${currentPageType.toLowerCase()}`)}
</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
@@ -258,47 +307,51 @@ export default function Custom404() {
</li>
</ul>
)}
<ul role="list" className="border-subtle divide-subtle mt-4 divide-y">
{links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
{t("popular_pages")}
</h2>
<ul role="list" className="border-subtle divide-subtle divide-y">
{links
.filter((_, idx) => currentPageType === pageType.ORG || idx !== 0)
.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<a
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
</span>
</h3>
<p className="text-subtle text-base">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
</div>
</Link>
</li>
))}
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</h3>
<p className="text-subtle text-base">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
</div>
</a>
</li>
))}
<li className="px-4 py-2">
<a
href={JOIN_SLACK}
href={JOIN_DISCORD}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<Slack strokeWidth="1.5" fill="currentColor" className="h-6 w-6" />
<Discord className="text-default h-6 w-6" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
Slack
Discord
</span>
</h3>
<p className="text-subtle text-base">{t("join_our_community")}</p>
+1 -1
View File
@@ -21,7 +21,7 @@ export default function Error500() {
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
<h1 className="font-cal text-emphasis text-6xl">500</h1>
<h2 className="text-emphasis mt-6 text-2xl font-medium">It&apos;s not you, it&apos;s us.</h2>
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
away for you.
</p>
+10 -6
View File
@@ -13,7 +13,6 @@ import {
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
import { WEBAPP_URL } from "@calcom/lib/constants";
import defaultEvents, {
getDynamicEventDescription,
getGroupName,
@@ -37,7 +36,8 @@ import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
export type UserPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export function UserPage(props: UserPageProps) {
const {
users,
profile,
@@ -102,6 +102,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const query = { ...router.query };
delete query.user; // So it doesn't display in the Link (and make tests fail)
delete query.orgSlug;
const nameOrUsername = user.name || user.username || "";
/*
@@ -132,7 +133,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
"max-w-3xl py-24 px-4"
"max-w-3xl px-4 py-24"
)}>
{isSingleUser && ( // When we deal with a single user, not dynamic group
<div className="mb-8 text-center">
@@ -207,8 +208,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
);
}
User.isBookingPage = true;
User.PageWrapper = PageWrapper;
UserPage.isBookingPage = true;
UserPage.PageWrapper = PageWrapper;
const getEventTypesWithHiddenFromDB = async (userId: number) => {
return (
@@ -289,7 +290,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const users = usersWithoutAvatar.map((user) => ({
...user,
avatar: `${WEBAPP_URL}/${user.username}/avatar.png`,
avatar: `/${user.username}/avatar.png`,
}));
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
@@ -360,6 +361,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const safeBio = markdownToSafeHTML(user.bio) || "";
const markdownStrippedBio = stripMarkdown(user?.bio || "");
return {
props: {
users,
@@ -385,3 +387,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
};
};
export default UserPage;
+142 -319
View File
@@ -1,97 +1,61 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import type { LocationObject } from "@calcom/app-store/locations";
import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { classNames } from "@calcom/lib";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { User } from "@calcom/prisma/client";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssrInit } from "@server/lib/ssr";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export type AvailabilityPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function Type(props: AvailabilityPageProps) {
const { t } = useLocale();
return props.away ? (
<div className="dark:bg-inverted h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-emphasis600 mb-2 text-3xl">
😴{" " + t("user_away")}
</h2>
<p className="mx-auto max-w-md">{t("user_away_description")}</p>
</div>
</div>
</div>
</main>
</div>
) : props.isDynamic && !props.profile.allowDynamicBooking ? (
<div className="dark:bg-darkgray-50 h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-emphasis600 mb-2 text-3xl">
{" " + t("unavailable")}
</h2>
<p className="mx-auto max-w-md">{t("user_dynamic_booking_disabled")}</p>
</div>
</div>
</div>
</main>
</div>
) : !props.isValidOrgDomain && props.organizationContext ? (
<div className="dark:bg-darkgray-50 h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-emphasis600 mb-2 text-3xl">
{" " + t("unavailable")}
</h2>
<p className="mx-auto max-w-md">{t("user_belongs_organization")}</p>
</div>
</div>
</div>
</main>
</div>
) : (
<AvailabilityPage {...props} />
export default function Type({ slug, user, booking, away, isBrandingHidden, rescheduleUid, org }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
<BookerSeo
username={user}
eventSlug={slug}
rescheduleUid={rescheduleUid ?? undefined}
hideBranding={isBrandingHidden}
org={org}
/>
<Booker
username={user}
eventSlug={slug}
bookingData={booking}
isAway={away}
hideBranding={isBrandingHidden}
org={org}
/>
</main>
);
}
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
const paramsSchema = z.object({ type: z.string(), user: z.string() });
async function getUserPageProps(context: GetServerSidePropsContext) {
// load server side dependencies
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
const { EventTypeMetaDataSchema, teamMetadataSchema } = await import("@calcom/prisma/zod-utils");
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const ssr = await ssrInit(context);
const { type: slug, user: username } = paramsSchema.parse(context.query);
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
const { rescheduleUid, bookingUid } = context.query;
const user = await prisma.user.findFirst({
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const users = await prisma.user.findMany({
where: {
/** TODO: We should standarize this */
username: username.toLowerCase().replace(/( |%20)/g, "+"),
username: {
in: usernames,
},
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
@@ -99,183 +63,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
: null,
},
select: {
id: true,
username: true,
away: true,
name: true,
hideBranding: true,
timeZone: true,
theme: true,
weekStart: true,
brandColor: true,
darkBrandColor: true,
metadata: true,
organizationId: true,
eventTypes: {
where: {
// Many-to-many relationship causes inclusion of the team events - cool -
// but to prevent these from being selected, make sure the teamId is NULL.
AND: [{ slug }, { teamId: null }],
},
select: {
title: true,
slug: true,
hidden: true,
recurringEvent: true,
length: true,
locations: true,
id: true,
description: true,
price: true,
currency: true,
requiresConfirmation: true,
schedulingType: true,
metadata: true,
seatsPerTimeSlot: true,
team: {
select: {
logo: true,
parent: {
select: {
logo: true,
name: true,
},
},
},
},
},
orderBy: [
{
position: "desc",
},
{
id: "asc",
},
],
},
teams: {
include: {
team: true,
},
},
},
});
if (!user || !user.eventTypes.length) return { notFound: true };
const [eventType]: ((typeof user.eventTypes)[number] & {
users: Pick<User, "name" | "username" | "hideBranding" | "timeZone">[];
})[] = [
{
...user.eventTypes[0],
users: [
{
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
timeZone: user.timeZone,
},
],
},
];
if (!eventType) return { notFound: true };
//TODO: Use zodSchema to verify it instead of using Type Assertion
const locations = eventType.locations ? (eventType.locations as LocationObject[]) : [];
const eventTypeObject = Object.assign({}, eventType, {
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
locations: privacyFilteredLocations(locations),
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
});
// Check if the user you are logging into has any active teams or premium user name
const hasActiveTeam =
user.teams.filter((m) => {
if (!IS_TEAM_BILLING_ENABLED) return true;
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return true;
return false;
}).length > 0;
const hasPremiumUserName = hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false;
return {
props: {
eventType: eventTypeObject,
profile: {
...eventType.users[0],
theme: user.theme,
allowDynamicBooking: false,
weekStart: user.weekStart,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
slug: `${user.username}/${eventType.slug}`,
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
},
// Dynamic group has no theme preference right now. It uses system theme.
themeBasis: user.username,
organizationContext: user?.organizationId !== null,
away: user?.away,
isDynamic: false,
trpcState: ssr.dehydrate(),
isValidOrgDomain: orgDomainConfig(context.req.headers.host ?? ""),
isBrandingHidden: isBrandingHidden(user.hideBranding, hasActiveTeam || hasPremiumUserName),
},
};
}
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
// load server side dependencies
const { getDefaultEvent, getGroupName, getUsernameList } = await import("@calcom/lib/defaultEvents");
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const { EventTypeMetaDataSchema, userMetadata: userMetadataSchema } = await import(
"@calcom/prisma/zod-utils"
);
const ssr = await ssrInit(context);
const { getAppFromSlug } = await import("@calcom/app-store/utils");
const { type: typeParam, user: userParam } = paramsSchema.parse(context.params);
const usernameList = getUsernameList(userParam);
const length = parseInt(typeParam);
const eventType = getDefaultEvent("" + length);
const users = await prisma.user.findMany({
where: {
username: {
in: usernameList,
},
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
startTime: true,
endTime: true,
timeZone: true,
weekStart: true,
availability: true,
hideBranding: true,
brandColor: true,
darkBrandColor: true,
defaultScheduleId: true,
allowDynamicBooking: true,
metadata: true,
organizationId: true,
away: true,
schedules: {
select: {
availability: true,
timeZone: true,
id: true,
},
},
theme: true,
},
});
@@ -284,87 +72,122 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
notFound: true,
};
}
const org = isValidOrgDomain ? currentOrgDomain : null;
// sort and be in the same order as usernameList so first user is the first user in the list
let sortedUsers: typeof users = [];
if (users.length > 1) {
sortedUsers = users.sort((a, b) => {
const aIndex = (a.username && usernameList.indexOf(a.username)) || 0;
const bIndex = (b.username && usernameList.indexOf(b.username)) || 0;
return aIndex - bIndex;
});
let booking: GetBookingType | null = null;
if (rescheduleUid) {
booking = await getBookingForReschedule(`${rescheduleUid}`);
} else if (bookingUid) {
booking = await getBookingForSeatedEvent(`${bookingUid}`);
}
let locations = eventType.locations ? (eventType.locations as LocationObject[]) : [];
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({
username: usernames.join("+"),
eventSlug: slug,
org,
});
// Get the prefered location type from the first user
const firstUsersMetadata = userMetadataSchema.parse(sortedUsers[0].metadata || {});
const preferedLocationType = firstUsersMetadata?.defaultConferencingApp;
if (preferedLocationType?.appSlug) {
const foundApp = getAppFromSlug(preferedLocationType.appSlug);
const appType = foundApp?.appData?.location?.type;
if (appType) {
// Replace the location with the prefered location type
// This will still be default to daily if the app is not found
locations = [{ type: appType, link: preferedLocationType.appLink }] as LocationObject[];
}
if (!eventData) {
return {
notFound: true,
};
}
const eventTypeObject = Object.assign({}, eventType, {
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
locations: privacyFilteredLocations(locations),
users: users.map((user) => {
return {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
timeZone: user.timeZone,
};
}),
});
const dynamicNames = users.map((user) => {
return user.name || "";
});
const profile = {
name: getGroupName(dynamicNames),
image: null,
slug: "" + length,
theme: null as string | null,
weekStart: "Sunday",
brandColor: "",
darkBrandColor: "",
allowDynamicBooking: !users.some((user) => {
return !user.allowDynamicBooking;
}),
};
return {
props: {
eventType: eventTypeObject,
profile,
// Dynamic group has no theme preference right now. It uses system theme.
themeBasis: null,
isDynamic: true,
org,
booking,
user: usernames.join("+"),
slug,
away: false,
organizationContext: !users.some((user) => user.organizationId === null),
trpcState: ssr.dehydrate(),
isValidOrgDomain: orgDomainConfig(context.req.headers.host ?? ""),
isBrandingHidden: false, // I think we should always show branding for dynamic groups - saves us checking every single user
isBrandingHidden: false,
themeBasis: null,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
},
};
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { user: userParam } = paramsSchema.parse(context.params);
// dynamic groups are not generated at build time, but otherwise are probably cached until infinity.
const isDynamicGroup = getUsernameList(userParam).length > 1;
if (isDynamicGroup) {
return await getDynamicGroupPageProps(context);
} else {
return await getUserPageProps(context);
async function getUserPageProps(context: GetServerSidePropsContext) {
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
const username = usernames[0];
const { rescheduleUid, bookingUid } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const user = await prisma.user.findFirst({
where: {
username,
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
away: true,
hideBranding: true,
},
});
if (!user) {
return {
notFound: true,
};
}
let booking: GetBookingType | null = null;
if (rescheduleUid) {
booking = await getBookingForReschedule(`${rescheduleUid}`);
} else if (bookingUid) {
booking = await getBookingForSeatedEvent(`${bookingUid}`);
}
const org = isValidOrgDomain ? currentOrgDomain : null;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({
username,
eventSlug: slug,
org,
});
if (!eventData) {
return {
notFound: true,
};
}
return {
props: {
booking,
away: user?.away,
user: username,
slug,
org,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
themeBasis: username,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
},
};
}
const paramsSchema = z.object({
type: z.string().transform((s) => slugify(s)),
user: z.string().transform((s) => getUsernameList(s)),
});
// Booker page fetches a tiny bit of data server side, to determine early
// whether the page should show an away state or dynamic booking not allowed.
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { user } = paramsSchema.parse(context.params);
const isDynamicGroup = user.length > 1;
return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
};
+6 -14
View File
@@ -1,19 +1,11 @@
import type { GetServerSidePropsContext } from "next";
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssrResponse = await _getServerSideProps(context);
if (ssrResponse.notFound) {
return ssrResponse;
}
return {
...ssrResponse,
props: {
...ssrResponse.props,
isEmbed: true,
},
};
};
// Somehow these types don't accept the {notFound: true} return type.
// Probably still need to fix this. I don't know why this isn't allowed yet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
-309
View File
@@ -1,309 +0,0 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import type { LocationObject } from "@calcom/app-store/locations";
import { privacyFilteredLocations } from "@calcom/app-store/locations";
import { getAppFromSlug } from "@calcom/app-store/utils";
import dayjs from "@calcom/dayjs";
import getBooking from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
import { parseRecurringEvent } from "@calcom/lib";
import {
getDefaultEvent,
getDynamicEventName,
getGroupName,
getUsernameList,
} from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import prisma, { bookEventTypeSelect } from "@calcom/prisma";
import {
customInputSchema,
EventTypeMetaDataSchema,
userMetadata as userMetadataSchema,
} from "@calcom/prisma/zod-utils";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import BookingPage from "@components/booking/pages/BookingPage";
import { ssrInit } from "@server/lib/ssr";
export type BookPageProps = inferSSRProps<typeof getServerSideProps>;
export default function Book(props: BookPageProps) {
const { t } = useLocale();
return props.away ? (
<div className="dark:bg-inverted h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-default mb-2 text-3xl">
😴{" " + t("user_away")}
</h2>
<p className="mx-auto max-w-md">{t("user_away_description")}</p>
</div>
</div>
</div>
</main>
</div>
) : props.isDynamicGroupBooking && !props.profile.allowDynamicBooking ? (
<div className="dark:bg-inverted h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-default mb-2 text-3xl">
{" " + t("unavailable")}
</h2>
<p className="mx-auto max-w-md">{t("user_dynamic_booking_disabled")}</p>
</div>
</div>
</div>
</main>
</div>
) : (
<BookingPage {...props} />
);
}
Book.isBookingPage = true;
Book.PageWrapper = PageWrapper;
const querySchema = z.object({
bookingUid: z.string().optional(),
count: z.coerce.number().optional(),
embed: z.string().optional(),
rescheduleUid: z.string().optional(),
slug: z.string().optional(),
/** This is the event "type" ID */
type: z.coerce.number().optional(),
user: z.string(),
seatReferenceUid: z.string().optional(),
date: z.string().optional(),
duration: z.coerce.number().optional(),
});
export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
const query = querySchema.parse(context.query);
const usernameList = getUsernameList(query.user);
const eventTypeSlug = query.slug;
const recurringEventCountQuery = query.count;
const users = await prisma.user.findMany({
where: {
username: {
in: usernameList,
},
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
theme: true,
brandColor: true,
darkBrandColor: true,
allowDynamicBooking: true,
away: true,
metadata: true,
},
});
if (!users.length) return { notFound: true };
const [user] = users;
const isDynamicGroupBooking = users.length > 1 && !!eventTypeSlug;
// Dynamic Group link doesn't need a type but it must have a slug
if ((!isDynamicGroupBooking && !query.type) || (users.length > 1 && !eventTypeSlug)) {
return { notFound: true };
}
const eventTypeRaw = isDynamicGroupBooking
? getDefaultEvent(eventTypeSlug)
: await prisma.eventType.findUnique({
where: {
id: query.type,
},
select: {
...bookEventTypeSelect,
},
});
if (!eventTypeRaw) return { notFound: true };
const eventType = {
...eventTypeRaw,
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata || {}),
bookingFields: getBookingFieldsWithSystemFields(eventTypeRaw),
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
};
const getLocations = () => {
let locations = eventTypeRaw.locations || [];
if (!isDynamicGroupBooking) return locations;
let sortedUsers: typeof users = [];
// sort and be in the same order as usernameList so first user is the first user in the list
if (users.length > 1) {
sortedUsers = users.sort((a, b) => {
const aIndex = (a.username && usernameList.indexOf(a.username)) || 0;
const bIndex = (b.username && usernameList.indexOf(b.username)) || 0;
return aIndex - bIndex;
});
}
// Get the prefered location type from the first user
const firstUsersMetadata = userMetadataSchema.parse(sortedUsers[0].metadata || {});
const preferedLocationType = firstUsersMetadata?.defaultConferencingApp;
if (preferedLocationType?.appSlug) {
const foundApp = getAppFromSlug(preferedLocationType.appSlug);
const appType = foundApp?.appData?.location?.type;
if (appType) {
// Replace the location with the prefered location type
// This will still be default to daily if the app is not found
locations = [{ type: appType, link: preferedLocationType.appLink }] as LocationObject[];
}
}
return locations;
};
const eventTypeObject = [eventType].map((e) => {
let locations = getLocations();
locations = privacyFilteredLocations(locations as LocationObject[]);
return {
...e,
locations: locations,
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
schedulingType: null,
customInputs: customInputSchema.array().parse(e.customInputs || []),
users: users.map((u) => ({
id: u.id,
name: u.name,
username: u.username,
avatar: u.avatar,
image: u.avatar,
slug: u.username,
theme: u.theme,
})),
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
};
})[0];
// If rescheduleUid and event has seats lets convert Uid to bookingUid
let rescheduleUid = query.rescheduleUid;
const rescheduleEventTypeHasSeats = query.rescheduleUid && eventTypeRaw.seatsPerTimeSlot;
let attendeeEmail: string;
let bookingUidWithSeats: string | null = null;
if (rescheduleEventTypeHasSeats) {
const bookingSeat = await prisma.bookingSeat.findFirst({
where: {
referenceUid: query.rescheduleUid,
},
select: {
id: true,
attendee: true,
booking: {
select: {
uid: true,
},
},
},
});
if (bookingSeat) {
rescheduleUid = bookingSeat.booking.uid;
attendeeEmail = bookingSeat.attendee.email;
}
}
if (query.duration) {
// If it's not reschedule but event Type has seats we should obtain
// the bookingUid regardless and use it to get the booking
const currentSeats = await prisma.booking.findFirst({
where: {
eventTypeId: eventTypeRaw.id,
startTime: dayjs(query.date).toISOString(),
endTime: dayjs(query.date).add(query.duration, "minutes").toISOString(),
},
select: {
uid: true,
},
});
if (currentSeats && currentSeats) {
bookingUidWithSeats = currentSeats.uid;
}
}
let booking: GetBookingType | null = null;
if (rescheduleUid || query.bookingUid || bookingUidWithSeats) {
booking = await getBooking(prisma, rescheduleUid || query.bookingUid || bookingUidWithSeats || "");
}
if (rescheduleEventTypeHasSeats && booking?.attendees && booking?.attendees.length > 0) {
const currentAttendee = booking?.attendees.find((attendee) => {
return attendee.email === attendeeEmail;
});
if (currentAttendee) {
booking.attendees = [currentAttendee] || [];
}
}
const dynamicNames = isDynamicGroupBooking ? users.map((user) => user.name || "") : [];
const profile = isDynamicGroupBooking
? {
name: getGroupName(dynamicNames),
image: null,
slug: eventTypeSlug,
theme: null,
brandColor: "",
darkBrandColor: "",
allowDynamicBooking: !users.some((user) => !user.allowDynamicBooking),
eventName: getDynamicEventName(dynamicNames, eventTypeSlug),
}
: {
name: user.name || user.username,
image: user.avatar,
slug: user.username,
theme: user.theme,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
eventName: null,
};
// Checking if number of recurring event ocurrances is valid against event type configuration
const recurringEventCount =
(eventType.recurringEvent?.count &&
recurringEventCountQuery &&
(recurringEventCountQuery <= eventType.recurringEvent.count
? recurringEventCountQuery
: eventType.recurringEvent.count)) ||
null;
const currentSlotBooking = await getBooking(prisma, bookingUidWithSeats || "");
return {
props: {
away: user.away,
profile,
// Dynamic group has no theme preference right now. It uses system theme.
themeBasis: isDynamicGroupBooking ? null : user.username,
eventType: eventTypeObject,
booking,
currentSlotBooking: currentSlotBooking,
recurringEventCount,
trpcState: ssr.dehydrate(),
isDynamicGroupBooking,
hasHashedBookingLink: false,
hashedLink: null,
isEmbed: !!query.embed,
},
};
}
@@ -1 +0,0 @@
export { getStaticProps, getStaticPaths, default } from "./index";

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